]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Fix bogus ctid requirement for dummy-root partitioned targets
authorAmit Langote <amitlan@postgresql.org>
Fri, 23 Jan 2026 01:20:59 +0000 (10:20 +0900)
committerAmit Langote <amitlan@postgresql.org>
Fri, 23 Jan 2026 01:23:22 +0000 (10:23 +0900)
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

contrib/file_fdw/expected/file_fdw.out
contrib/file_fdw/sql/file_fdw.sql
src/backend/executor/nodeModifyTable.c

index 4a31613fa2910d585bc0d666cf18dea4431d3496..25b79479da4c50f6ebaaed7ef549dc62471cac12 100644 (file)
@@ -415,6 +415,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'
index e91b9799c4293e46e4268d8ccddeaa3fff8917c6..c5567ea82ea7da3fd5f90a1daffe6797c46a99c9 100644 (file)
@@ -226,6 +226,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
index 569f5f7d6ee5ee1f00b9bbb238101a15bf64b6af..4719cc9e840c616a013ce65483166d8144ec5131 100644 (file)
@@ -4167,8 +4167,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);
@@ -4595,7 +4599,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)