@@ -6,11 +6,13 @@ import (
6
6
"encoding/json"
7
7
"errors"
8
8
"fmt"
9
+ "github.com/sqlc-dev/sqlc/internal/constants"
9
10
"io"
10
11
"log"
11
12
"os"
12
13
"path/filepath"
13
14
"runtime/trace"
15
+ "slices"
14
16
"strings"
15
17
"time"
16
18
@@ -37,9 +39,6 @@ var ErrFailedChecks = errors.New("failed checks")
37
39
38
40
var pjson = protojson.UnmarshalOptions {AllowPartial : true , DiscardUnknown : true }
39
41
40
- const RuleDbPrepare = "sqlc/db-prepare"
41
- const QueryFlagSqlcVetDisable = "@sqlc-vet-disable"
42
-
43
42
func NewCmdVet () * cobra.Command {
44
43
return & cobra.Command {
45
44
Use : "vet" ,
@@ -109,7 +108,7 @@ func Vet(ctx context.Context, dir, filename string, opts *Options) error {
109
108
}
110
109
111
110
rules := map [string ]rule {
112
- RuleDbPrepare : {NeedsPrepare : true },
111
+ constants . QueryRuleDbPrepare : {NeedsPrepare : true },
113
112
}
114
113
115
114
for _ , c := range conf .Rules {
@@ -538,11 +537,23 @@ func (c *checker) checkSQL(ctx context.Context, s config.SQL) error {
538
537
req := codeGenRequest (result , combo )
539
538
cfg := vetConfig (req )
540
539
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
+ }
544
556
}
545
- continue
546
557
}
547
558
548
559
evalMap := map [string ]any {
@@ -551,74 +562,81 @@ func (c *checker) checkSQL(ctx context.Context, s config.SQL) error {
551
562
}
552
563
553
564
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 )
564
568
}
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 )
570
573
}
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
- }
577
574
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
+ }
586
587
}
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 {
591
591
continue
592
592
}
593
593
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
+ }
597
613
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
+ }
602
618
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
617
635
}
618
- errored = true
619
636
}
620
637
}
621
638
}
639
+
622
640
if errored {
623
641
return ErrFailedChecks
624
642
}
0 commit comments