Skip to content

Give SQLite its own engine rules; DuckDB quoted group keys and recursive CTE types - #156

Closed
kvlonge wants to merge 16 commits into
mainfrom
wip/sqlite-semantics
Closed

kvlonge wants to merge 16 commits into
mainfrom
wip/sqlite-semantics

Conversation

@kvlonge

@kvlonge kvlonge commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

Why

SQLite had no engine arm in sqlquack-semantics, so its coercion and predicate findings, computed and literal types, cast spelling and output names fell through to Snowflake's rules. SQLBuild's SQLite models got Snowflake findings and Snowflake-spelled types.

Changes

  • SQLite gets its own Engine arm. Where SQLite's answer isn't recorded, typing, cast spelling and output naming abstain, and type checks stay off until its coercions are recorded, following the Databricks pattern.
  • SQLite window-only calls that arrive as generic calls raise E232 without OVER.
  • DuckDB: a GROUP BY key matches quoted and unquoted spellings of the same column, which removes a false E230.
  • DuckDB: a recursive CTE column whose recursive branch disagrees with the anchor's type is left untyped instead of taking the anchor's type.

Verification

  • make check-ci RECORDER=0 and make verify-recorded RECORDER=0 (0 failed, 0 pending, 0 unproven) pass. The landing head's results are posted as a gate comment.
  • Over SQLBuild's compiled corpus (1,376 Snowflake, 6,227 DuckDB and 345 SQLite models, plus broken-name variants): Snowflake output is byte-identical; SQLite has 0 false findings checked against sqlite3; DuckDB has 0 wrong types checked against DuckDB 2.0.0.

… where not recorded; DuckDB group keys and recursive types

SQLite fell through to Snowflake's engine. It now has its own: no type
findings, as its coercions are not recorded; no grouping rule, as SQLite
reads a bare column from any row of its group; duplicate table aliases and
negative LIMITs pass, as sqlite3 accepts them. Duplicate CTE names, CTE
column alias counts, set-operation widths, window calls without OVER and
catalogue arities still raise. Only literals and casts to a fixed affinity
are typed; computed values stay untyped, and unaliased expressions unnamed.

A quoted read of a grouped column in another case no longer raises E230 in
DuckDB, which compares names without regard to case. A DuckDB recursive
CTE keeps its anchor's column types, as DuckDB casts the recursive branch.
Snowflake and PostgreSQL output is unchanged.
…s tokenize.c

New crate sqlquack-syntax-sqlite. The scanner follows sqlite3GetToken and the
WINDOW, OVER and FILTER checks of sqlite3RunParser; against a C driver built on
the 3.53.4 amalgamation it splits 203,085 inputs (the SQLite catalogue, the
SQLBuild SQLite models and random fuzz) into the same tokens.

The recursive-descent parser over parse.y is a first draft, not yet checked
against the engine.
…ueries and SQLBuild's SQLite models

Grammar action checks now report their error only once the parser takes the
next token, as SQLite reduces before it shifts: a duplicate CTE name, ORDER BY
or LIMIT before a compound operator, the compound term limit, unknown join
types, ON or USING without a join, unsupported frames and the call checks.
NOT after an operand that cannot take it and a call cut off after its star
fail where SQLite fails. An unquoted true or false is a boolean, as SQLite
resolves it when no column has that name. Adds verdict tests and a README
listing the gaps and lossy mappings.
…ts IN list nesting

A statement whose tree is deeper than the shared parser's 512-level AST depth
guard now fails as too deep, so nested derived tables within the 1,000-level
recursion limit no longer build trees that overflow serde_json on a 2 MiB
stack. IN lists now parse one level deeper each, so nested IN lists count
toward the recursion limit and grow the stack instead of overflowing it.
… CTE fields

Main's shared tree added locks and with_ties to set operations, using_alias to
joins and search_cycle to CTEs. SQLite has none of these, so the parser sets
them empty.
…3.53.4

Lists, per nested shape, how deep the parser and sqlite3 each accept, and
the shapes the parser accepts past sqlite3's own limits.
Both the parser and sqlite3 accept 500 terms and refuse 501, so the limits
table says 500 for both, and the parser refuses at or before sqlite3's figure
rather than always earlier.
…d tree walks them stack-safely

The shared tree now clones, compares, serializes and drops deep trees on a
grown stack, so the parser no longer needs the 512-level AST depth guard to
keep serde_json within a 2 MiB stack. It refuses past 8,000 tree levels
instead, as a backstop; the 1,000-level recursion limit binds first. Nested
subqueries now parse to 499-999 levels and flat chains to 998 operators,
where the 512 guard stopped them at 170-511. The README's limits table has the
new figures against sqlite3 3.53.4 and names the shapes the parser now
accepts past sqlite3's parser-stack and expression-size limits.
… parser limit on a small stack

The SQLite parser now builds trees about 2,000 levels deep, and lineage
recursed once per expression and query level, overflowing a 2 MiB debug
thread from about 400 levels. Expression lineage, query resolution, FROM
items and the per-level findings now continue on a new stack segment when
the current one runs low, as the SQLite parser does.

A test analyses 998 nested derived tables (a tree 1,998 levels deep) and
990 chained additions on a 2 MiB thread, and checks that deeper nesting
gets the parser's clean refusal.

kvlonge commented Nov 8, 2026

Copy link
Copy Markdown
Contributor Author

Closing: SQLQuack lands through local-gate trains, not PR merges (D94), so open PRs only add clutter (D628/D630). The branch is kept. Any work here that isn't on main yet is tracked by the train owner and lands through a train.

@kvlonge kvlonge closed this Nov 8, 2026
@kvlonge
kvlonge deleted the wip/sqlite-semantics branch November 9, 2026 21:32
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.

1 participant