1
0
mirror of https://github.com/postgres/postgres.git synced 2025-12-21 05:21:08 +03:00

Ensure first ModifyTable rel initialized if all are pruned

Commit cbc127917e introduced tracking of unpruned relids to avoid
processing pruned relations, and changed ExecInitModifyTable() to
initialize only unpruned result relations. As a result, MERGE
statements that prune all target partitions can now lead to crashes
or incorrect behavior during execution.

The crash occurs because some executor code paths rely on
ModifyTableState.resultRelInfo[0] being present and initialized,
even when no result relations remain after pruning. For example,
ExecMerge() and ExecMergeNotMatched() use the first resultRelInfo
to determine the appropriate action. Similarly,
ExecInitPartitionInfo() assumes that at least one result relation
exists.

To preserve these assumptions, ExecInitModifyTable() now includes the
first result relation in the initialized result relation list if all
result relations for that ModifyTable were pruned. To enable that,
ExecDoInitialPruning() ensures the first relation is locked if it was
pruned and locking is necessary.

To support this exception to the pruning logic, PlannedStmt now
includes a list of RT indexes identifying the first result relation
of each ModifyTable node in the plan. This allows
ExecDoInitialPruning() to check whether each such relation was
pruned and, if so, lock it if necessary.

Bug: #18830
Reported-by: Robins Tharakan <tharakan@gmail.com>
Diagnozed-by: Tender Wang <tndrwang@gmail.com>
Diagnozed-by: Dean Rasheed <dean.a.rasheed@gmail.com>
Co-authored-by: Dean Rasheed <dean.a.rasheed@gmail.com>
Reviewed-by: Tender Wang <tndrwang@gmail.com>
Reviewed-by: Dean Rasheed <dean.a.rasheed@gmail.com>
Discussion: https://postgr.es/m/18830-1f31ea1dc930d444%40postgresql.org
This commit is contained in:
Amit Langote
2025-03-19 12:14:24 +09:00
parent 06fb5612c9
commit 28317de723
13 changed files with 214 additions and 18 deletions

View File

@@ -4662,6 +4662,88 @@ table part_abc_view;
2 | c | t
(1 row)
-- MERGE ... INSERT when all pruned from MERGE source.
begin;
explain (costs off)
merge into part_abc_view pt
using (select stable_one() + 1 as pid) as q join part_abc_2 pt2 on (q.pid = pt2.a) on pt.a = stable_one() + 2
when not matched then insert values (1, 'd', false) returning pt.a;
QUERY PLAN
------------------------------------------------
Merge on part_abc
-> Nested Loop Left Join
-> Seq Scan on part_abc_2 pt2
Filter: ((stable_one() + 1) = a)
-> Materialize
-> Append
Subplans Removed: 2
(7 rows)
merge into part_abc_view pt
using (select stable_one() + 1 as pid) as q join part_abc_2 pt2 on (q.pid = pt2.a) on pt.a = stable_one() + 2
when not matched then insert values (1, 'd', false) returning pt.a;
a
---
1
(1 row)
table part_abc_view;
a | b | c
---+---+---
1 | d | f
2 | c | t
(2 rows)
rollback;
-- A case with multiple ModifyTable nodes.
begin;
create table part_abc_log (action text, a int, b text, c bool);
explain (costs off)
with t as (
merge into part_abc_view pt
using (select stable_one() + 1 as pid) as q join part_abc_2 pt2 on (q.pid = pt2.a) on pt.a = stable_one() + 2
when not matched then insert values (1, 'd', false) returning merge_action(), pt.*
)
insert into part_abc_log select * from t returning *;
QUERY PLAN
--------------------------------------------------------
Insert on part_abc_log
CTE t
-> Merge on part_abc
-> Nested Loop Left Join
-> Seq Scan on part_abc_2 pt2
Filter: ((stable_one() + 1) = a)
-> Materialize
-> Append
Subplans Removed: 2
-> CTE Scan on t
(10 rows)
with t as (
merge into part_abc_view pt
using (select stable_one() + 1 as pid) as q join part_abc_2 pt2 on (q.pid = pt2.a) on pt.a = stable_one() + 2
when not matched then insert values (1, 'd', false) returning merge_action(), pt.*
)
insert into part_abc_log select * from t returning *;
action | a | b | c
--------+---+---+---
INSERT | 1 | d | f
(1 row)
table part_abc_view;
a | b | c
---+---+---
1 | d | f
2 | c | t
(2 rows)
table part_abc_log;
action | a | b | c
--------+---+---+---
INSERT | 1 | d | f
(1 row)
rollback;
-- A case with nested MergeAppend with its own PartitionPruneInfo.
create index on part_abc (a);
alter table part_abc add d int;

View File

@@ -1401,6 +1401,38 @@ using (select stable_one() + 2 as pid) as q join part_abc_1 pt1 on (q.pid = pt1.
when matched then delete returning pt.a;
table part_abc_view;
-- MERGE ... INSERT when all pruned from MERGE source.
begin;
explain (costs off)
merge into part_abc_view pt
using (select stable_one() + 1 as pid) as q join part_abc_2 pt2 on (q.pid = pt2.a) on pt.a = stable_one() + 2
when not matched then insert values (1, 'd', false) returning pt.a;
merge into part_abc_view pt
using (select stable_one() + 1 as pid) as q join part_abc_2 pt2 on (q.pid = pt2.a) on pt.a = stable_one() + 2
when not matched then insert values (1, 'd', false) returning pt.a;
table part_abc_view;
rollback;
-- A case with multiple ModifyTable nodes.
begin;
create table part_abc_log (action text, a int, b text, c bool);
explain (costs off)
with t as (
merge into part_abc_view pt
using (select stable_one() + 1 as pid) as q join part_abc_2 pt2 on (q.pid = pt2.a) on pt.a = stable_one() + 2
when not matched then insert values (1, 'd', false) returning merge_action(), pt.*
)
insert into part_abc_log select * from t returning *;
with t as (
merge into part_abc_view pt
using (select stable_one() + 1 as pid) as q join part_abc_2 pt2 on (q.pid = pt2.a) on pt.a = stable_one() + 2
when not matched then insert values (1, 'd', false) returning merge_action(), pt.*
)
insert into part_abc_log select * from t returning *;
table part_abc_view;
table part_abc_log;
rollback;
-- A case with nested MergeAppend with its own PartitionPruneInfo.
create index on part_abc (a);
alter table part_abc add d int;