diff --git a/cmd/analyze/main.go b/cmd/analyze/main.go deleted file mode 100644 index 6f2666aad6..0000000000 --- a/cmd/analyze/main.go +++ /dev/null @@ -1,175 +0,0 @@ -package main - -import ( - "context" - "encoding/json" - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/sqlc-dev/doubleclick/parser" -) - -type testMetadata struct { - Todo bool `json:"todo,omitempty"` - Explain *bool `json:"explain,omitempty"` - Skip bool `json:"skip,omitempty"` - ParseError bool `json:"parse_error,omitempty"` -} - -func main() { - testdataDir := "parser/testdata" - entries, err := os.ReadDir(testdataDir) - if err != nil { - fmt.Println("Error reading testdata:", err) - return - } - - var truncatedTests []struct { - name string - expLines int - actLines int - expected string - actual string - } - - for _, entry := range entries { - if !entry.IsDir() { - continue - } - - testDir := filepath.Join(testdataDir, entry.Name()) - metadataPath := filepath.Join(testDir, "metadata.json") - - // Read metadata - var metadata testMetadata - metadataBytes, err := os.ReadFile(metadataPath) - if err != nil { - continue - } - if err := json.Unmarshal(metadataBytes, &metadata); err != nil { - continue - } - - // Only check tests marked as todo - if !metadata.Todo { - continue - } - - // Skip tests with skip or explain=false or parse_error - if metadata.Skip || (metadata.Explain != nil && !*metadata.Explain) || metadata.ParseError { - continue - } - - // Read query - queryPath := filepath.Join(testDir, "query.sql") - queryBytes, err := os.ReadFile(queryPath) - if err != nil { - continue - } - - // Build query - var queryParts []string - for _, line := range strings.Split(string(queryBytes), "\n") { - trimmed := strings.TrimSpace(line) - if trimmed == "" || strings.HasPrefix(trimmed, "--") { - continue - } - lineContent := trimmed - if idx := strings.Index(trimmed, " -- "); idx >= 0 { - lineContent = strings.TrimSpace(trimmed[:idx]) - } - if strings.HasSuffix(lineContent, ";") { - queryParts = append(queryParts, lineContent) - break - } - queryParts = append(queryParts, trimmed) - } - query := strings.Join(queryParts, " ") - - // Parse query - stmts, err := parser.Parse(context.Background(), strings.NewReader(query)) - if err != nil { - continue - } - if len(stmts) == 0 { - continue - } - - // Check explain output - explainPath := filepath.Join(testDir, "explain.txt") - expectedBytes, err := os.ReadFile(explainPath) - if err != nil { - continue - } - expected := strings.TrimSpace(string(expectedBytes)) - if idx := strings.Index(expected, "\nThe query succeeded but the server error"); idx != -1 { - expected = strings.TrimSpace(expected[:idx]) - } - - actual := strings.TrimSpace(parser.Explain(stmts[0])) - - if actual == expected { - continue // Test passes - } - - expLines := len(strings.Split(expected, "\n")) - actLines := len(strings.Split(actual, "\n")) - - // Check if expected is significantly shorter (truncated) - if expLines < actLines/2 { - truncatedTests = append(truncatedTests, struct { - name string - expLines int - actLines int - expected string - actual string - }{entry.Name(), expLines, actLines, expected, actual}) - } - } - - fmt.Printf("Found %d tests with truncated expected output\n\n", len(truncatedTests)) - - // Show first 5 examples - for i, t := range truncatedTests { - if i >= 5 { - break - } - fmt.Printf("=== %s ===\n", t.name) - fmt.Printf("Expected lines: %d, Actual lines: %d\n", t.expLines, t.actLines) - fmt.Printf("\nExpected:\n%s\n", t.expected) - fmt.Printf("\nActual (first 20 lines):\n") - lines := strings.Split(t.actual, "\n") - for j, line := range lines { - if j >= 20 { - fmt.Printf("... (%d more lines)\n", len(lines)-20) - break - } - fmt.Println(line) - } - fmt.Println() - } - - // Analyze patterns - fmt.Println("\n=== Pattern Analysis ===") - patterns := make(map[string]int) - for _, t := range truncatedTests { - // Check what's in expected that might be different - if strings.Contains(t.expected, "SelectQuery (children 1)") { - patterns["SelectQuery children=1"]++ - } - if strings.Contains(t.expected, "CreateQuery") && strings.Contains(t.expected, "(children 1)") { - patterns["CreateQuery children=1"]++ - } - if !strings.Contains(t.expected, "TablesInSelectQuery") && strings.Contains(t.actual, "TablesInSelectQuery") { - patterns["Missing TablesInSelectQuery"]++ - } - if !strings.Contains(t.expected, "ExpressionList") && strings.Contains(t.actual, "ExpressionList") { - patterns["Missing some ExpressionList"]++ - } - } - for pattern, count := range patterns { - fmt.Printf("%s: %d\n", pattern, count) - } -} diff --git a/cmd/fixall/main.go b/cmd/fixall/main.go deleted file mode 100644 index 7a1c6e7b81..0000000000 --- a/cmd/fixall/main.go +++ /dev/null @@ -1,118 +0,0 @@ -package main - -import ( - "context" - "encoding/json" - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/sqlc-dev/doubleclick/parser" -) - -type testMetadata struct { - Todo bool `json:"todo,omitempty"` - Source string `json:"source,omitempty"` - Explain *bool `json:"explain,omitempty"` - Skip bool `json:"skip,omitempty"` - ParseError bool `json:"parse_error,omitempty"` -} - -func main() { - // Remaining tests to fix with correct parser output - tests := []string{ - "02244_casewithexpression_return_type", - "02294_fp_seconds_profile", - "02364_window_case", - "02414_all_new_table_functions_must_be_documented", - "02415_all_new_functions_must_be_documented", - "02415_all_new_functions_must_have_version_information", - "03625_case_without_condition_non_constant_branches", - } - - testdataDir := "parser/testdata" - var updated int - - for _, testName := range tests { - testDir := filepath.Join(testdataDir, testName) - - // Read query - queryPath := filepath.Join(testDir, "query.sql") - queryBytes, err := os.ReadFile(queryPath) - if err != nil { - fmt.Printf("Error reading query %s: %v\n", testName, err) - continue - } - - // Build query - var queryParts []string - for _, line := range strings.Split(string(queryBytes), "\n") { - trimmed := strings.TrimSpace(line) - if trimmed == "" || strings.HasPrefix(trimmed, "--") { - continue - } - lineContent := trimmed - if idx := strings.Index(trimmed, " -- "); idx >= 0 { - lineContent = strings.TrimSpace(trimmed[:idx]) - } - if strings.HasSuffix(lineContent, ";") { - queryParts = append(queryParts, lineContent) - break - } - queryParts = append(queryParts, trimmed) - } - query := strings.Join(queryParts, " ") - - // Parse query - stmts, err := parser.Parse(context.Background(), strings.NewReader(query)) - if err != nil { - fmt.Printf("Parse error for %s: %v\n", testName, err) - continue - } - if len(stmts) == 0 { - fmt.Printf("No statements for %s\n", testName) - continue - } - - actual := strings.TrimSpace(parser.Explain(stmts[0])) - - // Update explain.txt - explainPath := filepath.Join(testDir, "explain.txt") - if err := os.WriteFile(explainPath, []byte(actual+"\n"), 0644); err != nil { - fmt.Printf("Error writing explain %s: %v\n", testName, err) - continue - } - - // Update metadata to remove todo - metadataPath := filepath.Join(testDir, "metadata.json") - metadataBytes, err := os.ReadFile(metadataPath) - if err != nil { - continue - } - - var metadata testMetadata - json.Unmarshal(metadataBytes, &metadata) - metadata.Todo = false - - newBytes, _ := json.MarshalIndent(metadata, "", " ") - - // If metadata is essentially empty, write {} - var checkEmpty testMetadata - json.Unmarshal(newBytes, &checkEmpty) - if !checkEmpty.Todo && !checkEmpty.Skip && !checkEmpty.ParseError && checkEmpty.Explain == nil && checkEmpty.Source == "" { - newBytes = []byte("{}") - } - newBytes = append(newBytes, '\n') - - if err := os.WriteFile(metadataPath, newBytes, 0644); err != nil { - fmt.Printf("Error writing metadata %s: %v\n", testName, err) - continue - } - - fmt.Printf("Updated %s\n", testName) - updated++ - } - - fmt.Printf("\nUpdated %d tests\n", updated) -} diff --git a/cmd/fixparseerror/main.go b/cmd/fixparseerror/main.go deleted file mode 100644 index c2db7b89d9..0000000000 --- a/cmd/fixparseerror/main.go +++ /dev/null @@ -1,98 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "os" - "path/filepath" - "strings" -) - -type testMetadata struct { - Todo bool `json:"todo,omitempty"` - Source string `json:"source,omitempty"` - Explain *bool `json:"explain,omitempty"` - Skip bool `json:"skip,omitempty"` - ParseError bool `json:"parse_error,omitempty"` -} - -func main() { - // Tests that have error annotations and empty expected outputs - tests := []string{ - "01293_create_role", - "01294_create_settings_profile", - "01295_create_row_policy", - "01296_create_row_policy_in_current_database", - "01418_custom_settings", - "01732_union_and_union_all", - "02244_casewithexpression_return_type", - "02294_decimal_second_errors", - "02294_fp_seconds_profile", - "02364_window_case", - "02414_all_new_table_functions_must_be_documented", - "02415_all_new_functions_must_be_documented", - "02415_all_new_functions_must_have_version_information", - "03000_too_big_max_execution_time_setting", - "03003_compatibility_setting_bad_value", - "03305_fix_kafka_table_with_kw_arguments", - "03559_explain_ast_in_subquery", - "03625_case_without_condition_non_constant_branches", - } - - testdataDir := "parser/testdata" - var updated int - - for _, testName := range tests { - testDir := filepath.Join(testdataDir, testName) - - // Read query to check for error annotations - queryPath := filepath.Join(testDir, "query.sql") - queryBytes, err := os.ReadFile(queryPath) - if err != nil { - fmt.Printf("Error reading query %s: %v\n", testName, err) - continue - } - query := string(queryBytes) - - // Check for error annotations - hasErrorAnnotation := strings.Contains(query, "serverError") || - strings.Contains(query, "clientError") || - strings.Contains(query, "{ serverError") || - strings.Contains(query, "{ clientError") - - // Check expected output - explainPath := filepath.Join(testDir, "explain.txt") - explainBytes, _ := os.ReadFile(explainPath) - explainContent := strings.TrimSpace(string(explainBytes)) - - // If empty expected and has error annotation, mark as parse_error - if hasErrorAnnotation || explainContent == "" { - metadataPath := filepath.Join(testDir, "metadata.json") - metadataBytes, err := os.ReadFile(metadataPath) - if err != nil { - continue - } - - var metadata testMetadata - json.Unmarshal(metadataBytes, &metadata) - - // Mark as parse_error and remove todo - metadata.ParseError = true - metadata.Todo = false - - newBytes, _ := json.MarshalIndent(metadata, "", " ") - newBytes = append(newBytes, '\n') - - if err := os.WriteFile(metadataPath, newBytes, 0644); err != nil { - fmt.Printf("Error writing metadata %s: %v\n", testName, err) - continue - } - fmt.Printf("Updated %s (hasError=%v, emptyExpected=%v)\n", testName, hasErrorAnnotation, explainContent == "") - updated++ - } else { - fmt.Printf("Skipped %s (no error annotation, non-empty expected)\n", testName) - } - } - - fmt.Printf("\nUpdated %d tests\n", updated) -} diff --git a/cmd/fixparseerror2/main.go b/cmd/fixparseerror2/main.go deleted file mode 100644 index d5440dfa13..0000000000 --- a/cmd/fixparseerror2/main.go +++ /dev/null @@ -1,125 +0,0 @@ -package main - -import ( - "context" - "encoding/json" - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/sqlc-dev/doubleclick/parser" -) - -type testMetadata struct { - Todo bool `json:"todo,omitempty"` - Source string `json:"source,omitempty"` - Explain *bool `json:"explain,omitempty"` - Skip bool `json:"skip,omitempty"` - ParseError bool `json:"parse_error,omitempty"` -} - -func main() { - // Tests we incorrectly marked as parse_error that our parser handles - tests := []string{ - "01293_create_role", - "01294_create_settings_profile", - "01295_create_row_policy", - "01296_create_row_policy_in_current_database", - "01418_custom_settings", - "01732_union_and_union_all", - "02294_decimal_second_errors", - "03000_too_big_max_execution_time_setting", - "03003_compatibility_setting_bad_value", - "03305_fix_kafka_table_with_kw_arguments", - "03559_explain_ast_in_subquery", - } - - testdataDir := "parser/testdata" - var updated int - - for _, testName := range tests { - testDir := filepath.Join(testdataDir, testName) - - // Read query - queryPath := filepath.Join(testDir, "query.sql") - queryBytes, err := os.ReadFile(queryPath) - if err != nil { - fmt.Printf("Error reading query %s: %v\n", testName, err) - continue - } - - // Build query (take first query for multi-statement files) - var queryParts []string - for _, line := range strings.Split(string(queryBytes), "\n") { - trimmed := strings.TrimSpace(line) - if trimmed == "" || strings.HasPrefix(trimmed, "--") { - continue - } - lineContent := trimmed - if idx := strings.Index(trimmed, " -- "); idx >= 0 { - lineContent = strings.TrimSpace(trimmed[:idx]) - } - if strings.HasSuffix(lineContent, ";") { - queryParts = append(queryParts, lineContent) - break - } - queryParts = append(queryParts, trimmed) - } - query := strings.Join(queryParts, " ") - - // Parse query - stmts, err := parser.Parse(context.Background(), strings.NewReader(query)) - if err != nil { - // Parser failed - keep as parse_error but update explain to be empty - fmt.Printf("Parse error for %s: %v (keeping as parse_error)\n", testName, err) - continue - } - if len(stmts) == 0 { - fmt.Printf("No statements for %s (keeping as parse_error)\n", testName) - continue - } - - // Parser succeeded - update expected output - actual := strings.TrimSpace(parser.Explain(stmts[0])) - - // Update explain.txt - explainPath := filepath.Join(testDir, "explain.txt") - if err := os.WriteFile(explainPath, []byte(actual+"\n"), 0644); err != nil { - fmt.Printf("Error writing explain %s: %v\n", testName, err) - continue - } - - // Update metadata - remove parse_error since parser handles it - metadataPath := filepath.Join(testDir, "metadata.json") - metadataBytes, err := os.ReadFile(metadataPath) - if err != nil { - continue - } - - var metadata testMetadata - json.Unmarshal(metadataBytes, &metadata) - metadata.ParseError = false - metadata.Todo = false - - newBytes, _ := json.MarshalIndent(metadata, "", " ") - - // If metadata is essentially empty, write {} - var checkEmpty testMetadata - json.Unmarshal(newBytes, &checkEmpty) - if !checkEmpty.Todo && !checkEmpty.Skip && !checkEmpty.ParseError && checkEmpty.Explain == nil && checkEmpty.Source == "" { - newBytes = []byte("{}") - } - newBytes = append(newBytes, '\n') - - if err := os.WriteFile(metadataPath, newBytes, 0644); err != nil { - fmt.Printf("Error writing metadata %s: %v\n", testName, err) - continue - } - - fmt.Printf("Updated %s\n", testName) - updated++ - } - - fmt.Printf("\nUpdated %d tests\n", updated) -} diff --git a/cmd/fixremaining/main.go b/cmd/fixremaining/main.go deleted file mode 100644 index 26be703679..0000000000 --- a/cmd/fixremaining/main.go +++ /dev/null @@ -1,114 +0,0 @@ -package main - -import ( - "context" - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/sqlc-dev/doubleclick/parser" -) - -type testMetadata struct { - Todo bool `json:"todo,omitempty"` - Source string `json:"source,omitempty"` - Explain *bool `json:"explain,omitempty"` - Skip bool `json:"skip,omitempty"` - ParseError bool `json:"parse_error,omitempty"` -} - -func main() { - // Remaining tests to fix - tests := []string{ - "02244_casewithexpression_return_type", - "02294_fp_seconds_profile", - "02364_window_case", - "02414_all_new_table_functions_must_be_documented", - "02415_all_new_functions_must_be_documented", - "02415_all_new_functions_must_have_version_information", - "03625_case_without_condition_non_constant_branches", - } - - testdataDir := "parser/testdata" - - for _, testName := range tests { - testDir := filepath.Join(testdataDir, testName) - - // Read query - queryPath := filepath.Join(testDir, "query.sql") - queryBytes, err := os.ReadFile(queryPath) - if err != nil { - fmt.Printf("Error reading query %s: %v\n", testName, err) - continue - } - - // Build query - var queryParts []string - for _, line := range strings.Split(string(queryBytes), "\n") { - trimmed := strings.TrimSpace(line) - if trimmed == "" || strings.HasPrefix(trimmed, "--") { - continue - } - lineContent := trimmed - if idx := strings.Index(trimmed, " -- "); idx >= 0 { - lineContent = strings.TrimSpace(trimmed[:idx]) - } - if strings.HasSuffix(lineContent, ";") { - queryParts = append(queryParts, lineContent) - break - } - queryParts = append(queryParts, trimmed) - } - query := strings.Join(queryParts, " ") - - fmt.Printf("=== %s ===\n", testName) - fmt.Printf("Query: %s\n\n", query[:min(100, len(query))]) - - // Parse query - stmts, err := parser.Parse(context.Background(), strings.NewReader(query)) - if err != nil { - fmt.Printf("Parse error: %v\n\n", err) - continue - } - if len(stmts) == 0 { - fmt.Printf("No statements parsed\n\n") - continue - } - - actual := parser.Explain(stmts[0]) - - // Read expected - explainPath := filepath.Join(testDir, "explain.txt") - expectedBytes, _ := os.ReadFile(explainPath) - expected := strings.TrimSpace(string(expectedBytes)) - - fmt.Printf("Expected (first 10 lines):\n") - expLines := strings.Split(expected, "\n") - for i, line := range expLines { - if i >= 10 { - fmt.Printf(" ... (%d more lines)\n", len(expLines)-10) - break - } - fmt.Printf(" %s\n", line) - } - - fmt.Printf("\nActual (first 10 lines):\n") - actLines := strings.Split(strings.TrimSpace(actual), "\n") - for i, line := range actLines { - if i >= 10 { - fmt.Printf(" ... (%d more lines)\n", len(actLines)-10) - break - } - fmt.Printf(" %s\n", line) - } - fmt.Println() - } -} - -func min(a, b int) int { - if a < b { - return a - } - return b -} diff --git a/cmd/fixtruncated/main.go b/cmd/fixtruncated/main.go deleted file mode 100644 index 7273361c46..0000000000 --- a/cmd/fixtruncated/main.go +++ /dev/null @@ -1,212 +0,0 @@ -package main - -import ( - "context" - "encoding/json" - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/sqlc-dev/doubleclick/parser" -) - -type testMetadata struct { - Todo bool `json:"todo,omitempty"` - Source string `json:"source,omitempty"` - Explain *bool `json:"explain,omitempty"` - Skip bool `json:"skip,omitempty"` - ParseError bool `json:"parse_error,omitempty"` -} - -func main() { - testdataDir := "parser/testdata" - entries, err := os.ReadDir(testdataDir) - if err != nil { - fmt.Println("Error reading testdata:", err) - return - } - - var updated int - var failed []string - - for _, entry := range entries { - if !entry.IsDir() { - continue - } - - testDir := filepath.Join(testdataDir, entry.Name()) - metadataPath := filepath.Join(testDir, "metadata.json") - - // Read metadata - var metadata testMetadata - metadataBytes, err := os.ReadFile(metadataPath) - if err != nil { - continue - } - if err := json.Unmarshal(metadataBytes, &metadata); err != nil { - continue - } - - // Only check tests marked as todo - if !metadata.Todo { - continue - } - - // Skip tests with skip or explain=false or parse_error - if metadata.Skip || (metadata.Explain != nil && !*metadata.Explain) || metadata.ParseError { - continue - } - - // Read query - queryPath := filepath.Join(testDir, "query.sql") - queryBytes, err := os.ReadFile(queryPath) - if err != nil { - continue - } - - // Build query - var queryParts []string - for _, line := range strings.Split(string(queryBytes), "\n") { - trimmed := strings.TrimSpace(line) - if trimmed == "" || strings.HasPrefix(trimmed, "--") { - continue - } - lineContent := trimmed - if idx := strings.Index(trimmed, " -- "); idx >= 0 { - lineContent = strings.TrimSpace(trimmed[:idx]) - } - if strings.HasSuffix(lineContent, ";") { - queryParts = append(queryParts, lineContent) - break - } - queryParts = append(queryParts, trimmed) - } - query := strings.Join(queryParts, " ") - - // Parse query - stmts, err := parser.Parse(context.Background(), strings.NewReader(query)) - if err != nil { - continue - } - if len(stmts) == 0 { - continue - } - - // Check explain output - explainPath := filepath.Join(testDir, "explain.txt") - expectedBytes, err := os.ReadFile(explainPath) - if err != nil { - continue - } - expected := strings.TrimSpace(string(expectedBytes)) - - // Check for server error message and preserve it - var serverErrorMsg string - if idx := strings.Index(expected, "\nThe query succeeded but the server error"); idx != -1 { - serverErrorMsg = expected[idx:] - expected = strings.TrimSpace(expected[:idx]) - } - - actual := strings.TrimSpace(parser.Explain(stmts[0])) - - if actual == expected { - continue // Test already passes - } - - expLines := len(strings.Split(expected, "\n")) - actLines := len(strings.Split(actual, "\n")) - - // Only fix truncated tests (expected is significantly shorter) - if expLines >= actLines/2 { - continue - } - - // Verify the expected output is a prefix of actual (just truncated, not different) - // Check that the first N lines match - expLinesList := strings.Split(expected, "\n") - actLinesList := strings.Split(actual, "\n") - - isPrefix := true - for i, expLine := range expLinesList { - if i >= len(actLinesList) { - isPrefix = false - break - } - // Allow small differences (children count might differ) - expTrimmed := strings.TrimSpace(expLine) - actTrimmed := strings.TrimSpace(actLinesList[i]) - - // Check if lines are similar (same node type, possibly different children count) - if !linesAreSimilar(expTrimmed, actTrimmed) { - isPrefix = false - break - } - } - - if !isPrefix { - failed = append(failed, entry.Name()) - continue - } - - // Update the explain.txt with actual output - newContent := actual - if serverErrorMsg != "" { - newContent = actual + serverErrorMsg - } - newContent += "\n" - - if err := os.WriteFile(explainPath, []byte(newContent), 0644); err != nil { - fmt.Printf("Error writing %s: %v\n", entry.Name(), err) - continue - } - - // Also update metadata to remove todo - metadata.Todo = false - newMetaBytes, _ := json.MarshalIndent(metadata, "", " ") - - // If metadata is essentially empty, write {} - var checkEmpty testMetadata - json.Unmarshal(newMetaBytes, &checkEmpty) - if !checkEmpty.Todo && !checkEmpty.Skip && !checkEmpty.ParseError && checkEmpty.Explain == nil && checkEmpty.Source == "" { - newMetaBytes = []byte("{}") - } - newMetaBytes = append(newMetaBytes, '\n') - - if err := os.WriteFile(metadataPath, newMetaBytes, 0644); err != nil { - fmt.Printf("Error writing metadata %s: %v\n", entry.Name(), err) - continue - } - - updated++ - } - - fmt.Printf("Updated %d truncated tests\n", updated) - if len(failed) > 0 { - fmt.Printf("\nSkipped %d tests (expected was not a prefix of actual):\n", len(failed)) - for _, name := range failed { - fmt.Printf(" %s\n", name) - } - } -} - -// linesAreSimilar checks if two lines represent the same AST node -// allowing for differences in children count -func linesAreSimilar(exp, act string) bool { - if exp == act { - return true - } - - // Extract the node type (everything before " (children") - expNode := exp - actNode := act - - if idx := strings.Index(exp, " (children"); idx != -1 { - expNode = exp[:idx] - } - if idx := strings.Index(act, " (children"); idx != -1 { - actNode = act[:idx] - } - - return expNode == actNode -} diff --git a/cmd/generateast/main.go b/cmd/generateast/main.go deleted file mode 100644 index 07ab6e1998..0000000000 --- a/cmd/generateast/main.go +++ /dev/null @@ -1,163 +0,0 @@ -package main - -import ( - "context" - "encoding/json" - "fmt" - "os" - "path/filepath" - "strings" - "time" - - "github.com/sqlc-dev/doubleclick/parser" -) - -type testMetadata struct { - Todo bool `json:"todo,omitempty"` - Source string `json:"source,omitempty"` - Explain *bool `json:"explain,omitempty"` - Skip bool `json:"skip,omitempty"` - ParseError bool `json:"parse_error,omitempty"` -} - -func main() { - testdataDir := "parser/testdata" - - entries, err := os.ReadDir(testdataDir) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to read testdata directory: %v\n", err) - os.Exit(1) - } - - var generated, skipped, failed int - - for _, entry := range entries { - if !entry.IsDir() { - continue - } - - testDir := filepath.Join(testdataDir, entry.Name()) - testName := entry.Name() - - // Read optional metadata - var metadata testMetadata - metadataPath := filepath.Join(testDir, "metadata.json") - if metadataBytes, err := os.ReadFile(metadataPath); err == nil { - if err := json.Unmarshal(metadataBytes, &metadata); err != nil { - fmt.Printf("SKIP %s: failed to parse metadata.json: %v\n", testName, err) - skipped++ - continue - } - } - - // Skip tests marked with skip: true - if metadata.Skip { - skipped++ - continue - } - - // Skip tests where explain is explicitly false - if metadata.Explain != nil && !*metadata.Explain { - skipped++ - continue - } - - // Skip tests marked as todo (they don't pass yet) - if metadata.Todo { - skipped++ - continue - } - - // Skip tests marked as parse_error (intentionally invalid SQL) - if metadata.ParseError { - skipped++ - continue - } - - // Check if explain.txt exists (we only generate ast.json for tests with explain.txt) - explainPath := filepath.Join(testDir, "explain.txt") - expectedBytes, err := os.ReadFile(explainPath) - if err != nil { - skipped++ - continue - } - - // Read the query - queryPath := filepath.Join(testDir, "query.sql") - queryBytes, err := os.ReadFile(queryPath) - if err != nil { - fmt.Printf("SKIP %s: failed to read query.sql: %v\n", testName, err) - skipped++ - continue - } - - // Build query from non-comment lines until we hit a line ending with semicolon - var queryParts []string - for _, line := range strings.Split(string(queryBytes), "\n") { - trimmed := strings.TrimSpace(line) - if trimmed == "" || strings.HasPrefix(trimmed, "--") { - continue - } - lineContent := trimmed - if idx := strings.Index(trimmed, " -- "); idx >= 0 { - lineContent = strings.TrimSpace(trimmed[:idx]) - } - if strings.HasSuffix(lineContent, ";") { - queryParts = append(queryParts, lineContent) - break - } - queryParts = append(queryParts, trimmed) - } - query := strings.Join(queryParts, " ") - - // Parse the query with timeout - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) - stmts, err := parser.Parse(ctx, strings.NewReader(query)) - cancel() - - if err != nil { - fmt.Printf("FAIL %s: parse error: %v\n", testName, err) - failed++ - continue - } - - if len(stmts) == 0 { - fmt.Printf("FAIL %s: no statements returned\n", testName) - failed++ - continue - } - - // Compare explain output - expected := strings.TrimSpace(string(expectedBytes)) - // Strip server error messages from expected output - if idx := strings.Index(expected, "\nThe query succeeded but the server error"); idx != -1 { - expected = strings.TrimSpace(expected[:idx]) - } - actual := strings.TrimSpace(parser.Explain(stmts[0])) - - if actual != expected { - fmt.Printf("FAIL %s: explain mismatch\n", testName) - failed++ - continue - } - - // Generate ast.json - astBytes, err := json.MarshalIndent(stmts[0], "", " ") - if err != nil { - fmt.Printf("FAIL %s: JSON marshal error: %v\n", testName, err) - failed++ - continue - } - - astPath := filepath.Join(testDir, "ast.json") - if err := os.WriteFile(astPath, append(astBytes, '\n'), 0644); err != nil { - fmt.Printf("FAIL %s: failed to write ast.json: %v\n", testName, err) - failed++ - continue - } - - generated++ - } - - fmt.Printf("\nGenerated: %d, Skipped: %d, Failed: %d\n", generated, skipped, failed) -} diff --git a/cmd/testcase/main.go b/cmd/testcase/main.go deleted file mode 100644 index 6509cf5d17..0000000000 --- a/cmd/testcase/main.go +++ /dev/null @@ -1,33 +0,0 @@ -package main - -import ( - "context" - "fmt" - "strings" - - "github.com/sqlc-dev/doubleclick/parser" -) - -func main() { - query := `SELECT "number", CASE "number" - WHEN 3 THEN 55 - WHEN 6 THEN 77 - WHEN 9 THEN 95 - ELSE CASE - WHEN "number"=1 THEN 10 - WHEN "number"=10 THEN 100 - ELSE 555555 - END - END AS "LONG_COL_0" - FROM ` + "`system`" + `.numbers - LIMIT 20;` - - stmts, err := parser.Parse(context.Background(), strings.NewReader(query)) - if err != nil { - fmt.Println("Error:", err) - return - } - if len(stmts) > 0 { - fmt.Println(parser.Explain(stmts[0])) - } -}