@@ -6,11 +6,13 @@ import (
66 "encoding/json"
77 "errors"
88 "fmt"
9+ "github.com/sqlc-dev/sqlc/internal/constants"
910 "io"
1011 "log"
1112 "os"
1213 "path/filepath"
1314 "runtime/trace"
15+ "slices"
1416 "strings"
1517 "time"
1618
@@ -37,9 +39,6 @@ var ErrFailedChecks = errors.New("failed checks")
3739
3840var pjson = protojson.UnmarshalOptions {AllowPartial : true , DiscardUnknown : true }
3941
40- const RuleDbPrepare = "sqlc/db-prepare"
41- const QueryFlagSqlcVetDisable = "@sqlc-vet-disable"
42-
4342func NewCmdVet () * cobra.Command {
4443 return & cobra.Command {
4544 Use : "vet" ,
@@ -109,7 +108,7 @@ func Vet(ctx context.Context, dir, filename string, opts *Options) error {
109108 }
110109
111110 rules := map [string ]rule {
112- RuleDbPrepare : {NeedsPrepare : true },
111+ constants . QueryRuleDbPrepare : {NeedsPrepare : true },
113112 }
114113
115114 for _ , c := range conf .Rules {
@@ -538,11 +537,23 @@ func (c *checker) checkSQL(ctx context.Context, s config.SQL) error {
538537 req := codeGenRequest (result , combo )
539538 cfg := vetConfig (req )
540539 for i , query := range req .Queries {
541- if result .Queries [i ].Metadata .Flags [QueryFlagSqlcVetDisable ] {
542- if debug .Active {
543- log .Printf ("Skipping vet rules for query: %s\n " , query .Name )
540+ md := result .Queries [i ].Metadata
541+ if md .Flags [constants .QueryFlagSqlcVetDisable ] {
542+ // If the vet disable flag is specified without any rules listed, all rules are ignored.
543+ if len (md .RuleSkiplist ) == 0 {
544+ if debug .Active {
545+ log .Printf ("Skipping all vet rules for query: %s\n " , query .Name )
546+ }
547+ continue
548+ }
549+
550+ // Rules which are listed to be disabled but not declared in the config file are rejected.
551+ for r := range md .RuleSkiplist {
552+ if ! slices .Contains (s .Rules , r ) {
553+ fmt .Fprintf (c .Stderr , "%s: %s: rule-check error: rule %q does not exist in the config file\n " , query .Filename , query .Name , r )
554+ errored = true
555+ }
544556 }
545- continue
546557 }
547558
548559 evalMap := map [string ]any {
@@ -551,74 +562,81 @@ func (c *checker) checkSQL(ctx context.Context, s config.SQL) error {
551562 }
552563
553564 for _ , name := range s .Rules {
554- rule , ok := c .Rules [name ]
555- if ! ok {
556- return fmt .Errorf ("type-check error: a rule with the name '%s' does not exist" , name )
557- }
558-
559- if rule .NeedsPrepare {
560- if prep == nil {
561- fmt .Fprintf (c .Stderr , "%s: %s: %s: error preparing query: database connection required\n " , query .Filename , query .Name , name )
562- errored = true
563- continue
565+ if _ , skip := md .RuleSkiplist [name ]; skip {
566+ if debug .Active {
567+ log .Printf ("Skipping vet rule %q for query: %s\n " , name , query .Name )
564568 }
565- prepName := fmt .Sprintf ("sqlc_vet_%d_%d" , time .Now ().Unix (), i )
566- if err := prep .Prepare (ctx , prepName , query .Text ); err != nil {
567- fmt .Fprintf (c .Stderr , "%s: %s: %s: error preparing query: %s\n " , query .Filename , query .Name , name , err )
568- errored = true
569- continue
569+ } else {
570+ rule , ok := c .Rules [name ]
571+ if ! ok {
572+ return fmt .Errorf ("type-check error: a rule with the name '%s' does not exist" , name )
570573 }
571- }
572-
573- // short-circuit for "sqlc/db-prepare" rule which doesn't have a CEL program
574- if rule .Program == nil {
575- continue
576- }
577574
578- // Get explain output for this query if we need it
579- _ , pgsqlOK := evalMap ["postgresql" ]
580- _ , mysqlOK := evalMap ["mysql" ]
581- if rule .NeedsExplain && ! (pgsqlOK || mysqlOK ) {
582- if expl == nil {
583- fmt .Fprintf (c .Stderr , "%s: %s: %s: error explaining query: database connection required\n " , query .Filename , query .Name , name )
584- errored = true
585- continue
575+ if rule .NeedsPrepare {
576+ if prep == nil {
577+ fmt .Fprintf (c .Stderr , "%s: %s: %s: error preparing query: database connection required\n " , query .Filename , query .Name , name )
578+ errored = true
579+ continue
580+ }
581+ prepName := fmt .Sprintf ("sqlc_vet_%d_%d" , time .Now ().Unix (), i )
582+ if err := prep .Prepare (ctx , prepName , query .Text ); err != nil {
583+ fmt .Fprintf (c .Stderr , "%s: %s: %s: error preparing query: %s\n " , query .Filename , query .Name , name , err )
584+ errored = true
585+ continue
586+ }
586587 }
587- engineOutput , err := expl .Explain (ctx , query .Text , query .Params ... )
588- if err != nil {
589- fmt .Fprintf (c .Stderr , "%s: %s: %s: error explaining query: %s\n " , query .Filename , query .Name , name , err )
590- errored = true
588+
589+ // short-circuit for "sqlc/db-prepare" rule which doesn't have a CEL program
590+ if rule .Program == nil {
591591 continue
592592 }
593593
594- evalMap ["postgresql" ] = engineOutput .PostgreSQL
595- evalMap ["mysql" ] = engineOutput .MySQL
596- }
594+ // Get explain output for this query if we need it
595+ _ , pgsqlOK := evalMap ["postgresql" ]
596+ _ , mysqlOK := evalMap ["mysql" ]
597+ if rule .NeedsExplain && ! (pgsqlOK || mysqlOK ) {
598+ if expl == nil {
599+ fmt .Fprintf (c .Stderr , "%s: %s: %s: error explaining query: database connection required\n " , query .Filename , query .Name , name )
600+ errored = true
601+ continue
602+ }
603+ engineOutput , err := expl .Explain (ctx , query .Text , query .Params ... )
604+ if err != nil {
605+ fmt .Fprintf (c .Stderr , "%s: %s: %s: error explaining query: %s\n " , query .Filename , query .Name , name , err )
606+ errored = true
607+ continue
608+ }
609+
610+ evalMap ["postgresql" ] = engineOutput .PostgreSQL
611+ evalMap ["mysql" ] = engineOutput .MySQL
612+ }
597613
598- if debug .Debug .DumpVetEnv {
599- fmt .Printf ("vars for rule '%s' evaluating against query '%s':\n " , name , query .Name )
600- debug .DumpAsJSON (evalMap )
601- }
614+ if debug .Debug .DumpVetEnv {
615+ fmt .Printf ("vars for rule '%s' evaluating against query '%s':\n " , name , query .Name )
616+ debug .DumpAsJSON (evalMap )
617+ }
602618
603- out , _ , err := (* rule .Program ).Eval (evalMap )
604- if err != nil {
605- return err
606- }
607- tripped , ok := out .Value ().(bool )
608- if ! ok {
609- return fmt .Errorf ("expression returned non-bool value: %v" , out .Value ())
610- }
611- if tripped {
612- // TODO: Get line numbers in the output
613- if rule .Message == "" {
614- fmt .Fprintf (c .Stderr , "%s: %s: %s\n " , query .Filename , query .Name , name )
615- } else {
616- fmt .Fprintf (c .Stderr , "%s: %s: %s: %s\n " , query .Filename , query .Name , name , rule .Message )
619+ out , _ , err := (* rule .Program ).Eval (evalMap )
620+ if err != nil {
621+ return err
622+ }
623+ tripped , ok := out .Value ().(bool )
624+ if ! ok {
625+ return fmt .Errorf ("expression returned non-bool value: %v" , out .Value ())
626+ }
627+ if tripped {
628+ // TODO: Get line numbers in the output
629+ if rule .Message == "" {
630+ fmt .Fprintf (c .Stderr , "%s: %s: %s\n " , query .Filename , query .Name , name )
631+ } else {
632+ fmt .Fprintf (c .Stderr , "%s: %s: %s: %s\n " , query .Filename , query .Name , name , rule .Message )
633+ }
634+ errored = true
617635 }
618- errored = true
619636 }
620637 }
621638 }
639+
622640 if errored {
623641 return ErrFailedChecks
624642 }
0 commit comments