1
0
mirror of https://github.com/postgres/postgres.git synced 2026-01-26 09:41:40 +03:00

Fix bogus ctid requirement for dummy-root partitioned targets

ExecInitModifyTable() unconditionally required a ctid junk column even
when the target was a partitioned table. This led to spurious "could
not find junk ctid column" errors when all children were excluded and
only the dummy root result relation remained.

A partitioned table only appears in the result relations list when all
leaf partitions have been pruned, leaving the dummy root as the sole
entry. Assert this invariant (nrels == 1) and skip the ctid requirement.
Also adjust ExecModifyTable() to tolerate invalid ri_RowIdAttNo for
partitioned tables, which is safe since no rows will be processed in
this case.

Bug: #19099
Reported-by: Alexander Lakhin <exclusion@gmail.com>
Author: Amit Langote <amitlangote09@gmail.com>
Reviewed-by: Tender Wang <tndrwang@gmail.com>
Reviewed-by: Kirill Reshke <reshkekirill@gmail.com>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
Discussion: https://postgr.es/m/19099-e05dcfa022fe553d%40postgresql.org
Backpatch-through: 14
This commit is contained in:
Amit Langote
2026-01-23 10:17:43 +09:00
parent 4b760a181a
commit f9a468c664
3 changed files with 49 additions and 3 deletions

View File

@@ -482,6 +482,21 @@ SELECT tableoid::regclass, * FROM p2;
p2 | 2 | xyzzy
(3 rows)
-- Test DELETE/UPDATE/MERGE on a partitioned table when all partitions
-- are excluded and only the dummy root result relation remains. The
-- operation is a no-op but should not fail regardless of whether the
-- foreign child was processed (pruning off) or not (pruning on).
DROP TABLE p2;
SET enable_partition_pruning TO off;
DELETE FROM pt WHERE false;
UPDATE pt SET b = 'x' WHERE false;
MERGE INTO pt t USING (VALUES (1, 'x'::text)) AS s(a, b)
ON false WHEN MATCHED THEN UPDATE SET b = s.b;
SET enable_partition_pruning TO on;
DELETE FROM pt WHERE false;
UPDATE pt SET b = 'x' WHERE false;
MERGE INTO pt t USING (VALUES (1, 'x'::text)) AS s(a, b)
ON false WHEN MATCHED THEN UPDATE SET b = s.b;
DROP TABLE pt;
-- generated column tests
\set filename :abs_srcdir '/data/list1.csv'

View File

@@ -255,6 +255,24 @@ UPDATE pt set a = 1 where a = 2; -- ERROR
SELECT tableoid::regclass, * FROM pt;
SELECT tableoid::regclass, * FROM p1;
SELECT tableoid::regclass, * FROM p2;
-- Test DELETE/UPDATE/MERGE on a partitioned table when all partitions
-- are excluded and only the dummy root result relation remains. The
-- operation is a no-op but should not fail regardless of whether the
-- foreign child was processed (pruning off) or not (pruning on).
DROP TABLE p2;
SET enable_partition_pruning TO off;
DELETE FROM pt WHERE false;
UPDATE pt SET b = 'x' WHERE false;
MERGE INTO pt t USING (VALUES (1, 'x'::text)) AS s(a, b)
ON false WHEN MATCHED THEN UPDATE SET b = s.b;
SET enable_partition_pruning TO on;
DELETE FROM pt WHERE false;
UPDATE pt SET b = 'x' WHERE false;
MERGE INTO pt t USING (VALUES (1, 'x'::text)) AS s(a, b)
ON false WHEN MATCHED THEN UPDATE SET b = s.b;
DROP TABLE pt;
-- generated column tests

View File

@@ -4360,8 +4360,12 @@ ExecModifyTable(PlanState *pstate)
relkind == RELKIND_MATVIEW ||
relkind == RELKIND_PARTITIONED_TABLE)
{
/* ri_RowIdAttNo refers to a ctid attribute */
Assert(AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo));
/*
* ri_RowIdAttNo refers to a ctid attribute. See the comment
* in ExecInitModifyTable().
*/
Assert(AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo) ||
relkind == RELKIND_PARTITIONED_TABLE);
datum = ExecGetJunkAttribute(slot,
resultRelInfo->ri_RowIdAttNo,
&isNull);
@@ -4874,7 +4878,16 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
{
resultRelInfo->ri_RowIdAttNo =
ExecFindJunkAttributeInTlist(subplan->targetlist, "ctid");
if (!AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo))
/*
* For heap relations, a ctid junk attribute must be present.
* Partitioned tables should only appear here when all leaf
* partitions were pruned, in which case no rows can be
* produced and ctid is not needed.
*/
if (relkind == RELKIND_PARTITIONED_TABLE)
Assert(nrels == 1);
else if (!AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo))
elog(ERROR, "could not find junk ctid column");
}
else if (relkind == RELKIND_FOREIGN_TABLE)