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
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'
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
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);
{
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)