Skip to content

Milestones 3–4: AST, transformers, and the tree-shape gate - #3

Merged
kyleconroy merged 2 commits into
mainfrom
claude/milestones-3-4-9xcila
Aug 21, 2026
Merged

Milestones 3–4: AST, transformers, and the tree-shape gate#3
kyleconroy merged 2 commits into
mainfrom
claude/milestones-3-4-9xcila

Conversation

@kyleconroy

Copy link
Copy Markdown
Contributor

Implements milestones 3 and 4 of PLAN.md: the public AST and Parse API, hand-written transformers for the full SELECT tree plus DML and DDL, and the json_serialize_sql tree-shape gate — all pinned against the milestone-2 oracle.

Milestone 3 — AST + transformer core

  • ast/: the public AST — statements, query nodes, table refs, expressions, values, and DDL infos, porting upstream's parser node classes.
  • parser/: Parse/ParseStatement/ParseExpr on top of the milestone-2 engine, with Error carrying message, offset, line, and column; statement spans tile the input.
  • Transformers (parser/transform_*.go): the port of upstream's transformer/, replicating its effective behavior — operator desugarings (LIKE family, IN, ANY/ALL, list comprehensions, ARRAY(SELECT), interval literals, IS TRUE/FALSE, EXTRACT), prepared-parameter numbering, named windows, GROUP BY dedup/CUBE/ROLLUP set orders, SHOW/DESCRIBE/SUMMARIZE expansion, PIVOT/UNPIVOT, and upstream's location-tracking quirks (point spans for additive operators, outermost-only spans on chains, location-free synthesized nodes).
  • internal/serialize/: renders the AST in json_serialize_sql-compatible JSON; serialize.Equal compares structurally with the few documented normalizations for upstream's unordered hash containers.
  • cmd/serialize-diff: sweeps ad-hoc statements or the corpus SELECT subset against a live oracle binary and writes the vendored goldens.
  • cmd/debug-parse -ast: dumps the transformed AST as JSON.

Milestone 4 — DML + DDL transformers

  • DML: INSERT (column lists, BY NAME/POSITION, DEFAULT VALUES, OR REPLACE/IGNORE, ON CONFLICT, RETURNING), UPDATE (aliased targets, FROM, tuple SET), DELETE (USING), TRUNCATE, and MERGE INTO, with parameters end-to-end. DML inside a CTE body becomes upstream's INSERT/UPDATE/DELETE query nodes, serialization included.
  • DDL: CREATE TABLE (columns, constraints, generated columns, CTAS), VIEW, SCHEMA, INDEX, SEQUENCE, TYPE; the ALTER families; DROP families. MACRO/SECRET/TRIGGER remain ErrUnsupported for milestone 5.

Conformance

  • Accept/reject (TestCorpus) now classifies with the full Parse pipeline, so transformer-raised Parser Errors count alongside the matcher's syntax errors; the 29 todo entries this resolves are removed. Getting the gate green fixed a batch of oracle-verified behaviors: the regex ~/~*/!~/!~* ANY/ALL CASE desugar, OPERATOR(schema.op) semantics, FROM-clause UNPIVOT shapes, PIVOT ... IN (SELECT ...), bare SHOW, out-of-range literals as ±Inf DOUBLEs (serialized as upstream's bare Infinity tokens), JOIN BY type aliases, and DML target-location conventions.
  • Tree shape (TestSerializeGoldens): 2,153 vendored goldens sampled from the corpus SELECT subset.
  • Milestones 3–4 exit criterion: the full-corpus sweep against the pinned binary is clean — 25,974 SELECT-subset statements, 25,846 match, 0 mismatch, 128 skipped (statements the oracle itself refuses to serialize).
  • New hand-written shape tests pin the Go-side DML/DDL AST surface (parser/dml_test.go, parser/ddl_test.go).

go build && go vet && gofmt -l && go test -race ./... all pass.

🤖 Generated with Claude Code

https://claude.ai/code/session_01B918Vu4wvBaZcRUX4nkQdr


Generated by Claude Code

