package main import ( "bufio" "encoding/csv" "flag" "fmt" "io" "log" "os" "github.com/xwb1989/sqlparser" ) var indexFile = flag.String("i", "", "index files to check queries against") func init() { flag.Usage = func() { fmt.Fprintf(os.Stderr, "Returns unindexed queries. Reads select sql statements from stdin and checks the provided index file which is the result of analyze.\n") fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) flag.PrintDefaults() } } type tableStats map[string][]string func main() { flag.Parse() if *indexFile == "" { flag.Usage() os.Exit(1) } stats := readTableIndexes(*indexFile) r := bufio.NewReader(os.Stdin) analyzeQueries(r, stats) } func readTableIndexes(path string) tableStats { f, err := os.Open(path) if err != nil { fmt.Fprintln(os.Stderr, "index list not found at", path) os.Exit(1) } br := bufio.NewReader(f) r := csv.NewReader(br) indexes := map[string][]string{} for { record, err := r.Read() if err == io.EOF { break } if err != nil { log.Fatal(err) } table := record[0] indexes[table] = append(indexes[table], record[1]) } return indexes } func analyzeQueries(r io.Reader, stats tableStats) { scanner := bufio.NewScanner(r) for scanner.Scan() { sql := scanner.Text() stmt, err := sqlparser.Parse(sql) if err != nil { fmt.Println("Unable to parse line", sql) fmt.Fprintln(os.Stderr, err) os.Exit(1) } switch q := stmt.(type) { case *sqlparser.Select: if !hasIndex(q, stats) { fmt.Println(sql) } default: fmt.Fprintln(os.Stderr, "Only Select queries are supported", sqlparser.String(stmt)) os.Exit(1) } } if err := scanner.Err(); err != nil { fmt.Fprintln(os.Stderr, "reading standard input:", err) } } func hasIndex(q *sqlparser.Select, stats tableStats) bool { // fixme: assume simple table from table := sqlparser.String(q.From[0]) // expr := if _, ok := stats[table]; !ok { fmt.Fprintln(os.Stderr, "Table does not appear to have an index", sqlparser.String(q)) return false } indexed := stats[table] var found bool sqlparser.Walk(func(node sqlparser.SQLNode) (bool, error) { switch node.(type) { case *sqlparser.ColName: // we're getting fully qualified column back c := sqlparser.String(node) for _, v := range indexed { // quick hack because I'm parsing columns wrong somehow if v == c || (table+"."+v) == c { found = true return false, nil } } } return true, nil }, q.Where) return found } func checkErr(err error) { if err != nil { panic(err) } }