Skip to content

Type native Postgres trees as PostgreSQL 18.6 does, and drop follow-on findings - #153

Merged
kvlonge merged 4 commits into
mainfrom
wip/pg-typing-native
Nov 7, 2026
Merged

kvlonge merged 4 commits into
mainfrom
wip/pg-typing-native

Conversation

@kvlonge

@kvlonge kvlonge commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

Postgres typing from native trees, checked against PostgreSQL 18.6, plus narrow follow-on suppression for Postgres name findings. Snowflake and DuckDB behaviour is unchanged.

Typing

Under the Postgres profile, typing now:

  • resolves || on arrays (array || array, array || element), jsonb, bytea and other non-string types through pg_operator;
  • gives lower/upper of a range or multirange its element type;
  • reads 0x, 0o, 0b and _-separated integer literals;
  • keeps the precision of current_time(p), current_timestamp(p), localtime(p) and localtimestamp(p);
  • collapses nested array casts (integer[][]) to one array type;
  • drops the typmod from polymorphic results such as lag and lead.

Where the exact type isn't certain, typing abstains (untyped) and doesn't guess. That covers casts to pseudo-types and unknown, polymorphic calls whose arguments disagree or include untyped literals, and || between an untyped literal and an array.

Each change has a case in given_a_native_postgres_tree_when_typing_outputs_then_types_follow_postgresql_18_6. All 15 cases fail on main. Every expected type was recorded from a postgres:18.6 view column.

Engine-type replay through the native parser

given_engine_recorded_postgres_types_when_typing_outputs_then_none_is_wrong now also replays through the native PostgreSQL parser. Each replay fails on any wrong type and on any rise in the untyped count.

Fixture Path Exact Wrong Untyped
expression matrix (1,367 typed) shared parser 1,322 0 45
expression matrix native parser 1,345 0 22
accepted query-shaped syntax cases (959 typed) native parser 683 0 276

On main, the native figures are 1,343 / 2 / 22 and 662 / 31 / 276. The 276 untyped include 43 queries the native parser on main can't read whole. They span 15 SQL/JSON and SQL/XML forms, and the pending Postgres parser work already reads them.

The new fixture postgres_syntax_types.json comes from python scripts/record_postgres_types.py --container <pg18.6> --docker '<docker>' --syntax-cases. Re-recording the existing matrix fixture with the changed script reproduces it byte for byte.

Test-only dependency: sqlquack-semantics gains a [dev-dependencies] entry on sqlquack-syntax-postgres, for the native replay. Its normal dependencies don't change.

Follow-on name findings (Postgres profile only)

A CTE, or a derived table in FROM, is faulted when its body has a finding or it reads a faulted CTE. Column references checked against a faulted relation's columns (E201, E221) are not reported, because they repeat the root finding. The root finding, unknown qualifiers (E222) and findings against other relations are still reported. The test covers:

  • a cascade through a CTE and through a derived table;
  • a root error plus an independent error in the same query, where both are reported;
  • clean CTEs and derived tables, whose unknown columns are still found.

The test fails without the change.

Proof on the exact head

Gate on the exact head 5e9722ee088a7c77098926a24fe99423602b670f against main 12b3c6f8d (its base), each step run once:

Step Result
make check-ci rc=0
make test-rust rc=0
Snowflake checker, 10 commands with summary and stale, main vs head 939 of 941 outputs byte-identical; snapshot.json differs only in catalogue_commit, translation.err only in the binary path; exit codes identical
summary/stale []
sqlquack verify with recorded results, libduckdb loaded main and head both 12,102 cases, 0 failed; outputs byte-identical apart from paths
syntax-coverage results --against main's baseline 0 regressions, 0 other changes; results-baseline.json unchanged
Lineage diff, Snowflake and DuckDB profiles 7,829 cases, 15,658 outputs, identical

The Postgres coverage figure is unchanged: 1,384 of 1,555 forms, 19,346 of 22,726 obligations, 0 problems.

The Postgres typing rules now resolve `||` on arrays, jsonb, bytea and
other non-string types through pg_operator, give lower and upper of a
range or multirange its element type, read hexadecimal, octal, binary
and underscore-separated integer literals, keep the precision of
current_time, current_timestamp, localtime and localtimestamp, collapse
nested array casts to one array type, and drop the typmod from
polymorphic results such as lag and lead. Casts to pseudo-types and
unknown, and polymorphic calls whose arguments do not agree, are left
untyped instead of guessed.

The engine-type replay now runs through the native PostgreSQL parser
as well as the shared one, over the expression matrix and over every
accepted query-shaped syntax case. Each replay fails on any wrong type
and on any rise in the untyped count. scripts/record_postgres_types.py
--syntax-cases records the second fixture from a postgres:18.6
container.
Under the Postgres profile, a CTE whose body has a finding, or that
reads a CTE that does, is faulted. Column references checked against a
faulted CTE's columns are not reported, because they repeat the root
finding. The root finding, unknown qualifiers and findings against other
relations are still reported. Other profiles are unchanged.
A derived table in FROM whose body has a finding, or reads a faulted
CTE, is faulted like a CTE, so column references checked against its
columns are not reported again. The root finding and findings against
other relations are still reported.
Single-line doc comments, explicit parse results, a named helper for the
faulted-CTE lookup, a per-case loop in the native typing test and a named
constant for the accepted-case marker. No behaviour changes.
@kvlonge
kvlonge merged commit 6e318d1 into main Nov 7, 2026
2 checks passed
@kvlonge
kvlonge deleted the wip/pg-typing-native 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