Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
117 commits
Select commit Hold shift + click to select a range
5301735
Rust PG server: a domain may be over a user domain's array type (RP-654)
jdrumgoole Nov 14, 2026
b8aef9e
Rust PG server: a domain may be over a composite type (RP-538)
jdrumgoole Nov 14, 2026
4257712
Rust PG server: CASE/COALESCE/GREATEST/LEAST over one domain keep it …
jdrumgoole Nov 14, 2026
2f84c60
Rust PG server: a LATERAL subquery sees the outer level's domain colu…
jdrumgoole Nov 14, 2026
98fe8e9
Rust PG server: a set operation of a domain and its base type has no …
jdrumgoole Nov 14, 2026
de1b4df
Rust PG server: a PL/pgSQL literal's input error carries its expressi…
jdrumgoole Nov 14, 2026
7b11dc6
Rust PG server: a generated foreign-key name is built from the bare t…
jdrumgoole Nov 14, 2026
6b57f38
Rust PG server: a folded DEFAULT is coerced to its column's length (R…
jdrumgoole Nov 14, 2026
a641589
Rust PG server: an explicit-cast DEFAULT prints its modifier (RP-863)
jdrumgoole Nov 14, 2026
2cddfc2
Rust PG server: an ARRAY sublink over numeric(p,s) describes its modi…
jdrumgoole Nov 14, 2026
307bf83
backlog: size RP-693 (unsized bpchar keeps its blanks)
jdrumgoole Nov 14, 2026
2f904c5
backlog: RP-693 needs its keys before its write path (no code change)
jdrumgoole Nov 14, 2026
c329bad
Rust PG server: min / max over a char(n) column describe modifier -1 …
jdrumgoole Nov 14, 2026
181f83f
backlog: RP-693 probed; what is left before an unsized bpchar keeps i…
jdrumgoole Nov 14, 2026
47f1b06
backlog: RP-693 needs a bpchar comparison in the filter path too (no …
jdrumgoole Nov 14, 2026
92034b9
Rust PG server: an index outside public can be renamed (RP-933)
jdrumgoole Nov 14, 2026
bac7192
backlog: RP-621 needs a decision, libpg_query drops the PL/pgSQL erro…
jdrumgoole Nov 14, 2026
d4192a6
Rust PG server: a sequence function over a relation that is not a seq…
jdrumgoole Nov 14, 2026
2458637
Rust PG server: a DROP in a schema that does not exist is 3F000 (RP-533)
jdrumgoole Nov 14, 2026
4f07b80
Rust PG server: a missing output column is reported before the WHERE'…
jdrumgoole Nov 14, 2026
fdb1a14
backlog: RP-621 decided -- fork pg_query to carry PL/pgSQL compile er…
jdrumgoole Nov 14, 2026
b8849a8
Rust PG server: the temp namespace follows the (sub)transaction that …
jdrumgoole Nov 14, 2026
50a10b6
Rust PG server: a domain array literal's 22001 carries its position (…
jdrumgoole Nov 14, 2026
434adb4
Rust PG server: a built-in routine is looked up among its overloads (…
jdrumgoole Nov 14, 2026
8260e66
backlog: RP-526 sized from probes of the join paths
jdrumgoole Nov 14, 2026
4047cf0
Rust PG server: a CHECK resolves a qualified column against its own t…
jdrumgoole Nov 14, 2026
19fe7a4
Rust PG server: a built-in routine takes a comment, keyed by its over…
jdrumgoole Nov 14, 2026
f865d5f
Backlog: RP-526 re-measured, the comma form of two schemas' t reads t…
jdrumgoole Nov 14, 2026
b1ab789
Rust PG server: a row's untyped literal takes its pair's type (RP-850)
jdrumgoole Nov 14, 2026
09eeff6
Rust PG server: two schemas' tables of one name share a FROM clause (…
jdrumgoole Nov 14, 2026
6989fc9
Rust PG server: rustfmt (RP-526)
jdrumgoole Nov 14, 2026
c19910f
Rust PG server: a composite type's attribute records its declared arr…
jdrumgoole Nov 14, 2026
f250fcd
Rust PG server: a FROM function's whole row under a column alias is i…
jdrumgoole Nov 14, 2026
27d916c
Rust PG server: CREATE TABLE finds a clashing index or composite by n…
jdrumgoole Nov 14, 2026
c8d0608
Rust PG server: drop the now-unused table_names; rustfmt the storage …
jdrumgoole Nov 14, 2026
36775ea
Rust PG server: ALTER COLUMN TYPE sees a domain through to its base t…
jdrumgoole Nov 14, 2026
6e4217a
Rust PG server: a pg_type read keyed to a built-in oid is built alone…
jdrumgoole Nov 14, 2026
a82b649
Rust PG server: rustfmt (RP-927)
jdrumgoole Nov 14, 2026
a6adf6b
Rust PG server: a sequence default's literal is resolved as regclass …
jdrumgoole Nov 14, 2026
9bc9f8d
Rust PG server: an unmade pg_temp is no schema to a routine type (RP-…
jdrumgoole Nov 15, 2026
e10bd45
Rust PG server: the statement after DDL reads only indexed tables' in…
jdrumgoole Nov 15, 2026
4d274e5
backlog: RP-911 records where its per-row invalidation starts
jdrumgoole Nov 15, 2026
0e3d1d7
backlog: RP-911 sizes its per-row invalidation hook to the storage wr…
jdrumgoole Nov 15, 2026
cfec6ed
Rust PG server: a missing column to an operator's left fails before t…
jdrumgoole Nov 15, 2026
7974e5d
Rust PG server: an ORDER BY constant that is no integer is 42601 (RP-…
jdrumgoole Nov 15, 2026
b962368
Rust PG server: a partition's copy of an index is named by its column…
jdrumgoole Nov 15, 2026
a000b35
Rust PG server: a USING list that repeats a name is refused with 4270…
jdrumgoole Nov 15, 2026
bc550c2
Rust PG server: a PL/pgSQL FETCH / MOVE / CLOSE reads a refcursor par…
jdrumgoole Nov 15, 2026
0041f36
Rust PG server: the statement after DDL patches the catalogs from a s…
jdrumgoole Nov 15, 2026
b640f5c
Rust PG server backlog: RP-928 measured after the catalog key log; it…
jdrumgoole Nov 15, 2026
c17d30a
Rust PG server: a grouped query's undefined column is 42703 (RP-874)
jdrumgoole Nov 15, 2026
6aef8ca
Rust PG server: a domain's CHECK and a partial index's WHERE must be …
jdrumgoole Nov 15, 2026
8ea3d8e
x
jdrumgoole Nov 15, 2026
7f7c9c6
Rust PG server: a PL/pgSQL expression's analysis error carries its ex…
jdrumgoole Nov 15, 2026
b27b66d
Rust PG server: CREATE TABLE finds its own relation oid by key, not a…
jdrumgoole Nov 15, 2026
8e0a6b4
Rust PG server backlog: RP-911 profiled after RP-928's keyed relation…
jdrumgoole Nov 15, 2026
04b99bd
Rust PG server: a row beside a non-row resolves its operands' names f…
jdrumgoole Nov 15, 2026
5780db5
Rust PG server: a row against a subquery resolves each pair's operato…
jdrumgoole Nov 15, 2026
ab4ebfe
Rust PG server: a table an aliased JOIN hides can be named again besi…
jdrumgoole Nov 15, 2026
8275131
Rust PG server: a row comparison takes the text pattern operators (RP…
jdrumgoole Nov 15, 2026
7ed61b7
Rust PG server: a CHECK may name tableoid (RP-859; RP-974, RP-975 filed)
jdrumgoole Nov 15, 2026
f7b0932
Rust PG server: the record_image operators compare records by their b…
jdrumgoole Nov 15, 2026
cba5727
Rust PG server: information_schema's text columns are collated "C" (R…
jdrumgoole Nov 15, 2026
16b86a1
Rust PG server: a WITH's duplicate name, alias count, forward referen…
jdrumgoole Nov 15, 2026
02e5d80
Rust PG server: a subquery in an EXISTS subquery's select list is ana…
jdrumgoole Nov 15, 2026
8a325fd
Rust PG server: a LATERAL subquery reads a field of a left item's who…
jdrumgoole Nov 15, 2026
1956249
Rust PG server: a temporary row type hides a built-in type of its nam…
jdrumgoole Nov 15, 2026
df60c5a
Rust PG server: a FROM-subquery sees only its left siblings, and none…
jdrumgoole Nov 15, 2026
869dffb
Rust PG server: a portal that fails as it runs takes its number (RP-907)
jdrumgoole Nov 15, 2026
1574dce
Rust PG server: CREATE TABLE IF NOT EXISTS over an existing table che…
jdrumgoole Nov 15, 2026
a33468d
Rust PG server: a LATERAL subquery names and expands its left items' …
jdrumgoole Nov 15, 2026
7a61f6d
Rust PG server: a missing column's hint searches a function in FROM (…
jdrumgoole Nov 15, 2026
6ae4e57
Rust PG server: an overlong varchar assignment over a constant is fra…
jdrumgoole Nov 15, 2026
20b1557
Rust PG server: an unread WITH item's literal is read as its type (RP…
jdrumgoole Nov 15, 2026
8fb8df5
Rust PG server: pg_get_viewdef prints the pg_catalog system views (RP…
jdrumgoole Nov 15, 2026
9877026
Rust PG server: a subquery's unread WITH item under a FROM and an EXI…
jdrumgoole Nov 15, 2026
4b133e3
Rust PG server: EXPLAIN ANALYZE counts a LEFT join filter over a char…
jdrumgoole Nov 15, 2026
141e409
Rust PG server: EXPLAIN ANALYZE counts every join of a nested-loop ch…
jdrumgoole Nov 15, 2026
eaeb4ee
Rust PG server: regclassin reports a missing schema, at the literal (…
jdrumgoole Nov 15, 2026
a2f8f40
Rust PG server: EXPLAIN ANALYZE counts a nested-loop chain into a Mem…
jdrumgoole Nov 15, 2026
bab35a5
Rust PG server: a missing GROUP BY column is reported at its referenc…
jdrumgoole Nov 15, 2026
99e53a2
Rust PG server: EXPLAIN ANALYZE counts a nested-loop chain over a Has…
jdrumgoole Nov 15, 2026
d4911ab
Rust PG server: a GROUP BY or DISTINCT ON constant is analysed before…
jdrumgoole Nov 15, 2026
d81382b
backlog: size RP-384 and RP-390 from their code paths
jdrumgoole Nov 15, 2026
f059bcc
Rust PG server: a nested loop's Join Filter is flattened and cheapest…
jdrumgoole Nov 15, 2026
f177a10
Rust PG server: a DO block's compile error points at its token (RP-492)
jdrumgoole Nov 15, 2026
48abef7
Rust PG server: a joined scan's Filter is in PostgreSQL's qual order …
jdrumgoole Nov 15, 2026
5c33fe4
Rust PG server: a wrong-arity aggregate call is 42883 naming its argu…
jdrumgoole Nov 15, 2026
d4c2baa
Rust PG server: a LEFT join the WHERE reduces to inner pushes its one…
jdrumgoole Nov 15, 2026
362c978
tests: a missing schema in a regclass literal is 3F000, as PostgreSQL…
jdrumgoole Nov 15, 2026
35e6807
Rust PG server: a write's unknown or ambiguous column is at the name …
jdrumgoole Nov 15, 2026
7462d80
Rust PG server: a repeated WHERE equality is planned once (RP-402; RP…
jdrumgoole Nov 15, 2026
cb63393
backlog: size RP-920 from the RP-903 bundle
jdrumgoole Nov 15, 2026
57eeb7b
Rust PG server: EXPLAIN resolves names before it reads its options (R…
jdrumgoole Nov 15, 2026
6bd7b8b
Rust PG server: a nested loop's Join Filter lists its clauses in join…
jdrumgoole Nov 15, 2026
1b489c4
Rust PG server: ALTER TABLE analyses USING and generated expressions …
jdrumgoole Nov 15, 2026
932e3a3
Rust PG server: a scan's Filter orders an expression's conjunct by it…
jdrumgoole Nov 15, 2026
ff87ebb
Rust PG server: a real meets a double uncast, and a coerced constant …
jdrumgoole Nov 15, 2026
0cfc566
Rust PG server: a GROUP BY position inside a grouping set is a positi…
jdrumgoole Nov 15, 2026
079bf74
Rust PG server: a NOT is pushed onto the clause it negates (RP-412)
jdrumgoole Nov 15, 2026
5b0c367
Rust PG server: EXPLAIN folds a COALESCE's constants and a LIKE's ESC…
jdrumgoole Nov 15, 2026
a734591
Rust PG server: a cast literal's bad input fails before the operator …
jdrumgoole Nov 15, 2026
3ce983f
Rust PG server: a PREPARE's error is positioned in the PREPARE's text…
jdrumgoole Nov 15, 2026
d5e53ab
Rust PG server: a join whose WHERE pins a column to two constants is …
jdrumgoole Nov 15, 2026
a653254
Rust PG server: a star over a FROM subquery expands in a grouped quer…
jdrumgoole Nov 15, 2026
af24525
Rust PG server: quals over one table of a join nested in an outer joi…
jdrumgoole Nov 15, 2026
e642282
Rust PG server: a parameterized inner index scan checks its table's o…
jdrumgoole Nov 15, 2026
e8f72e2
Rust PG server backlog: size RP-908 (no anti join exists); file RP-979
jdrumgoole Nov 15, 2026
5242aad
Rust PG server: a DROP or ALTER FUNCTION signature's %TYPE is the col…
jdrumgoole Nov 15, 2026
a60b7ea
Rust PG server: user schemas are published before the functions, so r…
jdrumgoole Nov 15, 2026
4cfda69
Rust PG server: a LEFT join's contradictory ON constants gate its nul…
jdrumgoole Nov 15, 2026
a04cd11
Rust PG server: a PL/pgSQL json literal's input error carries its ass…
jdrumgoole Nov 15, 2026
2ca9938
Rust PG server: a PL/pgSQL compile error keeps its SQLSTATE and posit…
jdrumgoole Nov 15, 2026
e2c113c
Rust PG server: a LEFT join whose WHERE keeps only unmatched rows exp…
jdrumgoole Nov 15, 2026
a5a12a7
backlog: RP-401 sized -- the general merge paths ignore mergejoinscansel
jdrumgoole Nov 15, 2026
799bff2
Rust PG server: pg_get_viewdef prints a view over a record-returning …
jdrumgoole Nov 15, 2026
ee6dda4
CI: run the catalog key-log and relation-name PG test files in pg-oracle
jdrumgoole Nov 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
5 changes: 5 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1831,6 +1831,11 @@ jobs:
# 3-minute test on its own: no shard of this lane gets below that.
if [ "${{ matrix.shard }}" = "server" ]; then
files="tests/test_rust_pgserver_slice.py"
# The catalog's storage key log (the statement after DDL patches
# the catalogs from it) and relation-name resolution across
# schemas. Binary only.
files="$files tests/test_rust_pg_catalog_key_log.py"
files="$files tests/test_rust_pgserver_relation_names.py"
else
files=""
# The DIFFERENTIAL suite, whole: 836 cases against the PostgreSQL 14
Expand Down
21 changes: 21 additions & 0 deletions changelog.d/pg-left-join-anti-join.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
### EXPLAIN shows the anti join a LEFT join's IS NULL makes

A LEFT join whose WHERE keeps only the rows with no match -- `left join e2
on e.a = e2.a where e2.a is null` -- is an anti join to PostgreSQL's
planner: it stops looking at the first match and drops the `IS NULL`
check. The Rust PostgreSQL server now explains it that way, as a `Hash
Anti Join` over an equality or a `Nested Loop Anti Join` over a `<>`, with
PostgreSQL's own costs and row estimate, where it printed a `Left Join`
with a `Filter` above it and zero costs. The rows returned were already
right.

#### Fixed

- EXPLAIN of a LEFT join whose WHERE tests the nullable side's join key
`IS NULL` prints PostgreSQL 15's `Hash Anti Join` / `Nested Loop Anti
Join`, priced by `final_cost_hashjoin` / `final_cost_nestloop`'s
JOIN_ANTI arms over `compute_semi_anti_join_factors`
(`eqjoinsel_semi`, and `neqjoinsel` for JOIN_ANTI).
Fixes RP-908 (tests/backlog/test_rp_explain_costs.py::test_plan[a left join where the nullable side is null is an anti join], tests/backlog/test_rp_explain_costs.py::test_plan[a left join on an equality where the nullable side is null is an anti join])
- Filed RP-980 (NOT EXISTS as a Hash Anti Join) and RP-981 (an anti join on
an expression key).
13 changes: 13 additions & 0 deletions changelog.d/pg-prepare-error-position.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
### A PREPARE's error points into the PREPARE

When the query inside a `PREPARE` fails analysis, the Rust PostgreSQL server
now reports the error's position in the statement the client sent, as
PostgreSQL does. It planned a deparsed copy of the query, so the position was
an offset into that copy: `prepare p1(int) as select 12 = true` pointed at the
`(` of `p1(int)` rather than at the `=`.

#### Fixed

- Rust PG server: an analysis error in a `PREPARE`'s query is positioned in
the `PREPARE` text.
Fixes RP-593 (tests/backlog/test_rp_found_rp530.py::test_prepare_error_position)
15 changes: 15 additions & 0 deletions changelog.d/rp-390-chain-into-memoize.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
### EXPLAIN ANALYZE counts a nested-loop chain that ends in a Memoize

When a chain of nested loops ended in a Memoize over a parameterized index
scan, EXPLAIN ANALYZE printed the lower join, its scans and the Memoize
bare. They now carry the counts PostgreSQL 15.19 prints: the lower join's
rows and its Join Filter's removed pairs, the Memoize's rows per loop and
its `Hits / Misses / Memory Usage` line, and the index scan's loops, each
taken from the rows the lower join actually produced.

#### Fixed

- EXPLAIN ANALYZE of `z join z z2 on z.a < z2.a join kz on z2.a < kz.a`
annotates every node below the top Nested Loop.

Fixes RP-390, part (tests/backlog/test_rp_explain_costs.py::test_plan[analyze of a nested loop of three relations into a memoized index])
12 changes: 12 additions & 0 deletions changelog.d/rp-390-chain-over-hash-join.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
### EXPLAIN ANALYZE counts a nested-loop chain over a Hash Join

A nested loop whose outer side is a lower Hash Join now prints that join's
counts as PostgreSQL does: the Hash Join's rows, each scan's, the Hash's
buckets, batches and memory, and the top join's rows removed by its filter.
They printed bare. The lower join's hash table is sized from its inner scan
priced on its own, as the plan above it carries no estimate.

#### Fixed

- EXPLAIN ANALYZE annotates a Nested Loop over a Hash Join (RP-390, part).
Fixes RP-390, part (tests/backlog/test_rp_explain_costs.py::test_a_chains_lower_hash_join_is_counted)
15 changes: 15 additions & 0 deletions changelog.d/rp-390-char-n-left-join-filter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
### EXPLAIN ANALYZE counts a LEFT join filter over a char(n) column

A LEFT Nested Loop whose Join Filter reads a `char(n)` column now prints
its children's actual rows and loops, as PostgreSQL 15.19 does. The
per-pair evaluation of the filter used to refuse any `char(n)` column, so
the scans below the join printed bare; it now reads the column
blank-padded to its declared width, as PostgreSQL stores it.

#### Fixed

- `explain (analyze) select * from cc left join s on cc.b < s.c` over a
`char(4)` column annotates `Seq Scan on cc` and the Materialize below
the join.

Fixes RP-390, part (tests/backlog/test_rp_explain_costs.py::test_plan[analyze of a left nested loop over a char(n) join filter]; crates/secantus-pgplan/src/tests.rs::a_pair_expression_pads_a_char_n_column)
16 changes: 16 additions & 0 deletions changelog.d/rp-390-nested-loop-chain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
### EXPLAIN ANALYZE counts every join of a nested-loop chain

A chain of three or more tables joined by plain Nested Loops now prints
actual rows, loops and `Rows Removed by Join Filter` at every level, as
PostgreSQL 15.19 does; before, only the top join was counted and the joins
below it printed bare. Each lower join's rows are counted by running its
Join Filter over its pairs, and the join above reads them as its outer
side.

#### Fixed

- `explain (analyze) select * from z join z z2 on z.a < z2.a join z z3 on
z2.a < z3.a` prints the lower Nested Loop's `(actual rows=190 loops=1)`
and its Materialize's `loops=20`.

Fixes RP-390, part (tests/backlog/test_rp_explain_costs.py::test_plan[analyze of a nested loop of three relations]; crates/secantus-pgplan/src/tests.rs::a_relations_expression_reads_each_aliass_row)
22 changes: 22 additions & 0 deletions changelog.d/rp-393-397-quals-through-nested-joins.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
### A filter inside a join nested in an outer join runs in its table's scan

On the Rust PostgreSQL server, a condition that reads one table of an inner
join nested inside a LEFT or RIGHT join now runs in that table's scan, as
PostgreSQL's `distribute_qual_to_rels` places it: both the inner join's own
one-table ON conjuncts and the outer join's ON conjuncts over one table of
the inner join it null-extends. EXPLAIN prints them as that scan's
`Filter`, not as a `Join Filter`. The rows returned are unchanged.

#### Fixed

- An inner join under an outer join runs its one-table ON conjuncts in
their tables' scans (RP-393).
- A LEFT or RIGHT join's ON conjunct over one table of an inner join on its
nullable side runs in that table's scan (RP-397). Not through a lower
outer join's nullable side, where PostgreSQL keeps it at the join.

The plan around them, its join order and method and its estimates, is filed
as RP-978.

Fixes RP-393 (tests/backlog/test_rp_explain_costs.py::test_plan[an inner join's one-table ON conjunct under an outer join]).
Fixes RP-397 (tests/backlog/test_rp_explain_costs.py::test_plan[a left join's ON conjunct over the hashed table of an inner join]).
13 changes: 13 additions & 0 deletions changelog.d/rp-398-joined-scan-filter-order.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
### A joined scan's Filter is in PostgreSQL's qual order

A scan under a join now orders its own Filter the way a lone scan already
did: an equality to a constant after the other conjuncts, then by cost.
That holds too for an ON conjunct over a LEFT join's nullable side once the
WHERE reduces the join to an inner one, which now sorts among that scan's
WHERE conjuncts.

#### Fixed

- `select * from s join e on s.a < e.a where e.b = 'v3' and e.a > 5`
prints `Filter: ((a > 5) AND (b = 'v3'::text))` on `e`'s scan.
Fixes RP-398 (tests/backlog/test_rp_explain_costs.py::test_plan[a joined scan's filter puts its equality to a constant last], tests/backlog/test_rp_explain_costs.py::test_plan[a reduced left join's scan filter puts its equality to a constant last])
14 changes: 14 additions & 0 deletions changelog.d/rp-400-join-contradictory-class.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
### A join whose WHERE pins one column to two constants plans no scan

On the Rust PostgreSQL server, EXPLAIN of an inner join whose WHERE equates
one column to two different constants now prints what PostgreSQL 15.19 does:
a bare `Result` with `One-Time Filter: false`. The column's equivalence
class is contradictory, so its relation is empty, and so is the join.

#### Fixed

- EXPLAIN of an inner (or comma) join whose WHERE equates a column to two
distinct constants is a `Result` with `One-Time Filter: false`, not the
join's scans. The same in a LEFT join's ON is still open (RP-400).

Fixes RP-400 in part (tests/backlog/test_rp_explain_costs.py::test_plan[a join whose where equates a column to two constants], tests/backlog/test_rp_explain_costs.py::test_plan[a comma join of aliases whose where equates a column to two constants]).
8 changes: 8 additions & 0 deletions changelog.d/rp-400-left-join-on-const-gate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
### A LEFT join's contradictory ON constants gate its nullable side

When a LEFT join's ON equates a column of the nullable side to two different constants, PostgreSQL's equivalence class keeps the first as that side's filter and turns the rest into a `false` pseudoconstant on a `Result` over its scan. EXPLAIN on the Rust PostgreSQL server now prints the same gated plan instead of a scan filtering on both constants.

#### Fixed

- EXPLAIN of `select * from s left join e on s.a < e.a and e.b = 'v1' and e.b = 'v2'` prints a `Result` with `One-Time Filter: false` over `e`'s scan, filtered by `b = 'v1'` only.
Fixes RP-400 (tests/backlog/test_rp_explain_costs.py::test_plan[a left join's on equating its nullable column to two constants])
14 changes: 14 additions & 0 deletions changelog.d/rp-402-repeated-equality.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
### A repeated WHERE equality is planned once

PostgreSQL's `process_equivalence` finds an equality that repeats an
earlier one redundant, so `where e.b = k.b and e.b = k.b` is one hash
clause and a repeated `e.a = 5` one scan filter, each priced once. The Rust
PostgreSQL server now drops such a repeat before it plans a join, so its
clauses, estimates and costs match.

#### Fixed

- A repeated join equality is one `Hash Cond`, estimated once
(`rows=100000`, not `10000`), and a repeated `column = constant` one
`Filter`.
Fixes RP-402 (tests/backlog/test_rp_explain_costs.py::test_plan[comma join with a repeated join equality], tests/backlog/test_rp_explain_costs.py::test_plan[comma join with a repeated constant equality])
8 changes: 8 additions & 0 deletions changelog.d/rp-406-scan-filter-cost-order.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
### A scan's Filter lists an expression's conjunct after a cheaper one

PostgreSQL orders a scan's quals cheapest first, pricing each by the operators it evaluates: an expression such as `a + 1 > 996`, a NOT BETWEEN's OR, or a column coerced by a call (`(a)::numeric`, an integer's cast to text) comes after a plain comparison. The Rust PostgreSQL server now prices those clauses the same way, alone or as a join's scan, where before it printed them in the order written.

#### Fixed

- EXPLAIN orders a scan Filter's expression, OR and coerced-column conjuncts by `cost_qual_eval`'s operator cost.
Fixes RP-406 (tests/backlog/test_rp_explain_costs.py::test_plan[a scan filter's conjuncts in cost order], tests/backlog/test_rp_explain_costs.py::test_plan[a scan filter's or after a cheaper conjunct])
8 changes: 8 additions & 0 deletions changelog.d/rp-407-coerced-constant-folded.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
### EXPLAIN folds a constant coerced to its operator's type

PostgreSQL's planner folds the implicit coercion of a constant before EXPLAIN prints it: `a::numeric > 4` filters on `'4'::numeric`, and a constant made double precision prints in `float8out`'s form (`'1e-05'::double precision`). The Rust PostgreSQL server now prints the folded constant, alone or as a join's scan, and places an expression equal to such a constant after the scan's other quals, as its equivalence class is.

#### Fixed

- EXPLAIN prints a constant coerced to an integer, numeric or double precision operator folded, and orders an expression's equality with a constant last.
Fixes RP-407 (tests/backlog/test_rp_explain_costs.py::test_plan[a scan filter's coerced constant is folded])
8 changes: 8 additions & 0 deletions changelog.d/rp-412-not-pushed-down.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
### A NOT is pushed onto the clause it negates

PostgreSQL's planner never keeps a NOT over something it can negate: `negate_clause` applies De Morgan to an AND or an OR, turns a BETWEEN into the OR of its negated comparisons, and replaces a comparison, LIKE, regex match or IN list by its negator and a null test by its opposite. The AND that comes out joins the scan's other quals, where it can become index conditions. The Rust PostgreSQL server's EXPLAIN now plans a NOT the same way, including one whose pushed-down AND meets a NULL arm and becomes a constant-false `One-Time Filter`.

#### Fixed

- EXPLAIN pushes a WHERE's NOT onto its arms and operators, as `negate_clause` does.
Fixes RP-412 (tests/backlog/test_rp_explain_costs.py::test_plan[a not over an or is the and of its negated arms], tests/backlog/test_rp_explain_costs.py::test_plan[a not over a between is the or of its negators], tests/backlog/test_rp_explain_costs.py::test_plan[a not over an or with a null arm is a false one-time filter])
12 changes: 12 additions & 0 deletions changelog.d/rp-413-function-in-from-hint.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
### A missing column's hint searches a function in FROM

A misspelt column in a query with a function in FROM now gets PostgreSQL's
`Perhaps you meant to reference the column ...` hint: the search for the
closest column reads the function's columns from its result type, as
PostgreSQL's range table holds them, instead of giving up.

#### Fixed

- Rust PG server: `select * from generate_series(1,2) g where gg = 1` hints
`"g.g"`, and a function beside a table no longer suppresses the table's
hint. Fixes RP-413 (tests/backlog/test_rp_column_errors.py::test_function_in_from_hint)
8 changes: 8 additions & 0 deletions changelog.d/rp-421-coalesce-folded.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
### A COALESCE's constant arguments are folded

PostgreSQL's planner simplifies a COALESCE before it plans a scan: NULL constant arguments are dropped, the list ends at the first non-NULL constant, and a COALESCE whose first argument is a constant is that constant. `coalesce(null, a) = 1` filters on `COALESCE(a) = 1`, and `coalesce(1, a) = 1` folds to true, a scan with no filter at all. The Rust PostgreSQL server's EXPLAIN now plans it the same way.

#### Fixed

- EXPLAIN folds a COALESCE's constant arguments as `eval_const_expressions` does, and a comparison of two literals that folding leaves.
Fixes RP-421 (tests/backlog/test_rp_explain_costs.py::test_plan[a coalesce drops its null arguments], tests/backlog/test_rp_explain_costs.py::test_plan[a coalesce of a constant first folds])
14 changes: 14 additions & 0 deletions changelog.d/rp-428-explain-names-before-options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
### EXPLAIN resolves names before it reads its options

PostgreSQL analyses the statement under EXPLAIN before it reads a single
option, so `explain (bogus) select * from nosuch` reports the missing
relation, not the unknown option. The Rust PostgreSQL server now does the
same for a missing relation, a missing function and a bad literal, while an
error that only planning raises, such as a constant division by zero, still
stays behind the option's.

#### Fixed

- EXPLAIN with a bad option reported 42601 ahead of a 42P01, 42883 or 22P02
its statement raises in parse analysis.
Fixes RP-428 (tests/backlog/test_rp_analysis_order.py::test_explain_resolves_names_before_options)
11 changes: 11 additions & 0 deletions changelog.d/rp-429-target-list-before-where.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
### A missing output column is reported before the WHERE's errors

PostgreSQL analyses a SELECT's target list before its WHERE clause, so
`select nosuch from e where 'x'` reports the missing column, not the
WHERE's bad boolean. The Rust PostgreSQL server now does the same.

#### Fixed

- Rust PG server: a missing column in the target list is 42703 ahead of a
22P02, 42804 or 42883 raised by the WHERE clause.
Fixes RP-429 (tests/backlog/test_rp_where_types.py::test_target_list_is_analysed_before_where)
8 changes: 8 additions & 0 deletions changelog.d/rp-442-cast-literal-before-operator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
### A cast literal's bad input is reported before the operator around it

PostgreSQL reads a constant cast such as `'x'::int` as the cast is analysed, before it looks up the operator the cast sits under. So `select 'x'::int = true` is the integer input error, not "operator does not exist: integer = boolean". The Rust PostgreSQL server now reports the same error at the same position, in an operator and in a simple CASE's argument.

#### Fixed

- Rust PG server: `select 'x'::int = true`, `select 1 = 'x'::uuid`, `select '2020-13-01'::date = 1` and `case 'x'::uuid when 1` are 22P02 / 22008 at the literal, not 42883.
Fixes RP-442 (tests/backlog/test_rp_fromless_analysis.py::test_cast_literal_input_error_before_operator)
17 changes: 17 additions & 0 deletions changelog.d/rp-447-operator-after-reference.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
### An operator's type error no longer jumps ahead of a missing column to its left

In a SELECT with no FROM, `select nosuch + (1 + true)` answered 42883
`operator does not exist: integer + boolean`, because the operator-type
check ran over the whole statement before any column reference was looked
at. PostgreSQL analyses an operator's operands before it looks the operator
up, so the missing column to its left fails first: 42703 `column "nosuch"
does not exist` at 8. The FROM-less reference walk now resolves each
operator with no reference under it as it meets it, and runs ahead of the
operator-type check, so the first error in parse-analysis order is the one
reported, whichever side of the operator it is on.

#### Fixed

- Rust PG server: in a FROM-less SELECT, a missing column to an operator's
left is reported before the operator's 42883.
Fixes RP-447 (tests/backlog/test_rp_fromless_analysis.py::test_missing_column_before_operator_to_its_right)
15 changes: 15 additions & 0 deletions changelog.d/rp-459-exists-nested-subquery.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
### A subquery in an EXISTS subquery's select list is analysed

PostgreSQL parse-analyses the whole select list of an EXISTS subquery before
its planner throws the list away, so a subquery inside it that names a
column or table that does not exist is an error, while one that would only
fail when run is never run. The select list's subqueries are now planned
without running before the list is dropped, and the errors parse analysis
raises are the statement's.

#### Fixed

- Rust PG server: `select exists (select (select nosuch))` is 42703 at 31
and `select exists (select 1 + (select nosuch))` 42703 at 35 (each
answered `true`); `select exists (select (select 1/0))` stays `true`.
- Fixes RP-459 (tests/backlog/test_rp_fromless_analysis.py::test_exists_target_subquery_is_analysed)
19 changes: 19 additions & 0 deletions changelog.d/rp-460-from-subquery-sibling-hint.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
### A FROM-subquery sees its siblings as PostgreSQL's range table has them

When PostgreSQL analyses a FROM-subquery, only the FROM items to its left are
in the range table, and unless the subquery is LATERAL none of them is
visible. A subquery inside one that names a sibling is therefore told the
entry cannot be referenced from there, never offered an alias it could not
use, and a name of an item to its right is simply a missing FROM-clause
entry.

#### Fixed

- Rust PG server: `select * from fe x, (select (select fe.a)) s` and the
`exists (select fe.a)` form are 42P01 with the hint `There is an entry for
table "x", but it cannot be referenced from this part of the query.` (the
hint offered the alias `x`); `select * from (select (select y.a)) s, fe y`
is `missing FROM-clause entry for table "y"` (an "invalid reference" with
the cannot-be-referenced hint); a FROM-subquery on a join's right side
sees its left items the same way.
- Fixes RP-460 (tests/backlog/test_rp_subquery_scope.py::test_from_subquery_sibling_is_not_visible)
15 changes: 15 additions & 0 deletions changelog.d/rp-469-hidden-join-name-reuse.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
### A table an aliased JOIN hides can be named again beside it

An aliased JOIN hides the names of the tables inside it, so PostgreSQL lets
another FROM item reuse one: `select * from (fs join fe using (a)) as j, fs`
reads `fs` twice, once inside `j` and once on its own. The Rust PostgreSQL
server refused this with "not supported", because a joined row's columns
were keyed by the table's name and the two `fs` would have shared keys. Each
table leaf of such a name is now keyed apart, so the query answers as
PostgreSQL does, whole-row references (`select fs from ...`) included.

#### Fixed

- A FROM item named as a table an aliased JOIN hides is no longer 0A000.
Fixes RP-469 (tests/backlog/test_rp_subquery_scope.py::test_hidden_join_name_reused)
- Filed RP-972: a subquery or function reusing such a name is still refused.
Loading
Loading