]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Re-index ModifyTable FDW arrays when pruning result relations
authorAmit Langote <amitlan@postgresql.org>
Mon, 22 Jun 2026 08:21:45 +0000 (17:21 +0900)
committerAmit Langote <amitlan@postgresql.org>
Tue, 23 Jun 2026 12:08:50 +0000 (21:08 +0900)
ExecInitModifyTable() rebuilds the per-result-relation lists after
dropping result relations removed by initial runtime pruning.  The
re-indexing was done for withCheckOptionLists, returningLists,
updateColnosLists, mergeActionLists and mergeJoinConditions, but
fdwPrivLists and fdwDirectModifyPlans were missed.  As a result, a
kept foreign result relation could be handed the wrong fdw_private,
or ri_usesFdwDirectModify could be set from the wrong plan index,
leading to wrong behavior or a crash in BeginForeignModify() and in
the direct-modify path.

show_modifytable_info() had the same problem: it indexed the
plan-ordered node->fdwPrivLists with the post-pruning executor
position, so once initial pruning removed a result relation it
could read a different relation's fdw_private (often a NIL entry),
producing wrong EXPLAIN output or a crash.

Fix by re-indexing fdwPrivLists and fdwDirectModifyPlans alongside
the other lists, saving the re-indexed private lists in
ModifyTableState.mt_fdwPrivLists and reading from there in both
nodeModifyTable.c and explain.c.

Reported-by: Chi Zhang <798604270@qq.com>
Author: Ayush Tiwari <ayushtiwari.slg01@gmail.com>
Author: Rafia Sabih <rafia.pghackers@gmail.com>
Reviewed-by: Matheus Alcantara <matheusssilv97@gmail.com>
Reviewed-by: Etsuro Fujita <etsuro.fujita@gmail.com>
Discussion: https://postgr.es/m/19484-a3cb82c8cde3c8fa%40postgresql.org
Backpatch-through: 18

contrib/postgres_fdw/expected/postgres_fdw.out
contrib/postgres_fdw/sql/postgres_fdw.sql
src/backend/commands/explain.c
src/backend/executor/nodeModifyTable.c
src/include/nodes/execnodes.h

index f975ceff2008e8a692b74e1520ee9af3030a3f9b..3cc3c6dc3c759003c7cc2a8fa36b2d81126573e0 100644 (file)
@@ -7132,6 +7132,70 @@ RESET enable_material;
 DROP FOREIGN TABLE remt2;
 DROP TABLE loct1;
 DROP TABLE loct2;