claude added 2 commits August 21, 2026 17:41
Add the typed AST (package ast, DuckDB-native node shapes), the public
parser API (Parse/ParseStatement/ParseExpr), and transformers for the
full expression layer and SELECT tree: query nodes, set operations
(n-ary UNION flattening), CTEs (incl. recursive conversion and USING
KEY), table refs, joins, PIVOT/UNPIVOT, samples, windows (named-window
resolution), GROUP BY grouping sets, result modifiers, and SHOW/DESCRIBE
desugaring. Every upstream transform-time rewrite the pinned build
performs is replicated: operator calls as functions, IN/ANY/ALL and
BETWEEN desugaring, list comprehensions, ARRAY(subquery), interval
literals, star modifiers, prepared-parameter numbering, and constant
typing (INTEGER through UHUGEINT, DECIMAL width/scale).

internal/serialize renders the AST as json_serialize_sql-compatible
JSON; cmd/serialize-diff diffs it statement-by-statement against the
pinned DuckDB CLI. The full corpus SELECT subset (25,414 statements) now
matches the oracle exactly: 25,301 match, 0 mismatch, 113 skipped
(statements the oracle itself refuses to serialize). A deterministic
sample of 2,108 statements is vendored as goldens for
parser/serialize_test.go; comparator normalizations for upstream's
unordered containers are documented on serialize.Equal.

The lexer learns upstream's Unicode-whitespace handling, the matcher
exposes an Expression entry point for ParseExpr, and debug-parse gains
an -ast mode.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B918Vu4wvBaZcRUX4nkQdr
DML: INSERT (column lists, BY NAME/POSITION, DEFAULT VALUES, OR
REPLACE/IGNORE and ON CONFLICT, RETURNING), UPDATE (aliased targets,
FROM, tuple SET), DELETE (USING), TRUNCATE and MERGE INTO, with
prepared-parameter numbering end-to-end. DML statements inside a CTE
body become INSERT/UPDATE/DELETE query nodes mirroring upstream's
serialization (TRUNCATE lands in its delete-node form).

DDL: CREATE TABLE (columns, constraints, generated columns, CTAS),
VIEW, SCHEMA, INDEX, SEQUENCE and TYPE; the ALTER families; DROP
families. MACRO/SECRET/TRIGGER stay ErrUnsupported for milestone 5.

The corpus accept/reject harness now classifies with the full Parse
pipeline, so transformer-raised Parser Errors count alongside the
matcher's syntax errors; the todo entries this resolves are removed.
Chasing the remaining disagreements fixed a batch of transformer
behaviors against the pinned oracle:

- regex ANY/ALL (~, ~*, !~, !~* over lists) desugars to upstream's
  CASE over list_transform/list_contains/list_filter
- OPERATOR(schema.op) keeps its schema and always builds a function
  (comparison spellings included); the prefix form folds qualifiers
  into the name; three-part qualifiers raise upstream's error
- other-operator chains ('a' || 'b' || 'c') carry a location only on
  the outermost fold, like the binary ladder levels
- FROM-clause UNPIVOT target lists parse (shape bug), multiple IN
  groups raise upstream's single-pivot-element error, and the pivot
  ref location covers the clause body
- statement PIVOT ... IN (SELECT ...) enum-discovery targets parse
- bare SHOW expands like SHOW ALL TABLES
- number literals beyond the double range become ±Inf DOUBLE
  constants, serialized as upstream's bare Infinity tokens
- UPDATE targets span through SET; DELETE/TRUNCATE targets carry no
  location; qualified UPDATE SET targets raise upstream's error
- ORDER BY ALL inside ARRAY(SELECT ...) clones the star under the
  __array_internal_idx alias

The full-corpus serialize sweep against the pinned binary is clean
(25,974 SELECT-subset statements: 25,846 match, 0 mismatch, 128
skipped where the oracle refuses to serialize), and the vendored
goldens are regenerated from it. New hand-written shape tests cover
the DML and DDL AST surface.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B918Vu4wvBaZcRUX4nkQdr
@kyleconroy
kyleconroy merged commit 1c88d3b into main Aug 21, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants