Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -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.
103 changes: 103 additions & 0 deletions cmd/next-test/main.go
Original file line number Diff line number Diff line change
@@ -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))
}
21 changes: 0 additions & 21 deletions parser/CLAUDE.md

This file was deleted.