Specializer wave 5: structural + dialect sweep — corpus 395 -> 484 (TASK-52) - #38
Merged
Conversation
…zed scope Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…sured before implementation (TASK-52) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ict superset (TASK-52 stage 1a) Spike (pins-wave5/sqlparser-spike.json): GenericDialect accepts every form DuckDbDialect did plus ^@ (PGStartsWith), * ILIKE, * RENAME; nothing was DuckDbDialect-only. Regression net: cargo test 129 green, py gate 545+13 green, corpus replay byte-identical (395 match / 281 unsupported / 2 known-divergent, zero FAILs). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… corpus 395 -> 397 (TASK-52 stage 1b) sqlparser parses DuckDB's colon alias SILENTLY WRONG (Snowflake JsonAccess) under every dialect, so no parse-error fallback can catch it; rewrite.rs desugars 'ident COLON' at select-item start to 'expr AS ident' on the token stream before parsing. Handles quoted aliases, nested selects, DISTINCT/ALL, and leaves :: casts and bracketed slice colons untouched. The binder's JsonAccess rejection stays as backstop. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… 397 -> 427 (TASK-52 stage 2) The wave-3 kernels (StrOp2i::Extract, SKind::Sslice) already matched the wave-5 pins; this binds the bracket AST syntax onto them: chained access, open-bound desugar ([:b]==[1:b], [a:]==[a:-1] — NULL is NOT an open bound), step slices reject with DuckDB's not-implemented shape, and the extract index gets substr's +-2^32 runtime trap window on both backends (per-row, NULL-skipped, matching pins-wave5/subscripts-extended.json). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ation — corpus 427 -> 431 (TASK-52 stage 3) New i64 BinOps on both backends: Ishl with DuckDB's five-step trap ladder (negative value even << 0, negative count, zero shortcut before the range check, count >= 64, overflow at 2^(63-count) via i128), Ishr total (out-of-range counts -> 0, arithmetic otherwise), Iand/Ior/Ixor plain two's-complement. DuckDB puts << >> & | in ONE flat left-assoc tier while sqlparser tiers them — the frontend re-associates maximal runs in source order before binding (user parens stop the flatten). xor is function-only; ^ stays cleanly unsupported (it is pow with a precedence sqlparser can't mirror). Fold reuses the same duck_shl kernel so folding and trapping can never disagree. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…her — corpus 431 -> 446 (TASK-52 stage 4)
^@ binds to the existing starts_with kernel (byte-prefix, VARCHAR-only, no
implicit casts). GLOB gets a real byte-level matcher (StrOp2::Glob, both
backends): ? consumes ONE byte (not a codepoint), classes are byte-sets
where only ! negates ('^' is a literal member), ']' is literal-if-first,
'-' literal-if-first else a range whose endpoint may be ']' ('[a-]' is
dead), backslash escapes outside classes only (dangling = dead), and
malformed patterns match nothing rather than erroring — all measured
(pins-wave5/text-operators.json). sqlparser can't parse GLOB at all, so
rewrite.rs turns infix GLOB into LIKE with a __glob_pat identity marker
the binder unwraps; NOT GLOB stays a parse error exactly like DuckDB, and
the marker name is reserved at the frontend gate.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…orpus 446 -> 477 (TASK-52 stage 5) Star forms per pins-wave5/star-forms.json: name filters * LIKE/ILIKE/ NOT LIKE/GLOB over declared-case column names (LIKE cs, ILIKE Unicode fold, GLOB via the byte matcher; zero matches errors, filter follows EXCLUDE), * REPLACE (position/name kept, type may change, sees EXCLUDEd originals), * RENAME (silent on nonexistent targets, ambiguous renames all copies), qualified EXCLUDE (strips one table's copy; unqualified strips all; EXCLUDE of a USING-merged column stays unsupported — DuckDB unmerges it). sqlparser can only parse * ILIKE and treats it as mutually exclusive with EXCLUDE, so rewrite.rs encodes the real operator AND any absorbed EXCLUDE entries into a marker inside the ILIKE pattern. Duplicate output names: the frontend reject is gone — dedup_output_names applies DuckDB's own boundary rename (left-to-right, first keeps its name, own-case _N smallest free, case-insensitive collision check covering generated candidates: id,ID -> id,ID_1; id,id,id_1 -> id,id_1,id_1_1). Identical to DuckDB's subquery/CTE/CTAS rename and .df() dedup, verified against .df() in tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…), NULL-op typing, mixed IN literals — corpus 477 -> 484 (TASK-52 stage 6) Per pins-wave5/binder-tail.json: lateral aliases with real-column-wins shadowing in SELECT and WHERE (WHERE now binds after the projection so DuckDB's alias-in-WHERE extension resolves; forward references get the pinned cannot-be-referenced-before-defined error); NULL <op> NULL types by operator (+ - * % BIGINT, / DOUBLE, comparisons BOOLEAN, ^@/|| VARCHAR); main.tbl resolves to the bare dynamic table while other schema qualifiers stay unsupported (DuckDB itself errors, never a bare-name fallback); NATURAL JOIN desugars to USING over common columns case-insensitively with the pinned hard error when none exist; t AS u(x, y) renames via a Cow column view (partial list = prefix rename, old names and the original table name fully shadowed, positions unchanged so marshalling still uses the original field names); BETWEEN/IN numeric-with-string/bool LITERALS cast to the numeric side (half-away rounding) — non-numeric literals stay clean-unsupported because DuckDB converts at EXECUTION time (an empty input succeeds; caught by corpus replay, not guessed). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…t fixes, TASK-52 all ACs checked + Done Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ahrzb
approved these changes
Jul 26, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The wave-5 structural + dialect sweep: ten commits taking served-bit-exactly from 395 to 484 of 678 (58% -> 71%), zero wrong answers and zero replay FAILs throughout. Pins-first as always: an 8-agent measurement fleet (7 DuckDB areas + a sqlparser capability spike) committed as
docs/superpowers/specs/2026-07-26-wave5-structural-pins.md+pins-wave5/*.jsonbefore any implementation, with a post-landing addendum recording the corrections the corpus caught.Stages (each landed with tests + corpus replay green)
^@,* ILIKE,* RENAME); corpus byte-identicalk: exprvia token pre-rewrite — sqlparser misparses it silently as a Snowflake JSON path, so no parse-error fallback could ever catch its[i]/s[a:b]bound onto the wave-3 kernels: chained access, open-bound desugar ([:b]≡[1:b], NULL is NOT an open bound), step-slices reject, and the asymmetric ±2^32 runtime trap window per-row on both backends<< >> & |+xor(): new i64 BinOps with DuckDB's five-step<<trap ladder and total>>; DuckDB puts all four in ONE flat left-assoc tier while sqlparser tiers them, so the frontend re-associates maximal runs in source order;^stays unsupported (it is pow with an unmirrorable precedence); fold shares theduck_shlkernel so folding and trapping can never disagree^@-> the starts_with kernel; GLOB gets a dedicated byte-level matcher (one-byte?, byte-set classes where only!negates and^is literal,[a-]is dead,\escapes outside classes, malformed patterns match nothing) reached via aLIKE __glob_pat(...)marker rewrite;NOT GLOBstays a parse error exactly like DuckDB* LIKE/ILIKE/NOT LIKE/GLOBover column names, qualified EXCLUDE,* REPLACE,* RENAME— and the duplicate-name contract: DuckDB's own boundary rename (left-to-right, own-case_N, case-insensitive collision check incl. candidates), identical to its subquery rename and.df()dedup, verified against.df()<op>NULL typing by operator,main.tblqualifier, NATURAL JOIN desugared to USING with the pinned hard error,t AS u(x, y)via a Cow column view, mixed BETWEEN/IN literal casts with half-away roundingCorpus-caught corrections (the three-outcome contract at work)
Two replay FAILs during stage 6, both fixed and recorded in the spec addendum:
NULL ^@ NULLneeded Str-typed nulls, and non-numeric string literals in an IN-list against a column stay clean-unsupported because DuckDB converts at execution time — the corpus case on an empty table succeeds.Verification
🤖 Generated with Claude Code