Skip to content

Commit dc443db

Browse files
committed
Add the SQL renderer, AST dump, round-trip property and snapshots
The second half of PLAN.md's answer to "SQLite has no parse-tree oracle". Accept/reject conformance cannot see a dropped clause or an operator associated the wrong way, so two layers stand in for upstream goldens: - ast.String/ast.Statements render a tree back to SQL. Not a formatter, and no promises beyond re-parseability. Parenthesisation needs no precedence logic, because the parser keeps explicit parentheses as ParenExpr nodes. - internal/dump renders a node reflectively, so it cannot fall behind the node set: a field added to a node shows up in the next snapshot diff whether or not anyone remembered. Two option sets — everything, for snapshots, and shape-only for comparisons, which drops the spans and Raw text a round trip is entitled to change. - TestRoundTrip runs parse/render/parse over all 20,815 accepting corpus cases and compares the trees, then checks that rendering is idempotent. - TestASTSnapshots covers a hand-written tour of the node set under parser/testdata/ast, with -update to rewrite the goldens. Two things this found: - IndexedColumn recorded that a COLLATE was present but not which collation, so "FOREIGN KEY(b COLLATE nocase DESC)" lost the name. - Three nodes built their Span from a composite literal that also called the parse function for their last child, so the span ended before the child began. Go does not order those evaluations. Enum types gained String methods, so a snapshot diff names what changed instead of printing an integer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JzBeCg7rjweVW3uGPg5G7T
1 parent 6f3292a commit dc443db

20 files changed

Lines changed: 5840 additions & 10 deletions

CLAUDE.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ https://sqlite.org/lang.html.
1313
- **Never edit `parser/testdata/*.test` by hand.** Corpus files are produced
1414
by `cmd/regenerate-parse` from SQLite's own test suite plus a real SQLite
1515
build (the oracle). `*.metadata.json` sidecars are updated by tooling
16-
(`go test ./parser -check-parse`), not by hand.
16+
(`go test ./parser -check-parse`), not by hand. The hand-written snapshot
17+
inputs under `parser/testdata/ast/` are the exception — see below.
1718
- Every nontrivial parse function carries a comment naming the `parse.y`
1819
rule(s) it implements.
1920
- Error messages must match SQLite's parser byte-for-byte
@@ -47,6 +48,20 @@ semantic, so meyer is permitted to accept such statements. The pattern list
4748
lives in `internal/testfile` (`syntaxFamily`) and can be extended without
4849
regenerating the corpus, because the corpus stores raw oracle output.
4950

51+
## Tree shape
52+
53+
Accept/reject conformance cannot see a dropped clause or a mis-associated
54+
operator, so two further checks stand in for the parse-tree goldens SQLite
55+
cannot produce:
56+
57+
- **Round trip** (`TestRoundTrip`): every corpus case that parses is
58+
rendered back to SQL with `ast.Statements`, re-parsed, and the two trees
59+
compared structurally with `internal/dump`. Spans and `Raw` fields are
60+
excluded — the renderer promises re-parseability and nothing else.
61+
- **Snapshots** (`TestASTSnapshots`): `parser/testdata/ast/*.sql` are
62+
hand-written and meant to be edited; their `.tree` goldens are rewritten
63+
with `go test ./parser -update` and reviewed in the diff.
64+
5065
## The loop
5166

5267
```sh

ast/ddl.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -148,12 +148,12 @@ func (n *TableConstraint) Children() []Node {
148148
// eidlist ::= eidlist COMMA nm collate sortorder.
149149
type IndexedColumn struct {
150150
Span
151-
Name *Ident `json:"name"`
152-
Collate bool `json:"collate,omitempty"`
153-
Order SortOrder `json:"order,omitempty"`
151+
Name *Ident `json:"name"`
152+
Collation *Ident `json:"collation,omitempty"`
153+
Order SortOrder `json:"order,omitempty"`
154154
}
155155

156-
func (n *IndexedColumn) Children() []Node { return nodes(n.Name) }
156+
func (n *IndexedColumn) Children() []Node { return nodes(n.Name, n.Collation) }
157157

158158
// ForeignKeyAction is the action of an ON DELETE / ON UPDATE clause.
159159
type ForeignKeyAction int

0 commit comments

Comments
 (0)