diff --git a/examples/authors/sqlc.json b/examples/authors/sqlc.json deleted file mode 100644 index 105d591628..0000000000 --- a/examples/authors/sqlc.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "version": "2", - "sql": [ - { - "schema": "postgresql/schema.sql", - "queries": "postgresql/query.sql", - "engine": "postgresql", - "database": { - "url": "postgresql://${PG_USER}:${PG_PASSWORD}@${PG_HOST}:${PG_PORT}/authors" - }, - "gen": { - "go": { - "package": "authors", - "out": "postgresql" - } - } - }, - { - "schema": "mysql/schema.sql", - "queries": "mysql/query.sql", - "engine": "mysql", - "database": { - "url": "root:${MYSQL_ROOT_PASSWORD}@tcp(${MYSQL_HOST}:${MYSQL_PORT})/authors?multiStatements=true&parseTime=true" - }, - "gen": { - "go": { - "package": "authors", - "out": "mysql" - } - } - }, - { - "schema": "sqlite/schema.sql", - "queries": "sqlite/query.sql", - "engine": "sqlite", - "database": { - "url": "file:authors?mode=memory&cache=shared" - }, - "gen": { - "go": { - "package": "authors", - "out": "sqlite" - } - } - } - ] -} \ No newline at end of file diff --git a/examples/authors/sqlc.yaml b/examples/authors/sqlc.yaml new file mode 100644 index 0000000000..a3995835fa --- /dev/null +++ b/examples/authors/sqlc.yaml @@ -0,0 +1,35 @@ +version: '2' +sql: +- schema: postgresql/schema.sql + queries: postgresql/query.sql + engine: postgresql + database: + url: postgresql://${PG_USER}:${PG_PASSWORD}@${PG_HOST}:${PG_PORT}/authors + rules: + - sqlc/db-prepare + gen: + go: + package: authors + out: postgresql +- schema: mysql/schema.sql + queries: mysql/query.sql + engine: mysql + database: + url: root:${MYSQL_ROOT_PASSWORD}@tcp(${MYSQL_HOST}:${MYSQL_PORT})/authors?multiStatements=true&parseTime=true + rules: + - sqlc/db-prepare + gen: + go: + package: authors + out: mysql +- schema: sqlite/schema.sql + queries: sqlite/query.sql + engine: sqlite + database: + url: file:authors?mode=memory&cache=shared + rules: + - sqlc/db-prepare + gen: + go: + package: authors + out: sqlite \ No newline at end of file diff --git a/examples/batch/sqlc.json b/examples/batch/sqlc.json index 899d116f5f..d19c3cfc13 100644 --- a/examples/batch/sqlc.json +++ b/examples/batch/sqlc.json @@ -10,6 +10,9 @@ "database": { "url": "postgresql://${PG_USER}:${PG_PASSWORD}@${PG_HOST}:${PG_PORT}/batch" }, + "rules": [ + "sqlc/db-prepare" + ], "sql_package": "pgx/v4", "emit_json_tags": true, "emit_prepared_queries": true, diff --git a/examples/booktest/sqlc.json b/examples/booktest/sqlc.json index da33d78f54..64fc96eea7 100644 --- a/examples/booktest/sqlc.json +++ b/examples/booktest/sqlc.json @@ -9,7 +9,10 @@ "engine": "postgresql", "database": { "url": "postgresql://${PG_USER}:${PG_PASSWORD}@${PG_HOST}:${PG_PORT}/booktest" - } + }, + "rules": [ + "sqlc/db-prepare" + ] }, { "name": "booktest", @@ -19,7 +22,10 @@ "engine": "mysql", "database": { "url": "root:${MYSQL_ROOT_PASSWORD}@tcp(${MYSQL_HOST}:${MYSQL_PORT})/booktest?multiStatements=true&parseTime=true" - } + }, + "rules": [ + "sqlc/db-prepare" + ] }, { "name": "booktest", @@ -29,7 +35,10 @@ "engine": "sqlite", "database": { "url": "file:booktest?mode=memory&cache=shared" - } + }, + "rules": [ + "sqlc/db-prepare" + ] } ] } \ No newline at end of file diff --git a/examples/jets/sqlc.json b/examples/jets/sqlc.json index 412de61761..c29494bd4b 100644 --- a/examples/jets/sqlc.json +++ b/examples/jets/sqlc.json @@ -9,7 +9,10 @@ "engine": "postgresql", "database": { "url": "postgresql://${PG_USER}:${PG_PASSWORD}@${PG_HOST}:${PG_PORT}/jets" - } + }, + "rules": [ + "sqlc/db-prepare" + ] } ] } diff --git a/examples/ondeck/sqlc.json b/examples/ondeck/sqlc.json index d6139b580c..e0896eed70 100644 --- a/examples/ondeck/sqlc.json +++ b/examples/ondeck/sqlc.json @@ -10,6 +10,9 @@ "database": { "url": "postgresql://${PG_USER}:${PG_PASSWORD}@${PG_HOST}:${PG_PORT}/ondeck" }, + "rules": [ + "sqlc/db-prepare" + ], "emit_json_tags": true, "emit_prepared_queries": true, "emit_interface": true @@ -23,6 +26,9 @@ "database": { "url": "root:${MYSQL_ROOT_PASSWORD}@tcp(${MYSQL_HOST}:${MYSQL_PORT})/ondeck?multiStatements=true&parseTime=true" }, + "rules": [ + "sqlc/db-prepare" + ], "emit_json_tags": true, "emit_prepared_queries": true, "emit_interface": true @@ -36,6 +42,9 @@ "database": { "url": "file:ondeck?mode=memory&cache=shared" }, + "rules": [ + "sqlc/db-prepare" + ], "emit_json_tags": true, "emit_prepared_queries": true, "emit_interface": true diff --git a/internal/cmd/vet.go b/internal/cmd/vet.go index f6e213300b..19040cf8ce 100644 --- a/internal/cmd/vet.go +++ b/internal/cmd/vet.go @@ -14,6 +14,7 @@ import ( _ "github.com/go-sql-driver/mysql" "github.com/google/cel-go/cel" + "github.com/google/cel-go/common/types/ref" "github.com/google/cel-go/ext" "github.com/jackc/pgx/v5" _ "github.com/mattn/go-sqlite3" @@ -29,6 +30,8 @@ import ( var ErrFailedChecks = errors.New("failed checks") +const RuleDbPrepare = "sqlc/db-prepare" + func NewCmdVet() *cobra.Command { return &cobra.Command{ Use: "vet", @@ -48,6 +51,17 @@ func NewCmdVet() *cobra.Command { } } +type emptyProgram struct { +} + +func (e *emptyProgram) Eval(any) (ref.Val, *cel.EvalDetails, error) { + return nil, nil, fmt.Errorf("unimplemented") +} + +func (e *emptyProgram) ContextEval(ctx context.Context, a any) (ref.Val, *cel.EvalDetails, error) { + return e.Eval(a) +} + func Vet(ctx context.Context, e Env, dir, filename string, stderr io.Writer) error { configPath, conf, err := readConfig(stderr, dir, filename) if err != nil { @@ -83,7 +97,9 @@ func Vet(ctx context.Context, e Env, dir, filename string, stderr io.Writer) err return fmt.Errorf("new env: %s", err) } - checks := map[string]cel.Program{} + checks := map[string]cel.Program{ + RuleDbPrepare: &emptyProgram{}, + } msgs := map[string]string{} for _, c := range conf.Rules { @@ -278,16 +294,26 @@ func (c *checker) checkSQL(ctx context.Context, s config.SQL) error { req := codeGenRequest(result, combo) cfg := vetConfig(req) for i, query := range req.Queries { - original := result.Queries[i] - if prep != nil && prepareable(s, original.RawStmt) { - name := fmt.Sprintf("sqlc_vet_%d_%d", time.Now().Unix(), i) - if err := prep.Prepare(ctx, name, query.Text); err != nil { - fmt.Fprintf(c.Stderr, "%s: error preparing %s on %s: %s\n", query.Filename, query.Name, s.Engine, err) - errored = true - } - } q := vetQuery(query) for _, name := range s.Rules { + // Built-in rule + if name == RuleDbPrepare { + if prep == nil { + fmt.Fprintf(c.Stderr, "%s: %s: %s: error preparing query: database connection required\n", query.Filename, q.Name, name) + errored = true + continue + } + original := result.Queries[i] + if prepareable(s, original.RawStmt) { + name := fmt.Sprintf("sqlc_vet_%d_%d", time.Now().Unix(), i) + if err := prep.Prepare(ctx, name, query.Text); err != nil { + fmt.Fprintf(c.Stderr, "%s: %s: %s: error preparing query: %s\n", query.Filename, q.Name, name, err) + errored = true + } + } + continue + } + prg, ok := c.Checks[name] if !ok { return fmt.Errorf("type-check error: a check with the name '%s' does not exist", name) diff --git a/internal/config/v_one.go b/internal/config/v_one.go index 122fe835d3..4ad96ea9eb 100644 --- a/internal/config/v_one.go +++ b/internal/config/v_one.go @@ -15,6 +15,7 @@ type V1GenerateSettings struct { Packages []v1PackageSettings `json:"packages" yaml:"packages"` Overrides []Override `json:"overrides,omitempty" yaml:"overrides,omitempty"` Rename map[string]string `json:"rename,omitempty" yaml:"rename,omitempty"` + Rules []Rule `json:"rules" yaml:"rules"` } type v1PackageSettings struct { @@ -51,6 +52,7 @@ type v1PackageSettings struct { StrictOrderBy *bool `json:"strict_order_by" yaml:"strict_order_by"` QueryParameterLimit *int32 `json:"query_parameter_limit,omitempty" yaml:"query_parameter_limit"` OmitUnusedStructs bool `json:"omit_unused_structs,omitempty" yaml:"omit_unused_structs"` + Rules []string `json:"rules" yaml:"rules"` } func v1ParseConfig(rd io.Reader) (Config, error) { @@ -131,6 +133,7 @@ func (c *V1GenerateSettings) Translate() Config { Version: c.Version, Project: c.Project, Cloud: c.Cloud, + Rules: c.Rules, } for _, pkg := range c.Packages { @@ -143,6 +146,7 @@ func (c *V1GenerateSettings) Translate() Config { Database: pkg.Database, Schema: pkg.Schema, Queries: pkg.Queries, + Rules: pkg.Rules, Gen: SQLGen{ Go: &SQLGo{ EmitInterface: pkg.EmitInterface,