8000 fix(postgres): keep not null on any column beside a table read twice by colinhacks · Pull Request #59 · colinhacks/reylo · GitHub
[go: up one dir, main page]

Skip to content

fix(postgres): keep not null on any column beside a table read twice - #59

Merged
colinhacks merged 1 commit into
mainfrom
probe-repeated-table
Oct 23, 2026
Merged

colinhacks merged 1 commit into
mainfrom
probe-repeated-table

Conversation

@colinhacks
Copy link
Copy Markdown
Owner

A statement that reads a table twice, with an outer join anywhere in it, typed every column but a top-level alias.column as nullable: the describe names a column's table, never which reading of it, so provenance was taken to say nothing. A large statement meets this easily, since a scalar subquery reads a table its WITH queries already read, and a left join sits beside it.

Statement main This PR
large_queries/repeated_table_outer_join_short.sql: select o.id, name, email, r.rating, exists (select 1 from orders x …) from orders o join users u … left join reviews r … name: text? name: text
inference_precision/whole_row_self_join.sql: jsonb_agg(b) of users a left join users b [users{ id: int4?, name: text?, email: text? }?] [users{ id: int4, name: text, email: text? }?]

An outer join nulls a table by its name: every reading of it, and every relation a WITH query it nulls reads. So a column of a table some outer join nulls is nullable whichever reading it comes from, and a column of a table no outer join nulls is what the catalog says, whichever reading it comes from. Neither needs the reading, and a repeated table no longer hides provenance; a top-level alias.column still takes the reading its alias names, which tells a table joined to itself apart (employees e left join employees m, where e.name stays non-null). Grouping sets still do.

Checked

  • The engine's native tests; every Postgres and SQLite oracle case with --strict-coverage (the two lines above change, and every other case is as it was); the whole workspace builds with no warning.
  • packages/postgres/engine.wasm rebuilt (with the other two in one run; only this one changes); nub run typecheck and nub run test against Postgres 18.

An outer join nulls a table by its name, so every reading of it: a
column of a table read twice is nullable where the join nulls the table
and as the catalog says where it does not, whichever reading it comes
from. A bare column or a whole row beside such a table was nullable.
Copilot AI balanced review requested due to automatic review settings October 23, 2026 03:56
@vercel
vercel Bot commented Oct 23, 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 23, 2026 3:57am 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.

✅ No new issues found.

Reviewed changes

I reviewed the Postgres settle change, which stops a table read twice from hiding provenance, along with the two oracle cases it moves and the rebuilt artifact.

  • settle no longer degrades a repeated table: hidden now comes from grouping sets alone. A column with provenance is nullable when the catalog says so or when an outer join nulls its table by name. Where a top-level alias.column names its reading, that reading's own flag is used instead.
  • New oracle case large_queries/repeated_table_outer_join_short.sql: here a scalar subquery reads orders again beside a left join, and name now comes out text (it was text?).
  • inference_precision/whole_row_self_join.sql: in jsonb_agg(b) over users a left join users b, the fields are non-null inside the element, and the element stays nullable.
  • packages/postgres/engine.wasm rebuilt: tests/fingerprint.test.ts passes for all three artifacts at this head.

I checked the soundness argument against analyze.rs:

  • nullable_tables is collected by relation name over the whole tree (relnames_in on every outer JoinExpr's nulled side). That covers derived tables, LATERAL subqueries and joins inside WITH queries.
  • unreduced → nulled_tables expands a nulled WITH query to every relation it reads, including a DML target.
  • The strict-WHERE and inner-ON reductions only apply to a relation with occurrences == 1, so a repeated name is never taken off.
  • own_provenance is false only for a set operation or a statement with no parse. aliased is empty in both cases, so folding the old degraded reading branch into the main branch loses nothing.
  • The self-join rule in site/content/docs/postgres.mdx (e.name stays non-null in employees e left join employees m) still holds.

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

@colinhacks
colinhacks merged commit f83f2b1 into main Oct 23, 2026
6 checks passed
@colinhacks
colinhacks deleted the probe-repeated-table branch October 23, 2026 04:03
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