package main import ( "go/ast" "go/parser" "go/token" "os" "path/filepath" "regexp" "strings" ) type rawSQLViolation struct { file string line int snippet string } // SQL keywords that indicate raw SQL usage var sqlKeywordRe = regexp.MustCompile(`(?i)\b(INSERT\s+INTO|UPDATE\s+\w+\s+SET|DELETE\s+FROM|SELECT\s+.+\s+FROM|ALTER\s+TABLE|DROP\s+TABLE|CREATE\s+TABLE)\b`) // Directories where raw SQL is expected (migrations, sqlc queries, seeder, test fixtures) func isRawSQLAllowed(path string) bool { return strings.Contains(path, "/sql/") || strings.Contains(path, "/migrations/") || strings.Contains(path, "/internal/db/") || strings.Contains(path, "/seeder/") || strings.HasSuffix(path, "_test.go") || strings.Contains(path, "/cmd/admincli/") || strings.Contains(path, "/datasources/") || // Bob query builder files strings.Contains(path, "internal/plugins/") // Plugin tables are not in core schema } // Specific SQL patterns that are allowed (postgres admin commands, not data queries) var allowedSQLPatterns = []string{ "pg_terminate_backend", // backup_service.go — kills connections before restore "FROM pg_database", // Administrative catalog lookup for tenant database existence "FROM pg_roles", // role_provisioner.go — per-plugin role existence lookup (DCL, not sqlc-able) } func isSQLAllowed(sql string) bool { for _, pattern := range allowedSQLPatterns { if strings.Contains(sql, pattern) { return true } } return false } // checkRawSQL finds raw SQL string literals passed to Exec/Query methods outside allowed dirs. func checkRawSQL(root string) []rawSQLViolation { var violations []rawSQLViolation if err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { if err != nil || info.IsDir() || !strings.HasSuffix(path, ".go") { return nil } if strings.Contains(path, "/vendor/") { return nil } if isRawSQLAllowed(path) { return nil } fset := token.NewFileSet() f, err := parser.ParseFile(fset, path, nil, 0) if err != nil { return nil } relPath, _ := filepath.Rel(root, path) ast.Inspect(f, func(n ast.Node) bool { call, ok := n.(*ast.CallExpr) if !ok { return true } // Match method calls: *.ExecContext, *.QueryContext, *.QueryRowContext, *.Exec, *.Query, *.QueryRow sel, ok := call.Fun.(*ast.SelectorExpr) if !ok { return true } method := sel.Sel.Name isDBMethod := method == "ExecContext" || method == "QueryContext" || method == "QueryRowContext" || method == "Exec" || method == "Query" || method == "QueryRow" if !isDBMethod { return true } // Check if any string argument contains SQL keywords for _, arg := range call.Args { lit, ok := arg.(*ast.BasicLit) if !ok || lit.Kind != token.STRING { continue } val := strings.Trim(lit.Value, "`\"") if sqlKeywordRe.MatchString(val) && !isSQLAllowed(val) { pos := fset.Position(call.Pos()) // Truncate the SQL for display display := val if len(display) > 80 { display = display[:77] + "..." } violations = append(violations, rawSQLViolation{ file: relPath, line: pos.Line, snippet: display, }) } } return true }) return nil }); err != nil { violations = append(violations, rawSQLViolation{file: root, line: 0, snippet: err.Error()}) } return violations }