+-- Test that direct modify and foreign modify work with runtime pruning of
+-- result relations (bug #19484)
+create table fdw_part_update (a int not null, b int) partition by list (a);
+create table fdw_part_update_p1 partition of fdw_part_update for values in (1);
+create table fdw_part_update_remote (a int not null, b int);
+create foreign table fdw_part_update_p2 partition of fdw_part_update
+    for values in (2)
+    server loopback options (table_name 'fdw_part_update_remote');
+insert into fdw_part_update_p1 values (1, 10);
+insert into fdw_part_update_remote values (2, 20);
+set plan_cache_mode = force_generic_plan;
+-- Check DirectModify case
+prepare fdw_part_upd(int) as
+    update fdw_part_update set b = b + 1 where a = $1
+    returning tableoid::regclass, a, b;
+explain (verbose, costs off)
+    execute fdw_part_upd(2);
+                                                       QUERY PLAN                                                        
+-------------------------------------------------------------------------------------------------------------------------
+ Update on public.fdw_part_update
+   Output: (fdw_part_update_1.tableoid)::regclass, fdw_part_update_1.a, fdw_part_update_1.b
+   Foreign Update on public.fdw_part_update_p2 fdw_part_update_2
+   ->  Append
+         Subplans Removed: 1
+         ->  Foreign Update on public.fdw_part_update_p2 fdw_part_update_2
+               Remote SQL: UPDATE public.fdw_part_update_remote SET b = (b + 1) WHERE ((a = $1::integer)) RETURNING a, b
+(7 rows)
+
+execute fdw_part_upd(2);
+      tableoid      | a | b  
+--------------------+---+----
+ fdw_part_update_p2 | 2 | 21
+(1 row)
+
+deallocate fdw_part_upd;
+-- Check ForeignModify case
+prepare fdw_part_upd2(int) as
+    update fdw_part_update set b = b + random()::int * 0 + 1 where a = $1
+    returning tableoid::regclass, a, b;
+explain (verbose, costs off)
+    execute fdw_part_upd2(2);
+                                                                       QUERY PLAN                                                                       
+--------------------------------------------------------------------------------------------------------------------------------------------------------
+ Update on public.fdw_part_update
+   Output: (fdw_part_update_1.tableoid)::regclass, fdw_part_update_1.a, fdw_part_update_1.b
+   Foreign Update on public.fdw_part_update_p2 fdw_part_update_2
+     Remote SQL: UPDATE public.fdw_part_update_remote SET b = $2 WHERE ctid = $1 RETURNING a, b
+   ->  Append
+         Subplans Removed: 1
+         ->  Foreign Scan on public.fdw_part_update_p2 fdw_part_update_2
+               Output: ((fdw_part_update_2.b + ((random())::integer * 0)) + 1), fdw_part_update_2.tableoid, fdw_part_update_2.ctid, fdw_part_update_2.*
+               Remote SQL: SELECT a, b, ctid FROM public.fdw_part_update_remote WHERE ((a = $1::integer)) FOR UPDATE
+(9 rows)
+
+execute fdw_part_upd2(2);
+      tableoid      | a | b  
+--------------------+---+----
+ fdw_part_update_p2 | 2 | 22
+(1 row)
+
+deallocate fdw_part_upd2;
+reset plan_cache_mode;
+drop table fdw_part_update;
+drop table fdw_part_update_remote;
 -- ===================================================================
 -- test check constraints
 -- ===================================================================
index 04a5a1244e8031fcbfb41cb2f943fe94219c1f3f..22e24bb024fa69ea74fbdcb5ad00010c29ed1eff 100644 (file)
@@ -1732,6 +1732,40 @@ DROP FOREIGN TABLE remt2;
 DROP TABLE loct1;
 DROP TABLE loct2;
 
+-- Test that direct modify and foreign modify work with runtime pruning of
+-- result relations (bug #19484)
+create table fdw_part_update (a int not null, b int) partition by list (a);
+create table fdw_part_update_p1 partition of fdw_part_update for values in (1);
+create table fdw_part_update_remote (a int not null, b int);
+create foreign table fdw_part_update_p2 partition of fdw_part_update
+    for values in (2)
+    server loopback options (table_name 'fdw_part_update_remote');
+insert into fdw_part_update_p1 values (1, 10);
+insert into fdw_part_update_remote values (2, 20);
+set plan_cache_mode = force_generic_plan;
+
+-- Check DirectModify case
+prepare fdw_part_upd(int) as
+    update fdw_part_update set b = b + 1 where a = $1
+    returning tableoid::regclass, a, b;
+explain (verbose, costs off)
+    execute fdw_part_upd(2);
+execute fdw_part_upd(2);
+deallocate fdw_part_upd;
+
+-- Check ForeignModify case
+prepare fdw_part_upd2(int) as
+    update fdw_part_update set b = b + random()::int * 0 + 1 where a = $1
+    returning tableoid::regclass, a, b;
+explain (verbose, costs off)
+    execute fdw_part_upd2(2);
+execute fdw_part_upd2(2);
+deallocate fdw_part_upd2;
+
+reset plan_cache_mode;
+drop table fdw_part_update;
+drop table fdw_part_update_remote;
+
 -- ===================================================================
 -- test check constraints
 -- ===================================================================
index 7e2792ead715b8c1b7a54bb17101127e9ec5b884..6d2624e75b87fb335ebcf2323bde346823f60f0c 100644 (file)
@@ -4609,7 +4609,7 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
                        fdwroutine != NULL &&
                        fdwroutine->ExplainForeignModify != NULL)
                {
-                       List       *fdw_private = (List *) list_nth(node->fdwPrivLists, j);
+                       List       *fdw_private = (List *) list_nth(mtstate->mt_fdwPrivLists, j);
 
                        fdwroutine->ExplainForeignModify(mtstate,
                                                                                         resultRelInfo,
index ea8ed94a7646ebd265a77dc3c034f4882d11594b..cac30666663bf0dd35226108818173bbecb56c28 100644 (file)
@@ -4647,6 +4647,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
        List       *updateColnosLists = NIL;
        List       *mergeActionLists = NIL;
        List       *mergeJoinConditions = NIL;
+       List       *fdwPrivLists = NIL;
+       Bitmapset  *fdwDirectModifyPlans = NULL;
        ResultRelInfo *resultRelInfo;
        List       *arowmarks;
        ListCell   *l;
@@ -4689,6 +4691,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
                if (keep_rel)
                {
+                       List       *fdwPrivList = (List *) list_nth(node->fdwPrivLists, i);
+
                        resultRelations = lappend_int(resultRelations, rti);
                        if (node->withCheckOptionLists)
                        {
@@ -4724,6 +4728,19 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
                                mergeJoinConditions = lappend(mergeJoinConditions, mergeJoinCondition);
                        }
+
+                       /*
+                        * fdwPrivLists/fdwDirectModifyPlans are re-indexed to match
+                        * resultRelations
+                        */
+                       fdwPrivLists = lappend(fdwPrivLists, fdwPrivList);
+                       if (bms_is_member(i, node->fdwDirectModifyPlans))
+                       {
+                               int                     new_index = list_length(resultRelations) - 1;
+
+                               fdwDirectModifyPlans = bms_add_member(fdwDirectModifyPlans,
+                                                                                                         new_index);
+                       }
                }
                i++;
        }
@@ -4753,6 +4770,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
        mtstate->mt_updateColnosLists = updateColnosLists;
        mtstate->mt_mergeActionLists = mergeActionLists;
        mtstate->mt_mergeJoinConditions = mergeJoinConditions;
+       mtstate->mt_fdwPrivLists = fdwPrivLists;
 
        /*----------
         * Resolve the target relation. This is the same as:
@@ -4828,7 +4846,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
                /* Initialize the usesFdwDirectModify flag */
                resultRelInfo->ri_usesFdwDirectModify =
-                       bms_is_member(i, node->fdwDirectModifyPlans);
+                       bms_is_member(i, fdwDirectModifyPlans);
 
                /*
                 * Verify result relation is a valid target for the current operation
@@ -4857,7 +4875,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
                        resultRelInfo->ri_FdwRoutine != NULL &&
                        resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
                {
-                       List       *fdw_private = (List *) list_nth(node->fdwPrivLists, i);
+                       List       *fdw_private = (List *) list_nth(fdwPrivLists, i);
 
                        resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
                                                                                                                         resultRelInfo,
index 409e172bfb669f5bd13adf5e8985798e402ad451..6677a03caab2c7819095f2e669562f95a85a224b 100644 (file)
@@ -1453,13 +1453,15 @@ typedef struct ModifyTableState
        double          mt_merge_deleted;
 
        /*
-        * Lists of valid updateColnosLists, mergeActionLists, and
-        * mergeJoinConditions.  These contain only entries for unpruned
-        * relations, filtered from the corresponding lists in ModifyTable.
+        * Lists of valid updateColnosLists, mergeActionLists,
+        * mergeJoinConditions, and fdwPrivLists.  These contain only entries for
+        * unpruned relations, filtered from the corresponding lists in
+        * ModifyTable.
         */
        List       *mt_updateColnosLists;
        List       *mt_mergeActionLists;
        List       *mt_mergeJoinConditions;
+       List       *mt_fdwPrivLists;
 } ModifyTableState;
 
 /* ----------------