diff --git a/docs/howto/vet.md b/docs/howto/vet.md index e51c82c747..087bde83e9 100644 --- a/docs/howto/vet.md +++ b/docs/howto/vet.md @@ -7,10 +7,12 @@ Rules are defined in the `sqlc` [configuration](../reference/config) file. They consist of a name, message, and a [Common Expression Language (CEL)](https://github.com/google/cel-spec) expression. Expressions are evaluated using [cel-go](https://github.com/google/cel-go). -If an expression evaluates to `true`, an error is reported using the given message. +If an expression evaluates to `true`, `sqlc vet` will report an error using the given message. -Each expression has access to variables from your sqlc configuration and queries, -defined in the following struct: +## Defining lint rules + +Each lint rule's CEL expression has access to variables from your sqlc configuration and queries, +defined in the following struct. ```proto message Config @@ -109,4 +111,47 @@ example](https://github.com/kyleconroy/sqlc/blob/main/examples/authors/sqlc.yaml Please note that `sqlc` does not manage or migrate your database. Use your migration tool of choice to create the necessary database tables and objects -before running `sqlc vet`. +before running `sqlc vet` with the `sqlc/db-prepare` rule. + +## Running lint rules + +When you add the name of a defined rule to the rules list +for a [sql package](https://docs.sqlc.dev/en/stable/reference/config.html#sql), +`sqlc vet` will evaluate that rule against every query in the package. + +In the example below, two rules are defined but only one is enabled. + +```yaml +version: 2 +sql: + - schema: "query.sql" + queries: "query.sql" + engine: "postgresql" + gen: + go: + package: "authors" + out: "db" + rules: + - no-delete +rules: + - name: no-pg + message: "invalid engine: postgresql" + rule: | + config.engine == "postgresql" + - name: no-delete + message: "don't use delete statements" + rule: | + query.sql.contains("DELETE") +``` + +### Opting-out of lint rules + +For any query, you can tell `sqlc vet` not to evaluate lint rules using the +`@sqlc-vet-disable` query annotation. + +```sql +/* name: GetAuthor :one */ +/* @sqlc-vet-disable */ +SELECT * FROM authors +WHERE id = ? LIMIT 1; +``` \ No newline at end of file diff --git a/internal/cmd/vet.go b/internal/cmd/vet.go index 33d19fec05..1233614340 100644 --- a/internal/cmd/vet.go +++ b/internal/cmd/vet.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "io" + "log" "os" "path/filepath" "runtime/trace" @@ -31,6 +32,7 @@ import ( var ErrFailedChecks = errors.New("failed checks") const RuleDbPrepare = "sqlc/db-prepare" +const QueryFlagSqlcVetDisable = "@sqlc-vet-disable" func NewCmdVet() *cobra.Command { return &cobra.Command{ @@ -249,7 +251,6 @@ func (c *checker) checkSQL(ctx context.Context, s config.SQL) error { return ErrFailedChecks } - // TODO: Add MySQL support var prep preparer if s.Database != nil { if c.NoDatabase { @@ -299,6 +300,12 @@ func (c *checker) checkSQL(ctx context.Context, s config.SQL) error { req := codeGenRequest(result, combo) cfg := vetConfig(req) for i, query := range req.Queries { + if result.Queries[i].Flags[QueryFlagSqlcVetDisable] { + if debug.Active { + log.Printf("Skipping vet rules for query: %s\n", query.Name) + } + continue + } q := vetQuery(query) for _, name := range s.Rules { // Built-in rule diff --git a/internal/endtoend/testdata/vet_disable/exec.json b/internal/endtoend/testdata/vet_disable/exec.json new file mode 100644 index 0000000000..07753636ee --- /dev/null +++ b/internal/endtoend/testdata/vet_disable/exec.json @@ -0,0 +1,3 @@ +{ + "command": "vet" +} diff --git a/internal/endtoend/testdata/vet_disable/query.sql b/internal/endtoend/testdata/vet_disable/query.sql new file mode 100644 index 0000000000..c1eb8d2219 --- /dev/null +++ b/internal/endtoend/testdata/vet_disable/query.sql @@ -0,0 +1,6 @@ +-- name: SkipVet :exec +-- @sqlc-vet-disable +SELECT true; + +-- name: RunVet :exec +SELECT true; \ No newline at end of file diff --git a/internal/endtoend/testdata/vet_disable/sqlc.yaml b/internal/endtoend/testdata/vet_disable/sqlc.yaml new file mode 100644 index 0000000000..ff7c1b5200 --- /dev/null +++ b/internal/endtoend/testdata/vet_disable/sqlc.yaml @@ -0,0 +1,15 @@ +version: 2 +sql: + - schema: "query.sql" + queries: "query.sql" + engine: "postgresql" + gen: + go: + package: "db" + out: "db" + rules: + - always-fail +rules: + - name: always-fail + message: "Fail" + rule: "true" diff --git a/internal/endtoend/testdata/vet_disable/stderr.txt b/internal/endtoend/testdata/vet_disable/stderr.txt new file mode 100644 index 0000000000..9acbb8b34b --- /dev/null +++ b/internal/endtoend/testdata/vet_disable/stderr.txt @@ -0,0 +1 @@ +query.sql: RunVet: always-fail: Fail diff --git a/internal/engine/dolphin/convert.go b/internal/engine/dolphin/convert.go index bd642c55ed..132be0b3f4 100644 --- a/internal/engine/dolphin/convert.go +++ b/internal/engine/dolphin/convert.go @@ -1,7 +1,6 @@ package dolphin import ( - "fmt" "log" "strings" @@ -144,7 +143,7 @@ func (c *cc) convertAlterTableStmt(n *pcast.AlterTableStmt) ast.Node { default: if debug.Active { - fmt.Printf("dolphin.convert: Unknown alter table cmd %v\n", spec.Tp) + log.Printf("dolphin.convert: Unknown alter table cmd %v\n", spec.Tp) } continue } diff --git a/internal/metadata/meta.go b/internal/metadata/meta.go index 3c34eff58a..4176da1e2b 100644 --- a/internal/metadata/meta.go +++ b/internal/metadata/meta.go @@ -115,6 +115,7 @@ func ParseQueryFlags(comments []string) (map[string]bool, error) { if strings.HasPrefix(cleanLine, "@") { flagName := strings.SplitN(cleanLine, " ", 2)[0] flags[flagName] = true + continue } } return flags, nil