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  }