github.com/samiam2013/sqlvet@v0.0.0-20221210043606-d72f678fc0aa/main.go (about) 1 package main 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "sync/atomic" 8 9 log "github.com/sirupsen/logrus" 10 "github.com/spf13/cobra" 11 12 "github.com/samiam2013/sqlvet/pkg/cli" 13 "github.com/samiam2013/sqlvet/pkg/config" 14 "github.com/samiam2013/sqlvet/pkg/schema" 15 "github.com/samiam2013/sqlvet/pkg/vet" 16 ) 17 18 const version = "1.1.1" 19 20 var ( 21 gitCommit = "?" 22 flagErrFormat = false 23 ) 24 25 // SQLVet includes Everything needed for check actions 26 type SQLVet struct { 27 QueryCnt int32 28 ErrCnt int32 29 30 Cfg config.Config 31 ProjectRoot string 32 Schema *schema.Db 33 } 34 35 func (s *SQLVet) reportError(format string, a ...interface{}) { 36 cli.Error(format, a...) 37 atomic.AddInt32(&s.ErrCnt, 1) 38 } 39 40 // Vet performs static analysis 41 func (s *SQLVet) Vet() { 42 queries, err := vet.CheckDir( 43 vet.VetContext{ 44 Schema: s.Schema, 45 }, 46 s.ProjectRoot, 47 s.Cfg.BuildFlags, 48 s.Cfg.SqlFuncMatchers, 49 ) 50 if err != nil { 51 cli.Exit(err) 52 } 53 54 for _, q := range queries { 55 atomic.AddInt32(&s.QueryCnt, 1) 56 57 if q.Err == nil { 58 if cli.Verbose { 59 cli.Show("query detected at %s", q.Position) 60 } 61 continue 62 } 63 64 // an error in the query is detected 65 if flagErrFormat { 66 relFilePath, err := filepath.Rel(s.ProjectRoot, q.Position.Filename) 67 if err != nil { 68 relFilePath = s.ProjectRoot 69 } 70 // format ref: https://github.com/reviewdog/reviewdog#errorformat 71 cli.Show( 72 "%s:%d:%d: %v", 73 relFilePath, q.Position.Line, q.Position.Column, q.Err) 74 } else { 75 cli.Bold("%s @ %s", q.Called, q.Position) 76 if q.Query != "" { 77 cli.Show("\t%s\n", q.Query) 78 } 79 80 s.reportError("\tERROR: %v", q.Err) 81 switch q.Err { 82 case vet.ErrQueryArgUnsafe: 83 cli.Show("\tHINT: if this is a false positive, annotate with `// sqlvet: ignore` comment") 84 } 85 cli.Show("") 86 } 87 } 88 } 89 90 // PrintSummary dumps analysis stats into stdout 91 func (s *SQLVet) PrintSummary() { 92 cli.Show("Checked %d SQL queries.", s.QueryCnt) 93 if s.ErrCnt == 0 { 94 cli.Success("🎉 Everything is awesome!") 95 } else { 96 cli.Error("Identified %d errors.", s.ErrCnt) 97 } 98 } 99 100 // NewSQLVet creates SQLVet for a given project dir 101 func NewSQLVet(projectRoot string) (*SQLVet, error) { 102 cfg, err := config.Load(projectRoot) 103 if err != nil { 104 return nil, err 105 } 106 107 var dbSchema *schema.Db 108 if cfg.SchemaPath != "" { 109 dbSchema, err = schema.NewDbSchema(filepath.Join(projectRoot, cfg.SchemaPath)) 110 if err != nil { 111 return nil, err 112 } 113 if !flagErrFormat { 114 cli.Show("Loaded DB schema from %s", cfg.SchemaPath) 115 for k, v := range dbSchema.Tables { 116 cli.Show("\ttable %s with %d columns", k, len(v.Columns)) 117 } 118 } 119 } else { 120 if !flagErrFormat { 121 cli.Show("[!] No schema specified, will run without table and column validation.") 122 } 123 } 124 125 return &SQLVet{ 126 Cfg: cfg, 127 ProjectRoot: projectRoot, 128 Schema: dbSchema, 129 }, nil 130 } 131 132 func main() { 133 var rootCmd = &cobra.Command{ 134 Use: "sqlvet PATH", 135 Short: "Go fearless SQL", 136 Args: cobra.ExactArgs(1), 137 Version: fmt.Sprintf("%s (%s)", version, gitCommit), 138 PreRun: func(cmd *cobra.Command, args []string) { 139 if cli.Verbose { 140 log.SetLevel(log.DebugLevel) 141 } 142 }, 143 Run: func(cmd *cobra.Command, args []string) { 144 projectRoot := args[0] 145 s, err := NewSQLVet(projectRoot) 146 if err != nil { 147 cli.Exit(err) 148 } 149 s.Vet() 150 151 if !flagErrFormat { 152 s.PrintSummary() 153 } 154 155 if s.ErrCnt > 0 { 156 os.Exit(1) 157 } 158 159 }, 160 } 161 162 rootCmd.PersistentFlags().BoolVarP( 163 &cli.Verbose, "verbose", "v", false, "verbose output") 164 rootCmd.PersistentFlags().BoolVarP( 165 &flagErrFormat, "errorformat", "e", false, 166 "output error in errorformat fromat for easier integration") 167 168 if err := rootCmd.Execute(); err != nil { 169 fmt.Println(err) 170 os.Exit(1) 171 } 172 }