Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Generate read-only Queries #3291

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 40 additions & 7 deletions internal/codegen/golang/gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@ import (
)

type tmplCtx struct {
Q string
Package string
SQLDriver opts.SQLDriver
Enums []Enum
Structs []Struct
GoQueries []Query
SqlcVersion string
Q string
Package string
SQLDriver opts.SQLDriver
Enums []Enum
Structs []Struct
GoQueries []Query
GoReadQueries []Query
SqlcVersion string

// TODO: Race conditions
SourceName string
Expand All @@ -37,6 +38,7 @@ type tmplCtx struct {
EmitMethodsWithDBArgument bool
EmitEnumValidMethod bool
EmitAllEnumValues bool
EmitReadOnlyPrepared bool
UsesCopyFrom bool
UsesBatch bool
OmitSqlcVersion bool
Expand Down Expand Up @@ -177,6 +179,7 @@ func generate(req *plugin.GenerateRequest, options *opts.Options, enums []Enum,
EmitMethodsWithDBArgument: options.EmitMethodsWithDbArgument,
EmitEnumValidMethod: options.EmitEnumValidMethod,
EmitAllEnumValues: options.EmitAllEnumValues,
EmitReadOnlyPrepared: options.EmitReadOnlyPrepared,
UsesCopyFrom: usesCopyFrom(queries),
UsesBatch: usesBatch(queries),
SQLDriver: parseDriver(options.SqlPackage),
Expand Down Expand Up @@ -240,6 +243,7 @@ func generate(req *plugin.GenerateRequest, options *opts.Options, enums []Enum,
w := bufio.NewWriter(&b)
tctx.SourceName = name
tctx.GoQueries = replacedQueries
tctx.GoReadQueries = readOnly(replacedQueries)
err := tmpl.ExecuteTemplate(w, templateName, &tctx)
w.Flush()
if err != nil {
Expand Down Expand Up @@ -278,6 +282,7 @@ func generate(req *plugin.GenerateRequest, options *opts.Options, enums []Enum,
if options.OutputCopyfromFileName != "" {
copyfromFileName = options.OutputCopyfromFileName
}
readQueriesFileName := "read.go"

batchFileName := "batch.go"
if options.OutputBatchFileName != "" {
Expand Down Expand Up @@ -305,6 +310,11 @@ func generate(req *plugin.GenerateRequest, options *opts.Options, enums []Enum,
return nil, err
}
}
if tctx.EmitReadOnlyPrepared {
if err := execute(readQueriesFileName, "readQueriesFile"); err != nil {
return nil, err
}
}

files := map[string]struct{}{}
for _, gq := range queries {
Expand Down Expand Up @@ -405,3 +415,26 @@ func filterUnusedStructs(enums []Enum, structs []Struct, queries []Query) ([]Enu

return keepEnums, keepStructs
}

func readOnly(queries []Query) []Query {
var rq []Query
for _, q := range queries {
if !q.hasRetType() {
continue
}

qsql := strings.ToUpper(q.SQL)
if !strings.Contains(qsql, "SELECT ") {
continue
}
if strings.Contains(qsql, "INSERT ") ||
strings.Contains(qsql, "UPDATE ") ||
strings.Contains(qsql, "FOR UPDATE") ||
strings.Contains(qsql, "DELETE ") {
continue
}

rq = append(rq, q)
}
return rq
}
Copy link

@K-odesome K-odesome Mar 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we do it something like this

func readOnly(queries []Query) []Query {
	var rq []Query
	for _, q := range queries {
		if !q.hasRetType() {
			continue
		}

		qsql := strings.ToUpper(q.SQL)
		if !containsOnlyAllowedKeywords(qsql) {
			continue
		}

		rq = append(rq, q)
	}
	return rq
}

func containsOnlyAllowedKeywords(query string) bool {
	keywords := []string{"INSERT", "UPDATE", "DELETE","CREATE","ALTER"} // and the other keywords
		for _, keyword := range keywords {
                          if strings.Contains(qsql, keyword) {
                             return false
                          }		
	}

	return true
}

So that we can define the list of exluded words

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

queries wont/shouldn't have ddl. so we can rule out CREATE and ALTER

3 changes: 3 additions & 0 deletions internal/codegen/golang/imports.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ func (i *importer) Imports(filename string) [][]ImportSpec {
if i.Options.OutputBatchFileName != "" {
batchFileName = i.Options.OutputBatchFileName
}
readQueriesFileName := "read.go"

switch filename {
case dbFileName:
Expand All @@ -113,6 +114,8 @@ func (i *importer) Imports(filename string) [][]ImportSpec {
return mergeImports(i.copyfromImports())
case batchFileName:
return mergeImports(i.batchImports())
case readQueriesFileName:
return mergeImports(i.dbImports())
default:
return mergeImports(i.queryImports(filename))
}
Expand Down
1 change: 1 addition & 0 deletions internal/codegen/golang/opts/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type Options struct {
EmitEnumValidMethod bool `json:"emit_enum_valid_method,omitempty" yaml:"emit_enum_valid_method"`
EmitAllEnumValues bool `json:"emit_all_enum_values,omitempty" yaml:"emit_all_enum_values"`
EmitSqlAsComment bool `json:"emit_sql_as_comment,omitempty" yaml:"emit_sql_as_comment"`
EmitReadOnlyPrepared bool `json:"emit_read_only_prepared,omitempty" yaml:"emit_read_only_prepared"`
JsonTagsCaseStyle string `json:"json_tags_case_style,omitempty" yaml:"json_tags_case_style"`
Package string `json:"package" yaml:"package"`
Out string `json:"out" yaml:"out"`
Expand Down
108 changes: 108 additions & 0 deletions internal/codegen/golang/templates/stdlib/readonlyCode.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
{{define "readQueriesFileStd"}}

{{if and .EmitPreparedQueries .EmitReadOnlyPrepared}}
func PrepareReadOnly(ctx context.Context, db DBTX) (*ReadQueries, error) {
q := ReadQueries{db: db}
var err error
{{- if eq (len .GoReadQueries) 0 }}
_ = err
{{- end }}
{{- range .GoReadQueries }}
if q.{{.FieldName}}, err = db.PrepareContext(ctx, {{.ConstantName}}); err != nil {
return nil, fmt.Errorf("error preparing query {{.MethodName}}: %w", err)
}
{{- end}}
return &q, nil
}

type ReadQueries struct {
{{- if not .EmitMethodsWithDBArgument}}
db DBTX
{{- end}}

{{- if .EmitPreparedQueries}}
{{- range .GoReadQueries}}
{{.FieldName}} *sql.Stmt
{{- end}}
{{- end}}
}

func (q *ReadQueries) Close() error {
var err error
{{- range .GoReadQueries }}
if q.{{.FieldName}} != nil {
if cerr := q.{{.FieldName}}.Close(); cerr != nil {
err = fmt.Errorf("error closing {{.FieldName}}: %w", cerr)
}
}
{{- end}}
return err
}

func (q *ReadQueries) query(ctx context.Context, stmt *sql.Stmt, query string, args ...interface{}) (*sql.Rows, error) {
switch {
case stmt != nil:
return stmt.QueryContext(ctx, args...)
default:
return q.db.QueryContext(ctx, query, args...)
}
}

func (q *ReadQueries) queryRow(ctx context.Context, stmt *sql.Stmt, query string, args ...interface{}) (*sql.Row) {
switch {
case stmt != nil:
return stmt.QueryRowContext(ctx, args...)
default:
return q.db.QueryRowContext(ctx, query, args...)
}
}

{{range .GoReadQueries}}

{{if eq .Cmd ":one"}}
{{range .Comments}}//{{.}}
{{end -}}
func (q *ReadQueries) {{.MethodName}}(ctx context.Context, {{ dbarg }} {{.Arg.Pair}}) ({{.Ret.DefineType}}, error) {
{{- template "queryCodeStdExec" . }}
{{- if or (ne .Arg.Pair .Ret.Pair) (ne .Arg.DefineType .Ret.DefineType) }}
var {{.Ret.Name}} {{.Ret.Type}}
{{- end}}
err := row.Scan({{.Ret.Scan}})
return {{.Ret.ReturnName}}, err
}
{{end}}

{{if eq .Cmd ":many"}}
{{range .Comments}}//{{.}}
{{end -}}
func (q *ReadQueries) {{.MethodName}}(ctx context.Context, {{ dbarg }} {{.Arg.Pair}}) ([]{{.Ret.DefineType}}, error) {
{{- template "queryCodeStdExec" . }}
if err != nil {
return nil, err
}
defer rows.Close()
{{- if $.EmitEmptySlices}}
items := []{{.Ret.DefineType}}{}
{{else}}
var items []{{.Ret.DefineType}}
{{end -}}
for rows.Next() {
var {{.Ret.Name}} {{.Ret.Type}}
if err := rows.Scan({{.Ret.Scan}}); err != nil {
return nil, err
}
items = append(items, {{.Ret.ReturnName}})
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
{{end}}

{{end}}
{{end}}
{{end}}
27 changes: 27 additions & 0 deletions internal/codegen/golang/templates/template.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -251,4 +251,31 @@ import (
{{if .SQLDriver.IsPGX }}
{{- template "batchCodePgx" .}}
{{end}}

{{template "readQueriesFile" . }}
{{end}}

{{define "readQueriesFile"}}
{{if not .SQLDriver.IsPGX }}
{{if .BuildTags}}
//go:build {{.BuildTags}}

{{end}}// Code generated by sqlc. DO NOT EDIT.
{{if not .OmitSqlcVersion}}// versions:
// sqlc {{.SqlcVersion}}

package {{.Package}}

{{ if hasImports .SourceName }}
import (
{{range imports .SourceName}}
{{range .}}{{.}}
{{end}}
{{end}}
)
{{end}}

{{- template "readQueriesFileStd" .}}
{{end}}
{{end}}
{{end}}
2 changes: 2 additions & 0 deletions internal/config/v_one.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ type v1PackageSettings struct {
EmitEnumValidMethod bool `json:"emit_enum_valid_method,omitempty" yaml:"emit_enum_valid_method"`
EmitAllEnumValues bool `json:"emit_all_enum_values,omitempty" yaml:"emit_all_enum_values"`
EmitSqlAsComment bool `json:"emit_sql_as_comment,omitempty" yaml:"emit_sql_as_comment"`
EmitReadOnlyPrepared bool `json:"emit_read_only_prepared,omitempty" yaml:"emit_read_only_prepared"`
JSONTagsCaseStyle string `json:"json_tags_case_style,omitempty" yaml:"json_tags_case_style"`
SQLPackage string `json:"sql_package" yaml:"sql_package"`
SQLDriver string `json:"sql_driver" yaml:"sql_driver"`
Expand Down Expand Up @@ -152,6 +153,7 @@ func (c *V1GenerateSettings) Translate() Config {
EmitEnumValidMethod: pkg.EmitEnumValidMethod,
EmitAllEnumValues: pkg.EmitAllEnumValues,
EmitSqlAsComment: pkg.EmitSqlAsComment,
EmitReadOnlyPrepared: pkg.EmitReadOnlyPrepared,
Package: pkg.Name,
Out: pkg.Path,
SqlPackage: pkg.SQLPackage,
Expand Down
Loading
Loading