Skip to content

Commit

Permalink
feat(cmd/vet): Add built-in db-prepare rule (#2396)
Browse files Browse the repository at this point in the history
* feat(cmd/vet): Add built-in db-prepare rule

Instead of always preparing a query when running vet, opt-in to doing it
with the `db-prepare` rule.

* Error if db-prepare is used when a database connection isn't available

* namespace db-prepare
  • Loading branch information
kyleconroy committed Jun 30, 2023
1 parent a0d5d93 commit 6fd9950
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 60 deletions.
47 changes: 0 additions & 47 deletions examples/authors/sqlc.json

This file was deleted.

35 changes: 35 additions & 0 deletions examples/authors/sqlc.yaml
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions examples/batch/sqlc.json
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
15 changes: 12 additions & 3 deletions examples/booktest/sqlc.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@
"engine": "postgresql",
"database": {
"url": "postgresql://${PG_USER}:${PG_PASSWORD}@${PG_HOST}:${PG_PORT}/booktest"
}
},
"rules": [
"sqlc/db-prepare"
]
},
{
"name": "booktest",
Expand All @@ -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",
Expand All @@ -29,7 +35,10 @@
"engine": "sqlite",
"database": {
"url": "file:booktest?mode=memory&cache=shared"
}
},
"rules": [
"sqlc/db-prepare"
]
}
]
}
5 changes: 4 additions & 1 deletion examples/jets/sqlc.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@
"engine": "postgresql",
"database": {
"url": "postgresql://${PG_USER}:${PG_PASSWORD}@${PG_HOST}:${PG_PORT}/jets"
}
},
"rules": [
"sqlc/db-prepare"
]
}
]
}
9 changes: 9 additions & 0 deletions examples/ondeck/sqlc.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
44 changes: 35 additions & 9 deletions internal/cmd/vet.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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",
Expand All @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions internal/config/v_one.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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 {
Expand All @@ -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,
Expand Down

0 comments on commit 6fd9950

Please sign in to comment.