8000 fix(postgres): null what an outer-joined with query reads by colinhacks · Pull Request #18 · colinhacks/reylo · GitHub
[go: up one dir, main page]

Skip to content

fix(postgres): null what an outer-joined with query reads - #18

Merged
colinhacks merged 2 commits into
mainfrom
fix-outer-joined-with-query
Oct 22, 2026
Merged

colinhacks merged 2 commits into
mainfrom
fix-outer-joined-with-query

Conversation

@colinhacks
Copy link
Copy Markdown
Owner

An unqualified * over several FROM items, one of them a WITH query on the nullable side of an outer join, typed that query's columns as non-null:

with t as (select id as tag_id, label from tags)
select * from bar b left join t on false
-- was { …, tag_id: int8, label: text }, though every row null-extends t

The describe traces a column read through a WITH query to the table inside it, while the outer join nulls the query by its own name. A column read by name (t.label) was settled from the query's own probe, but a * across several items maps no column to its item, so it fell back to the table's NOT NULL.

Now an outer join that nulls a WITH query also nulls every relation the query reads, through the WITH queries it reads in turn, after the strict WHERE and ON reductions have had their say. A relation read inside the nulled query and again elsewhere in the statement is nulled there too, which is how a derived table on the nullable side already behaves.

  • oracle/cases/derived_tables: with_star_left_join_nulled.sql and with_star_nested_nulled.sql (both failed the oracle's seeded check before).
  • packages/postgres/engine.wasm rebuilt.

Copilot AI balanced review requested due to automatic review settings October 22, 2026 18:53
@vercel
vercel Bot commented Oct 22, 2026 •
Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
reylo Ready Ready Preview Oct 22, 2026 8:29pm UTC

Request Review

Copilot AI left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@pullfrog pullfrog Bot left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

The fix is sound for SELECT WITH queries, but one shape of the same bug remains: a data-modifying WITH query on the nullable side of an outer join is still typed non-null (inline). This predates the PR — it fails on main too — but the new with_tables is meant to cover exactly this case.

Reviewed changes

Reviewed the single commit that nulls the relations an outer-joined WITH query reads, so a * traced through the query to its table no longer keeps the catalog's NOT NULL.

  • Analysis::with_tables / nulled_tables — each CTE name maps to the relations its ctequery reads, closed over the WITH queries it reads in turn. nulled_tables adds them for every nulled CTE.
  • unreduced in postgres.rs — applies nulled_tables after the WHERE/ON reductions and the untrusted-witness put-back, so a CTE that a strict predicate reduces brings nothing.
  • Oracle cases — with_star_left_join_nulled.sql and with_star_nested_nulled.sql, plus the rebuilt engine.wasm.

Verified locally at 3220aa1:

  • cargo test -p reylo-engine-postgres --lib passes (141 tests).
  • The oracle's derived_tables (35 statements) and joins (18 statements) cases pass on PG16. The new case fails on main, as the PR says.
  • Scratch probes typed correctly: a WHERE-reduced CTE, a RIGHT JOIN, a CTE reached through a derived table, a view inside the CTE, and a join inside the CTE.
  • Nulling a relation that is also read elsewhere in the statement costs nothing in practice. Such a statement already sets has_repeated_table, so its columns are all nullable through hidden anyway.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using claude-opus-5-5 | 𝕏

Comment thread engine/postgres/src/analyze.rs Resolved
@colinhacks
colinhacks force-pushed the fix-outer-joined-with-query branch from 3220aa1 to 4a1e41b Compare October 22, 2026 19:06
The describe traces a column read through a WITH query to the table
inside it, so `select *` over an outer-joined WITH query typed that
query's columns from the table's NOT NULL.
@colinhacks
colinhacks force-pushed the fix-outer-joined-with-query branch from 4a1e41b to 69a1d14 Compare October 22, 2026 20:27
@pullfrog pullfrog Bot left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found. The data-modifying WITH query case from the prior review is fixed and pinned by a test.

Reviewed changes

I reviewed 69a1d14, the commit that nulls the target of an outer-joined data-modifying WITH query.

  • DML target in with_tables — collecting a WITH query's read set now also walks ctequery and adds the relation.relname of any InsertStmt/UpdateStmt/DeleteStmt/MergeStmt. It reads the field the same way analyze.rs:227 already does. Nothing else needs to change, because nulled_tables and unreduced consume the list as before.
  • Coverage — there is a new assertion in an_outer_join_that_nulls_a_with_query_nulls_what_it_reads and a new oracle case, derived_tables/with_insert_left_join_nulled.sql. It uses the non-colliding key -424242, so the INSERT actually runs under seeding.

I checked this locally at 69a1d14:

  • cargo test -p reylo-engine-postgres --lib: 145 passed.
  • The oracle derived_tables case passes all 36 statements on PG16, including the new one.

Pullfrog  | View workflow run | Using claude-opus-5-5 | 𝕏

@colinhacks
colinhacks merged commit 48ac41d into main Oct 22, 2026
4 checks passed
@colinhacks
colinhacks deleted the fix-outer-joined-with-query branch October 22, 2026 20:38
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

0