diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..eb678d6df --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,62 @@ +# Claude Development Guide + +## Next Steps + +To find the next test to work on, run: + +```bash +go run ./cmd/next-test +``` + +This tool finds all tests with `todo: true` in their metadata and returns the one with the shortest `query.sql` file. + +## Workflow + +1. Run `go run ./cmd/next-test` to find the next test to implement +2. Check the test's `query.sql` to understand what ClickHouse SQL needs parsing +3. Check the test's `explain.txt` to understand the expected EXPLAIN output +4. Implement the necessary AST types in `ast/` +5. Add parser logic in `parser/parser.go` +6. Update the `Explain()` function if needed to match ClickHouse's output format +7. Enable the test by removing `todo: true` from its `metadata.json` (set it to `{}`) +8. Run `go test ./parser/... -timeout 5s` to verify +9. Check if other todo tests now pass (see below) + +## Running Tests + +Always run parser tests with a 5 second timeout: + +```bash +go test ./parser/... -timeout 5s +``` + +The tests are very fast. If a test is timing out, it indicates a bug (likely an infinite loop in the parser). + +## Checking for Newly Passing Todo Tests + +After implementing parser changes, run: + +```bash +go test ./parser/... -check-skipped -v 2>&1 | grep "PASSES NOW" +``` + +Tests that output `PASSES NOW` can have their `todo` flag removed from `metadata.json`. This helps identify when parser improvements fix multiple tests at once. + +## Test Structure + +Each test in `parser/testdata/` contains: + +- `metadata.json` - `{}` for enabled tests, `{"todo": true}` for pending tests +- `query.sql` - ClickHouse SQL to parse +- `explain.txt` - Expected EXPLAIN AST output (matches ClickHouse's format) + +### Metadata Options + +- `todo: true` - Test is pending implementation +- `skip: true` - Skip test entirely (e.g., causes infinite loop) +- `explain: false` - Skip test (e.g., ClickHouse couldn't parse it) +- `parse_error: true` - Query is intentionally invalid SQL + +## Important Rules + +**NEVER modify `explain.txt` files** - These are golden files containing the expected output from ClickHouse. If tests fail due to output mismatches, fix the Go code to match the expected output, not the other way around. diff --git a/cmd/next-test/main.go b/cmd/next-test/main.go new file mode 100644 index 000000000..e9c830276 --- /dev/null +++ b/cmd/next-test/main.go @@ -0,0 +1,103 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "sort" +) + +type testMetadata struct { + Todo bool `json:"todo,omitempty"` + Explain *bool `json:"explain,omitempty"` + Skip bool `json:"skip,omitempty"` + ParseError bool `json:"parse_error,omitempty"` +} + +type todoTest struct { + name string + querySize int +} + +func main() { + testdataDir := "parser/testdata" + entries, err := os.ReadDir(testdataDir) + if err != nil { + fmt.Fprintf(os.Stderr, "Error reading testdata: %v\n", err) + os.Exit(1) + } + + var todoTests []todoTest + + for _, entry := range entries { + if !entry.IsDir() { + continue + } + + testDir := filepath.Join(testdataDir, entry.Name()) + metadataPath := filepath.Join(testDir, "metadata.json") + + // Read metadata + metadataBytes, err := os.ReadFile(metadataPath) + if err != nil { + continue + } + + var metadata testMetadata + if err := json.Unmarshal(metadataBytes, &metadata); err != nil { + continue + } + + // Only include 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 to get its size + queryPath := filepath.Join(testDir, "query.sql") + queryBytes, err := os.ReadFile(queryPath) + if err != nil { + continue + } + + todoTests = append(todoTests, todoTest{ + name: entry.Name(), + querySize: len(queryBytes), + }) + } + + if len(todoTests) == 0 { + fmt.Println("No todo tests found!") + return + } + + // Sort by query size (shortest first) + sort.Slice(todoTests, func(i, j int) bool { + return todoTests[i].querySize < todoTests[j].querySize + }) + + // Print the shortest one + next := todoTests[0] + testDir := filepath.Join(testdataDir, next.name) + + fmt.Printf("Next test: %s\n\n", next.name) + + // Print query.sql contents + queryPath := filepath.Join(testDir, "query.sql") + queryBytes, _ := os.ReadFile(queryPath) + fmt.Printf("Query (%d bytes):\n%s\n", next.querySize, string(queryBytes)) + + // Print explain.txt contents if it exists + explainPath := filepath.Join(testDir, "explain.txt") + if explainBytes, err := os.ReadFile(explainPath); err == nil { + fmt.Printf("\nExpected EXPLAIN output:\n%s\n", string(explainBytes)) + } + + fmt.Printf("\nRemaining todo tests: %d\n", len(todoTests)) +} diff --git a/parser/CLAUDE.md b/parser/CLAUDE.md deleted file mode 100644 index 7ae455024..000000000 --- a/parser/CLAUDE.md +++ /dev/null @@ -1,21 +0,0 @@ -# Parser Development Notes - -## Running Tests - -Always run parser tests with a 5 second timeout: - -```bash -go test ./parser/... -timeout 5s -``` - -The tests are very fast. If a test is timing out, it indicates a bug (likely an infinite loop in the parser). - -## Checking Skipped Tests - -After fixing parser issues, check if any skipped tests now pass: - -```bash -go test ./parser -check-skipped -v 2>&1 | grep "PASSES NOW" -``` - -Tests that output `PASSES NOW` can have their `todo` flag removed from `metadata.json`. This helps identify when parser improvements fix multiple tests at once.