Repository navigation
feat(engine): carry keys through set operations and column lists - #147
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Important
One soundness regression in the Postgres reading of enclosing WITH queries. A nested WITH RECURSIVE that reuses an outer WITH query's name gets the outer query's keys, so a query that returns many rows is typed maybeOne. Also one docs paragraph that says more than the engine does.
Reviewed changes
This review covers the whole PR at 5655da1: key inference through set operations, column lists, sibling WITH queries and key columns left out of the select list, on Postgres, SQLite and MySQL, plus MySQL parameter typing through derived tables, WITH queries, set operations and ON clauses.
- Postgres set operations —
set_keyskeeps the left side's keys forintersect/except(withallor without). A non-allset operation is keyed on its whole row when every column passes one table column through on both sides, with one type. - Postgres omitted key columns — a key column left out of the select list still counts when the WHERE pins it to a value (
pinned_by_value), when an aliased index expression keeps it (kept_expression), or when an inner equality keeps a same-typed identity column in the list (equated_output). - Postgres column lists and sibling WITH queries —
renamed_keys/inner_namemap keys by position.Catalog::enclosing_bodiesholds deparsed sibling and enclosing WITH queries, soSource::Enclosingnow has keys. - SQLite compound selects —
compound_keysadds a whole-row key over arms that are all plain columns of one affinity and collation. It keeps the left side's keys forintersect/except. A key column the WHERE pins may also be left out. - MySQL keys — WITH queries and views with column lists are now read by position.
set_keyskeys the whole row only when every column is an exact number on both sides. A key column the WHERE pins may be left out. - MySQL parameter types —
comparednow looks inside WITH bodies, derived tables, each side of a set operation and joinONclauses. A parameter compared on both sides of a set operation keeps a type only when the two sides agree. - Oracle cases, docs and rebuilt components — new statements in
subquery_keys,sqlite_subquery_keysandmysql_subquery_keys; reblessed expectations;postgres.mdxandmysql.mdxupdates; three rebuiltengine.core.wasm.
claude-opus-5-5 | 𝕏
There was a problem hiding this comment.
✅ No new issues found. Both findings from the previous review are fixed.
Reviewed changes
This run covers 745f233, the one commit since the review at 5655da1.
- Fixed WITH query shadowing on Postgres —
Catalog::scopednow adds an entry for every WITH query in scope, and its statement isNonewhen the query is in a recursive clause, isn't a SELECT, or doesn't deparse. When the nearest entry for a name has no statement,enclosing_bodyreturnsNone. This means an inner WITH query hides an outer one with the same name even when the engine can't read it. A recursive clause's ownSource::Withbodies are never read, so theuptocutoff can't skip a forward reference to a later WITH query in the same clause. - Added an oracle case —
subquery_keys/recursive_shadows_enclosing.sqlis the failing statement from the earlier review, and it now infersmany. - Corrected
postgres.mdx— the docs now give the whole-row condition for set operations and say thatintersect/exceptkeep the left side's keys with or withoutall. They also cover the alias rule for an indexed expression, and the "leaves no key" line now exempts an expression that a unique index covers. - Rebuilt the Postgres component —
packages/postgres/engine/engine.core.wasmwas rebuilt in the same commit.
claude-opus-5-5 | 𝕏
A set operation without ALL keys its whole row, and INTERSECT and EXCEPT keep their left side's keys. A column list renames a subquery's or WITH query's keys by position, a WITH query reads a sibling's keys, and a key column the WHERE pins to one value, an inner equality makes equal to a kept column, or an aliased index expression keeps, still counts.
A compound SELECT but UNION ALL keys its whole row where every column is one column of one affinity and collation on each arm, INTERSECT and EXCEPT keep their left side's keys, and a key column the WHERE pins to one value still counts.
A set operation without ALL keys its whole row where every column is an exact number on both sides, INTERSECT and EXCEPT keep their left side's keys, a column list renames a derived table's, WITH query's or view's keys by position, and a key column the WHERE pins to one value still counts. A parameter compared in a join's ON, inside a derived table or a WITH query, or through a column list or a set operation takes its column's type.
A recursive WITH query, a write, or one that does not deparse is still in scope, with no statement to read, so a reference to its name gets no keys rather than an outer WITH query's of the same name.
745f233 to
2251b10
Compare

More of the unique keys a subquery, a WITH query or a view keeps, on all three databases, so that reading one by such a key infers one row at most.
Rules
(…) s (a, b),with s (a, b) as (…), a view created with onePostgres reads a sibling WITH query by deparsing its statement, and so reads it under the WITH queries listed before it.
On MySQL, a parameter compared in a join's ON, inside a derived table or a WITH query, or through a column list or a set operation now takes its column's type (a BIGINT's is
int8, not the prepare'sint4). A set operation gives the parameter one column per side, whose types must agree.Checks
subquery_keys,sqlite_subquery_keysandmysql_subquery_keys, adversarial ones included (a swapped column list, a union of mixed types, a recursive sibling, an outer join's equality, an unaliased index expression), run over seeded edge values with no contradiction.sqlite_subquery_keys/union.sqlandmysql_subquery_keys/view_column_list.sql:manytomaybeOne.int4toint8for BIGINT columns (mysql_subquery_keys,mysql_limits/union_limit_5).--strict-coveragepasses but for the DuckDB cases, which need--features duckdband which this change does not touch. The engine tests pass, the workspace builds with no warning, andnub run testwith both test databases passes 303 tests, none skipped, with the three rebuilt components matching their sources.