public inbox for [email protected]help / color / mirror / Atom feed
BUG #19484: Segmentation fault triggered by FDW 10+ messages / 6 participants [nested] [flat]
* BUG #19484: Segmentation fault triggered by FDW @ 2026-05-18 06:38 PG Bug reporting form <[email protected]> 0 siblings, 1 reply; 10+ messages in thread From: PG Bug reporting form @ 2026-05-18 06:38 UTC (permalink / raw) To: [email protected]; +Cc: [email protected] The following bug has been logged on the website: Bug reference: 19484 Logged by: Chi Zhang Email address: [email protected] PostgreSQL version: 18.4 Operating system: Ubuntu 24.04 Description: Hi, I found the following test case triggers a segmentation fault: ``` \set ON_ERROR_STOP on CREATE EXTENSION postgres_fdw; CREATE SERVER loopback FOREIGN DATA WRAPPER postgres_fdw OPTIONS ( host '/path/to/pg_socket', port '5432', dbname :'dbname' ); CREATE USER MAPPING FOR postgres SERVER loopback OPTIONS (user 'postgres'); CREATE SCHEMA r; CREATE TABLE r.remote_p2 (a int NOT NULL, b int); CREATE TABLE pt (a int NOT NULL, b int) PARTITION BY LIST (a); CREATE TABLE pt_p1 PARTITION OF pt FOR VALUES IN (1); CREATE FOREIGN TABLE pt_p2 PARTITION OF pt FOR VALUES IN (2) SERVER loopback OPTIONS (schema_name 'r', table_name 'remote_p2'); INSERT INTO pt_p1 VALUES (1, 10); INSERT INTO r.remote_p2 VALUES (2, 20); SET plan_cache_mode = force_generic_plan; PREPARE upd(int) AS UPDATE pt SET b = b + 1 WHERE a = $1 RETURNING tableoid::regclass, a, b; EXPLAIN (costs off) EXECUTE upd(2); EXECUTE upd(2); SELECT * FROM r.remote_p2 ORDER BY a; ``` This is the log: ``` 2026-05-18 13:40:41.888 CST [21729] LOG: database system is ready to accept connections 2026-05-18 13:41:03.317 CST [21932] LOG: unexpected EOF on client connection with an open transaction 2026-05-18 13:41:03.317 CST [21729] LOG: client backend (PID 21931) was terminated by signal 11: Segmentation fault 2026-05-18 13:41:03.317 CST [21729] DETAIL: Failed process was running: EXECUTE upd(2); 2026-05-18 13:41:03.317 CST [21729] LOG: terminating any other active server processes 2026-05-18 13:41:03.319 CST [21729] LOG: all server processes terminated; reinitializing 2026-05-18 13:41:03.345 CST [21936] LOG: database system was interrupted; last known up at 2026-05-18 13:40:41 CST 2026-05-18 13:41:03.509 CST [21936] LOG: database system was not properly shut down; automatic recovery in progress 2026-05-18 13:41:03.513 CST [21936] LOG: redo starts at 0/98371040 2026-05-18 13:41:03.531 CST [21936] LOG: invalid record length at 0/987B6E68: expected at least 24, got 0 2026-05-18 13:41:03.531 CST [21936] LOG: redo done at 0/987B6E40 system usage: CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.01 s 2026-05-18 13:41:03.537 CST [21937] LOG: checkpoint starting: end-of-recovery fast wait 2026-05-18 13:41:03.654 CST [21937] LOG: checkpoint complete: end-of-recovery fast wait: wrote 975 buffers (6.0%), wrote 3 SLRU buffers; 0 WAL file(s) added, 0 removed, 0 recycled; write=0.081 s, sync=0.030 s, total=0.121 s; sync files=325, longest=0.005 s, average=0.001 s; distance=4375 kB, estimate=4375 kB; lsn=0/987B6E68, redo lsn=0/987B6E68 2026-05-18 13:41:03.660 CST [21729] LOG: database system is ready to accept connections ``` I built the Postgres from source code 901ed9b352b41f034e17bc540725082a488fce31 of github commit. ^ permalink raw reply [nested|flat] 10+ messages in thread
* Re: BUG #19484: Segmentation fault triggered by FDW @ 2026-05-20 12:37 Ayush Tiwari <[email protected]> parent: PG Bug reporting form <[email protected]> 0 siblings, 2 replies; 10+ messages in thread From: Ayush Tiwari @ 2026-05-20 12:37 UTC (permalink / raw) To: [email protected]; [email protected]; Etsuro Fujita <[email protected]> Hi, On Wed, 20 May 2026 at 03:59, PG Bug reporting form <[email protected]> wrote: > The following bug has been logged on the website: > > Bug reference: 19484 > Logged by: Chi Zhang > Email address: [email protected] > PostgreSQL version: 18.4 > Operating system: Ubuntu 24.04 > Description: > > Hi, > > I found the following test case triggers a segmentation fault: > > ``` > \set ON_ERROR_STOP on > > CREATE EXTENSION postgres_fdw; > > CREATE SERVER loopback FOREIGN DATA WRAPPER postgres_fdw > OPTIONS ( > host '/path/to/pg_socket', > port '5432', > dbname :'dbname' > ); > > CREATE USER MAPPING FOR postgres SERVER loopback > OPTIONS (user 'postgres'); > > CREATE SCHEMA r; > CREATE TABLE r.remote_p2 (a int NOT NULL, b int); > > CREATE TABLE pt (a int NOT NULL, b int) PARTITION BY LIST (a); > CREATE TABLE pt_p1 PARTITION OF pt FOR VALUES IN (1); > CREATE FOREIGN TABLE pt_p2 PARTITION OF pt FOR VALUES IN (2) > SERVER loopback > OPTIONS (schema_name 'r', table_name 'remote_p2'); > > INSERT INTO pt_p1 VALUES (1, 10); > INSERT INTO r.remote_p2 VALUES (2, 20); > > SET plan_cache_mode = force_generic_plan; > > PREPARE upd(int) AS > UPDATE pt > SET b = b + 1 > WHERE a = $1 > RETURNING tableoid::regclass, a, b; > > EXPLAIN (costs off) EXECUTE upd(2); > EXECUTE upd(2); > SELECT * FROM r.remote_p2 ORDER BY a; > Thanks for the very precise repro, that made this easy to track down. I reproduced the crash on master. The plan EXPLAIN under force_generic_plan shows runtime pruning is in effect: Update on pt Foreign Update on pt_p2 pt_2 -> Append Subplans Removed: 1 -> Foreign Update on pt_p2 pt_2 The SEGV happens inside postgresBeginForeignModify() because ExecInitModifyTable() builds re-indexed "kept" copies of several parallel per-result-relation lists after dropping pruned relations - withCheckOptionLists, returningLists, updateColnosLists, mergeActionLists and mergeJoinConditions, however two members were missed: - node->fdwPrivLists, read with list_nth(node->fdwPrivLists, i) when BeginForeignModify() is called, and - node->fdwDirectModifyPlans, checked with bms_is_member(i, ...) when setting ri_usesFdwDirectModify. Both were still indexed against the original (pre-pruning) positions while the surrounding loop's "i" is now the kept position. When the foreign partition's kept-index no longer matched its original index, BeginForeignModify() got the wrong fdw_private and crashed. Attached patch builds re-indexed kept copies for these two arrays in the same loop as the other parallel lists, and uses them at the two call sites. Regards, Ayush Attachments: [application/octet-stream] v1-0001-Re-index-ModifyTable-FDW-arrays-when-pruning-resu.patch (7.3K, 3-v1-0001-Re-index-ModifyTable-FDW-arrays-when-pruning-resu.patch) download | inline diff: From 1bcf981c29f54b77a07c25a7b3eb06d90164bd8a Mon Sep 17 00:00:00 2001 From: Ayush Tiwari <[email protected]> Date: Wed, 20 May 2026 05:06:57 +0000 Subject: [PATCH v1] Re-index ModifyTable FDW arrays when pruning result relations ExecInitModifyTable() copies parallel per-result-relation lists from the plan node into a new "kept" set after dropping pruned result relations. That re-indexing was already done for withCheckOptionLists, returningLists, updateColnosLists, mergeActionLists and mergeJoinConditions, but two members were missed: * node->fdwPrivLists, indexed by list_nth() when calling BeginForeignModify(), and * node->fdwDirectModifyPlans, indexed by bms_is_member() when setting ri_usesFdwDirectModify. Both were still read using the *kept* loop variable i against the *original* (pre-pruning) indexing, so on a partitioned UPDATE/DELETE that uses a generic plan (PREPARE/EXECUTE under plan_cache_mode = force_generic_plan) and runtime partition pruning, a foreign partition whose original index no longer matched its kept position caused BeginForeignModify() to receive the wrong fdw_private and segfault inside the FDW. Build re-indexed kept copies for these two arrays in the same loop as the other parallel lists and use them at the call sites. Add a postgres_fdw regression case using PREPARE/EXECUTE under force_generic_plan that exercises the failing path. Reported-by: Chi Zhang <[email protected]> Discussion: https://postgr.es/m/[email protected] --- .../postgres_fdw/expected/postgres_fdw.out | 26 +++++++++++++++++++ contrib/postgres_fdw/sql/postgres_fdw.sql | 22 ++++++++++++++++ src/backend/executor/nodeModifyTable.c | 18 +++++++++++-- 3 files changed, 64 insertions(+), 2 deletions(-) diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index e90289e4ab1..872d871a675 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -9337,6 +9337,32 @@ select tableoid::regclass, * FROM locp; -- The executor should not let unexercised FDWs shut down update utrtest set a = 1 where b = 'foo'; +-- Runtime pruning of result relations must keep ModifyTable's per-relation +-- FDW arrays (fdwPrivLists, fdwDirectModifyPlans) aligned with the kept +-- resultRelations. Otherwise BeginForeignModify() reads the wrong +-- fdw_private and segfaults. +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; +prepare fdw_part_upd(int) as + update fdw_part_update set b = b + 1 where a = $1 + returning tableoid::regclass, a, b; +execute fdw_part_upd(2); + tableoid | a | b +--------------------+---+---- + fdw_part_update_p2 | 2 | 21 +(1 row) + +deallocate fdw_part_upd; +reset plan_cache_mode; +drop table fdw_part_update; +drop table fdw_part_update_remote; -- Test that remote triggers work with update tuple routing create trigger loct_br_insert_trigger before insert on loct for each row execute procedure br_insert_trigfunc(); diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql index dfc58beb0d2..c80aaf1c1b4 100644 --- a/contrib/postgres_fdw/sql/postgres_fdw.sql +++ b/contrib/postgres_fdw/sql/postgres_fdw.sql @@ -2723,6 +2723,28 @@ select tableoid::regclass, * FROM locp; -- The executor should not let unexercised FDWs shut down update utrtest set a = 1 where b = 'foo'; +-- Runtime pruning of result relations must keep ModifyTable's per-relation +-- FDW arrays (fdwPrivLists, fdwDirectModifyPlans) aligned with the kept +-- resultRelations. Otherwise BeginForeignModify() reads the wrong +-- fdw_private and segfaults. +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; +prepare fdw_part_upd(int) as + update fdw_part_update set b = b + 1 where a = $1 + returning tableoid::regclass, a, b; +execute fdw_part_upd(2); +deallocate fdw_part_upd; +reset plan_cache_mode; +drop table fdw_part_update; +drop table fdw_part_update_remote; + -- Test that remote triggers work with update tuple routing create trigger loct_br_insert_trigger before insert on loct for each row execute procedure br_insert_trigfunc(); diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 478cb01783c..f69060cb5ab 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -5108,6 +5108,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) List *resultRelations = NIL; List *withCheckOptionLists = NIL; List *returningLists = NIL; + /* fdwPrivLists/fdwDirectModifyPlans are re-indexed to match resultRelations */ + List *fdwPrivLists = NIL; + Bitmapset *fdwDirectModifyPlans = NULL; List *updateColnosLists = NIL; List *mergeActionLists = NIL; List *mergeJoinConditions = NIL; @@ -5153,6 +5156,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) if (keep_rel) { + int new_index = list_length(resultRelations); + resultRelations = lappend_int(resultRelations, rti); if (node->withCheckOptionLists) { @@ -5170,6 +5175,15 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) returningLists = lappend(returningLists, returningList); } + if (node->fdwPrivLists) + { + List *fdwPrivList = (List *) list_nth(node->fdwPrivLists, i); + + fdwPrivLists = lappend(fdwPrivLists, fdwPrivList); + } + if (bms_is_member(i, node->fdwDirectModifyPlans)) + fdwDirectModifyPlans = bms_add_member(fdwDirectModifyPlans, + new_index); if (node->updateColnosLists) { List *updateColnosList = list_nth(node->updateColnosLists, i); @@ -5291,7 +5305,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 @@ -5320,7 +5334,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, -- 2.43.0 ^ permalink raw reply [nested|flat] 10+ messages in thread
* Re: BUG #19484: Segmentation fault triggered by FDW @ 2026-05-20 17:46 Etsuro Fujita <[email protected]> parent: Ayush Tiwari <[email protected]> 1 sibling, 0 replies; 10+ messages in thread From: Etsuro Fujita @ 2026-05-20 17:46 UTC (permalink / raw) To: Ayush Tiwari <[email protected]>; +Cc: [email protected]; [email protected] Hi, On Wed, May 20, 2026 at 5:37 AM Ayush Tiwari <[email protected]> wrote: > On Wed, 20 May 2026 at 03:59, PG Bug reporting form <[email protected]> wrote: >> I found the following test case triggers a segmentation fault: [snip] > Thanks for the very precise repro, that made this easy to track down. > > I reproduced the crash on master. The plan EXPLAIN under > force_generic_plan shows runtime pruning is in effect: > > Update on pt > Foreign Update on pt_p2 pt_2 > -> Append > Subplans Removed: 1 > -> Foreign Update on pt_p2 pt_2 > > The SEGV happens inside postgresBeginForeignModify() because > ExecInitModifyTable() builds re-indexed "kept" copies of several > parallel per-result-relation lists after dropping pruned relations - > withCheckOptionLists, returningLists, updateColnosLists, > mergeActionLists and mergeJoinConditions, however two members were > missed: > > - node->fdwPrivLists, read with list_nth(node->fdwPrivLists, i) when > BeginForeignModify() is called, and > - node->fdwDirectModifyPlans, checked with bms_is_member(i, ...) when > setting ri_usesFdwDirectModify. > > Both were still indexed against the original (pre-pruning) positions > while the surrounding loop's "i" is now the kept position. When the > foreign partition's kept-index no longer matched its original index, > BeginForeignModify() got the wrong fdw_private and crashed. > > Attached patch builds re-indexed kept copies for these two arrays in > the same loop as the other parallel lists, and uses them at the two > call sites. Thanks Chi for the report, and Ayush for the analysis and patch! Will review. Best regards, Etsuro Fujita ^ permalink raw reply [nested|flat] 10+ messages in thread
* Re: BUG #19484: Segmentation fault triggered by FDW @ 2026-05-22 20:56 Matheus Alcantara <[email protected]> parent: Ayush Tiwari <[email protected]> 1 sibling, 1 reply; 10+ messages in thread From: Matheus Alcantara @ 2026-05-22 20:56 UTC (permalink / raw) To: Ayush Tiwari <[email protected]>; [email protected]; [email protected]; Etsuro Fujita <[email protected]> On Wed May 20, 2026 at 9:37 AM -03, Ayush Tiwari wrote: > I reproduced the crash on master. The plan EXPLAIN under > force_generic_plan shows runtime pruning is in effect: > > Update on pt > Foreign Update on pt_p2 pt_2 > -> Append > Subplans Removed: 1 > -> Foreign Update on pt_p2 pt_2 > > The SEGV happens inside postgresBeginForeignModify() because > ExecInitModifyTable() builds re-indexed "kept" copies of several > parallel per-result-relation lists after dropping pruned relations - > withCheckOptionLists, returningLists, updateColnosLists, > mergeActionLists and mergeJoinConditions, however two members were > missed: > > - node->fdwPrivLists, read with list_nth(node->fdwPrivLists, i) when > BeginForeignModify() is called, and > - node->fdwDirectModifyPlans, checked with bms_is_member(i, ...) when > setting ri_usesFdwDirectModify. > > Both were still indexed against the original (pre-pruning) positions > while the surrounding loop's "i" is now the kept position. When the > foreign partition's kept-index no longer matched its original index, > BeginForeignModify() got the wrong fdw_private and crashed. > > Attached patch builds re-indexed kept copies for these two arrays in > the same loop as the other parallel lists, and uses them at the two > call sites. > Hi, thanks for the patch. This issue started on version 18 by commit cbc127917e0. The patch fixes the issue and it make sense to me. One a minor comment is that I think pg_indent is needed on nodeModifyTable.c -- Matheus Alcantara EDB: https://www.enterprisedb.com ^ permalink raw reply [nested|flat] 10+ messages in thread
* Re: BUG #19484: Segmentation fault triggered by FDW @ 2026-05-30 06:18 Rafia Sabih <[email protected]> parent: Matheus Alcantara <[email protected]> 0 siblings, 1 reply; 10+ messages in thread From: Rafia Sabih @ 2026-05-30 06:18 UTC (permalink / raw) To: Matheus Alcantara <[email protected]>; +Cc: Ayush Tiwari <[email protected]>; [email protected]; [email protected]; Etsuro Fujita <[email protected]> On Fri, 22 May 2026 at 22:56, Matheus Alcantara <[email protected]> wrote: > On Wed May 20, 2026 at 9:37 AM -03, Ayush Tiwari wrote: > > I reproduced the crash on master. The plan EXPLAIN under > > force_generic_plan shows runtime pruning is in effect: > > > > Update on pt > > Foreign Update on pt_p2 pt_2 > > -> Append > > Subplans Removed: 1 > > -> Foreign Update on pt_p2 pt_2 > > > > The SEGV happens inside postgresBeginForeignModify() because > > ExecInitModifyTable() builds re-indexed "kept" copies of several > > parallel per-result-relation lists after dropping pruned relations - > > withCheckOptionLists, returningLists, updateColnosLists, > > mergeActionLists and mergeJoinConditions, however two members were > > missed: > > > > - node->fdwPrivLists, read with list_nth(node->fdwPrivLists, i) when > > BeginForeignModify() is called, and > > - node->fdwDirectModifyPlans, checked with bms_is_member(i, ...) when > > setting ri_usesFdwDirectModify. > > > > Both were still indexed against the original (pre-pruning) positions > > while the surrounding loop's "i" is now the kept position. When the > > foreign partition's kept-index no longer matched its original index, > > BeginForeignModify() got the wrong fdw_private and crashed. > > > > Attached patch builds re-indexed kept copies for these two arrays in > > the same loop as the other parallel lists, and uses them at the two > > call sites. > > > A good catch. However there is one issue that remains here, in show_modifytable_info still is using the old index here fdw_private = (List *) list_nth(node->fdwPrivLists, j) i.e. the one before pruning. In fact I found a scenario where it is causing crash, try this create table fdw_part_update2 (a int not null, b int) partition by list (a); create table fdw_part_update2_p1 partition of fdw_part_update2 for values in (1); create table fdw_part_update2_remote (a int not null, b int); create foreign table fdw_part_update2_p2 partition of fdw_part_update2 for values in (2) server loopback options (table_name 'fdw_part_update2_remote'); insert into fdw_part_update2_p1 values (1, 10); insert into fdw_part_update2_remote values (2, 20); set plan_cache_mode = force_generic_plan; prepare fdw_part_upd2(int) as update fdw_part_update2 set b = b + random()::int * 0 + 1 where a = $1 returning tableoid::regclass, a, b; execute fdw_part_upd2(2); explain (analyze, verbose, costs off, timing off, summary off) execute fdw_part_upd2(2); Please find the attached file for the patch to fix this. This patch applies over the earlier patch (given by Ayush) in this thread. > > Hi, thanks for the patch. This issue started on version 18 by commit > cbc127917e0. > > The patch fixes the issue and it make sense to me. One a minor comment > is that I think pg_indent is needed on nodeModifyTable.c > > -- > Matheus Alcantara > EDB: https://www.enterprisedb.com > > > -- Regards, Rafia Sabih CYBERTEC PostgreSQL International GmbH Attachments: [application/octet-stream] 0001-Fix-show_modifytable_info.patch (5.7K, 3-0001-Fix-show_modifytable_info.patch) download | inline diff: From 984ff4240463ae8627e734351afa4f9d131162dd Mon Sep 17 00:00:00 2001 From: Rafia Sabih <[email protected]> Date: Sat, 30 May 2026 08:11:35 +0200 Subject: [PATCH] Fix show_modifytable_info() show_modifytable_info() in explain.c reads the plan-indexed node->fdwPrivLists using the post-pruning executor index j, producing an out-of-bounds access when calling ExplainForeignModify on a non-direct-modify FDW relation. Fix by saving the re-indexed list to mtstate->fdwPrivLists (new field in ModifyTableState) and reading from there in explain.c. --- .../postgres_fdw/expected/postgres_fdw.out | 25 +++++++++++++++++++ contrib/postgres_fdw/sql/postgres_fdw.sql | 7 ++++++ src/backend/commands/explain.c | 2 +- src/backend/executor/nodeModifyTable.c | 3 ++- src/include/nodes/execnodes.h | 3 ++- 5 files changed, 37 insertions(+), 3 deletions(-) diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index 872d871a675..5f5cb78ee65 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -9360,6 +9360,31 @@ execute fdw_part_upd(2); (1 row) deallocate fdw_part_upd; +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; +execute fdw_part_upd2(2); + tableoid | a | b +--------------------+---+---- + fdw_part_update_p2 | 2 | 22 +(1 row) + +explain (analyze, verbose, costs off, timing off, summary off) + execute fdw_part_upd2(2); + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------- + Update on public.fdw_part_update (actual rows=1.00 loops=1) + 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 (actual rows=1.00 loops=1) + Subplans Removed: 1 + -> Foreign Scan on public.fdw_part_update_p2 fdw_part_update_2 (actual rows=1.00 loops=1) + 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) + +deallocate fdw_part_upd2; reset plan_cache_mode; drop table fdw_part_update; drop table fdw_part_update_remote; diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql index c80aaf1c1b4..dc135573a21 100644 --- a/contrib/postgres_fdw/sql/postgres_fdw.sql +++ b/contrib/postgres_fdw/sql/postgres_fdw.sql @@ -2741,6 +2741,13 @@ prepare fdw_part_upd(int) as returning tableoid::regclass, a, b; execute fdw_part_upd(2); deallocate fdw_part_upd; +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; +execute fdw_part_upd2(2); +explain (analyze, verbose, costs off, timing off, summary off) + execute fdw_part_upd2(2); +deallocate fdw_part_upd2; reset plan_cache_mode; drop table fdw_part_update; drop table fdw_part_update_remote; diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 112c17b0d64..92326291129 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -4821,7 +4821,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->fdwPrivLists, j); fdwroutine->ExplainForeignModify(mtstate, resultRelInfo, diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index f69060cb5ab..a66509465b9 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -5109,7 +5109,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) List *withCheckOptionLists = NIL; List *returningLists = NIL; /* fdwPrivLists/fdwDirectModifyPlans are re-indexed to match resultRelations */ - List *fdwPrivLists = NIL; + List *fdwPrivLists = NIL; Bitmapset *fdwDirectModifyPlans = NULL; List *updateColnosLists = NIL; List *mergeActionLists = NIL; @@ -5230,6 +5230,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) mtstate->mt_updateColnosLists = updateColnosLists; mtstate->mt_mergeActionLists = mergeActionLists; mtstate->mt_mergeJoinConditions = mergeJoinConditions; + mtstate->fdwPrivLists = fdwPrivLists; /*---------- * Resolve the target relation. This is the same as: diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 13359180d25..7b33d4d0410 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -1444,7 +1444,8 @@ typedef struct ModifyTableState bool mt_done; /* are we done? */ int mt_nrels; /* number of entries in resultRelInfo[] */ ResultRelInfo *resultRelInfo; /* info about target relation(s) */ - + /* Re-indexed fdw private data lists, aligned with resultRelInfo[] after pruning */ + List *fdwPrivLists; /* * Target relation mentioned in the original statement, used to fire * statement-level triggers and as the root for tuple routing. (This -- 2.39.5 (Apple Git-154) ^ permalink raw reply [nested|flat] 10+ messages in thread
* Re: BUG #19484: Segmentation fault triggered by FDW @ 2026-06-09 15:10 Matheus Alcantara <[email protected]> parent: Rafia Sabih <[email protected]> 0 siblings, 1 reply; 10+ messages in thread From: Matheus Alcantara @ 2026-06-09 15:10 UTC (permalink / raw) To: Rafia Sabih <[email protected]>; +Cc: Ayush Tiwari <[email protected]>; [email protected]; [email protected]; Etsuro Fujita <[email protected]> On Sat May 30, 2026 at 3:18 AM -03, Rafia Sabih wrote: > A good catch. However there is one issue that remains here, > in show_modifytable_info still is using the old index here fdw_private = > (List *) list_nth(node->fdwPrivLists, j) i.e. the one before pruning. > In fact I found a scenario where it is causing crash, try this > > create table fdw_part_update2 (a int not null, b int) partition by list (a); > create table fdw_part_update2_p1 partition of fdw_part_update2 for values > in (1); > create table fdw_part_update2_remote (a int not null, b int); > create foreign table fdw_part_update2_p2 partition of fdw_part_update2 > for values in (2) > server loopback options (table_name 'fdw_part_update2_remote'); > insert into fdw_part_update2_p1 values (1, 10); > insert into fdw_part_update2_remote values (2, 20); > set plan_cache_mode = force_generic_plan; > prepare fdw_part_upd2(int) as > update fdw_part_update2 set b = b + random()::int * 0 + 1 where a = $1 > returning tableoid::regclass, a, b; > execute fdw_part_upd2(2); > explain (analyze, verbose, costs off, timing off, summary off) > execute fdw_part_upd2(2); > > Please find the attached file for the patch to fix this. This patch applies > over the earlier patch (given by Ayush) in this thread. > Thanks for catching this, Rafia. The fix is correct — show_modifytable_info() was indeed still reading from node->fdwPrivLists using the post-pruning index j, which causes an out-of-bounds access when partitions are pruned. I think both patches should be squashed into a single one since they fix the same underlying issue. I've done this locally and also ran pg_indent over the result. Attached is the combined patch. One minor naming observation: the new fdwPrivLists field in ModifyTableState doesn't follow the mt_ prefix convention used by the other re-indexed lists (mt_updateColnosLists, mt_mergeActionLists, mt_mergeJoinConditions). Should we rename it to mt_fdwPrivLists for consistency? -- Matheus Alcantara EDB: https://www.enterprisedb.com From 4020e86e2ec85b6fd58397b5f4f467d1baf5ad87 Mon Sep 17 00:00:00 2001 From: Matheus Alcantara <[email protected]> Date: Tue, 9 Jun 2026 11:55:34 -0300 Subject: [PATCH] Re-index ModifyTable FDW arrays when pruning result relations ExecInitModifyTable() copies parallel per-result-relation lists from the plan node into a new "kept" set after dropping pruned result relations. That re-indexing was already done for withCheckOptionLists, returningLists, updateColnosLists, mergeActionLists and mergeJoinConditions, but fdwPrivLists and fdwDirectModifyPlans were missed. Additionally, show_modifytable_info() in explain.c was reading the plan-indexed node->fdwPrivLists using the post-pruning executor index, causing out-of-bounds access. Fix by saving the re-indexed list to mtstate->fdwPrivLists and reading from there. Author: Ayush Tiwari <[email protected]> Author: Rafia Sabih <[email protected]> Co-authored-by: Matheus Alcantara <[email protected]> Reported-by: Chi Zhang <[email protected]> Discussion: https://www.postgresql.org/message-id/19484-a3cb82c8cde3c8fa%40postgresql.org --- .../postgres_fdw/expected/postgres_fdw.out | 51 +++++++++++++++++++ contrib/postgres_fdw/sql/postgres_fdw.sql | 29 +++++++++++ src/backend/commands/explain.c | 2 +- src/backend/executor/nodeModifyTable.c | 23 ++++++++- src/include/nodes/execnodes.h | 6 +++ 5 files changed, 108 insertions(+), 3 deletions(-) diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index e90289e4ab1..5f5cb78ee65 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -9337,6 +9337,57 @@ select tableoid::regclass, * FROM locp; -- The executor should not let unexercised FDWs shut down update utrtest set a = 1 where b = 'foo'; +-- Runtime pruning of result relations must keep ModifyTable's per-relation +-- FDW arrays (fdwPrivLists, fdwDirectModifyPlans) aligned with the kept +-- resultRelations. Otherwise BeginForeignModify() reads the wrong +-- fdw_private and segfaults. +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; +prepare fdw_part_upd(int) as + update fdw_part_update set b = b + 1 where a = $1 + returning tableoid::regclass, a, b; +execute fdw_part_upd(2); + tableoid | a | b +--------------------+---+---- + fdw_part_update_p2 | 2 | 21 +(1 row) + +deallocate fdw_part_upd; +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; +execute fdw_part_upd2(2); + tableoid | a | b +--------------------+---+---- + fdw_part_update_p2 | 2 | 22 +(1 row) + +explain (analyze, verbose, costs off, timing off, summary off) + execute fdw_part_upd2(2); + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------- + Update on public.fdw_part_update (actual rows=1.00 loops=1) + 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 (actual rows=1.00 loops=1) + Subplans Removed: 1 + -> Foreign Scan on public.fdw_part_update_p2 fdw_part_update_2 (actual rows=1.00 loops=1) + 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) + +deallocate fdw_part_upd2; +reset plan_cache_mode; +drop table fdw_part_update; +drop table fdw_part_update_remote; -- Test that remote triggers work with update tuple routing create trigger loct_br_insert_trigger before insert on loct for each row execute procedure br_insert_trigfunc(); diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql index dfc58beb0d2..dc135573a21 100644 --- a/contrib/postgres_fdw/sql/postgres_fdw.sql +++ b/contrib/postgres_fdw/sql/postgres_fdw.sql @@ -2723,6 +2723,35 @@ select tableoid::regclass, * FROM locp; -- The executor should not let unexercised FDWs shut down update utrtest set a = 1 where b = 'foo'; +-- Runtime pruning of result relations must keep ModifyTable's per-relation +-- FDW arrays (fdwPrivLists, fdwDirectModifyPlans) aligned with the kept +-- resultRelations. Otherwise BeginForeignModify() reads the wrong +-- fdw_private and segfaults. +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; +prepare fdw_part_upd(int) as + update fdw_part_update set b = b + 1 where a = $1 + returning tableoid::regclass, a, b; +execute fdw_part_upd(2); +deallocate fdw_part_upd; +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; +execute fdw_part_upd2(2); +explain (analyze, verbose, costs off, timing off, summary off) + 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 that remote triggers work with update tuple routing create trigger loct_br_insert_trigger before insert on loct for each row execute procedure br_insert_trigfunc(); diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 112c17b0d64..3e43c97896e 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -4821,7 +4821,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->fdwPrivLists, j); fdwroutine->ExplainForeignModify(mtstate, resultRelInfo, diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 33a6735f08d..a631c345c5e 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -5105,6 +5105,13 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) List *resultRelations = NIL; List *withCheckOptionLists = NIL; List *returningLists = NIL; + + /* + * fdwPrivLists/fdwDirectModifyPlans are re-indexed to match + * resultRelations + */ + List *fdwPrivLists = NIL; + Bitmapset *fdwDirectModifyPlans = NULL; List *updateColnosLists = NIL; List *mergeActionLists = NIL; List *mergeJoinConditions = NIL; @@ -5150,6 +5157,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) if (keep_rel) { + int new_index = list_length(resultRelations); + resultRelations = lappend_int(resultRelations, rti); if (node->withCheckOptionLists) { @@ -5167,6 +5176,15 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) returningLists = lappend(returningLists, returningList); } + if (node->fdwPrivLists) + { + List *fdwPrivList = (List *) list_nth(node->fdwPrivLists, i); + + fdwPrivLists = lappend(fdwPrivLists, fdwPrivList); + } + if (bms_is_member(i, node->fdwDirectModifyPlans)) + fdwDirectModifyPlans = bms_add_member(fdwDirectModifyPlans, + new_index); if (node->updateColnosLists) { List *updateColnosList = list_nth(node->updateColnosLists, i); @@ -5213,6 +5231,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) mtstate->mt_updateColnosLists = updateColnosLists; mtstate->mt_mergeActionLists = mergeActionLists; mtstate->mt_mergeJoinConditions = mergeJoinConditions; + mtstate->fdwPrivLists = fdwPrivLists; /*---------- * Resolve the target relation. This is the same as: @@ -5288,7 +5307,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 @@ -5317,7 +5336,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, diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 53c138310db..f64c2cc5f34 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -1446,6 +1446,12 @@ typedef struct ModifyTableState int mt_nrels; /* number of entries in resultRelInfo[] */ ResultRelInfo *resultRelInfo; /* info about target relation(s) */ + /* + * Re-indexed fdw private data lists, aligned with resultRelInfo[] after + * pruning + */ + List *fdwPrivLists; + /* * Target relation mentioned in the original statement, used to fire * statement-level triggers and as the root for tuple routing. (This -- 2.50.1 (Apple Git-155) Attachments: [text/plain] 0001-Re-index-ModifyTable-FDW-arrays-when-pruning-result-.patch (10.3K, 2-0001-Re-index-ModifyTable-FDW-arrays-when-pruning-result-.patch) download | inline diff: From 4020e86e2ec85b6fd58397b5f4f467d1baf5ad87 Mon Sep 17 00:00:00 2001 From: Matheus Alcantara <[email protected]> Date: Tue, 9 Jun 2026 11:55:34 -0300 Subject: [PATCH] Re-index ModifyTable FDW arrays when pruning result relations ExecInitModifyTable() copies parallel per-result-relation lists from the plan node into a new "kept" set after dropping pruned result relations. That re-indexing was already done for withCheckOptionLists, returningLists, updateColnosLists, mergeActionLists and mergeJoinConditions, but fdwPrivLists and fdwDirectModifyPlans were missed. Additionally, show_modifytable_info() in explain.c was reading the plan-indexed node->fdwPrivLists using the post-pruning executor index, causing out-of-bounds access. Fix by saving the re-indexed list to mtstate->fdwPrivLists and reading from there. Author: Ayush Tiwari <[email protected]> Author: Rafia Sabih <[email protected]> Co-authored-by: Matheus Alcantara <[email protected]> Reported-by: Chi Zhang <[email protected]> Discussion: https://www.postgresql.org/message-id/19484-a3cb82c8cde3c8fa%40postgresql.org --- .../postgres_fdw/expected/postgres_fdw.out | 51 +++++++++++++++++++ contrib/postgres_fdw/sql/postgres_fdw.sql | 29 +++++++++++ src/backend/commands/explain.c | 2 +- src/backend/executor/nodeModifyTable.c | 23 ++++++++- src/include/nodes/execnodes.h | 6 +++ 5 files changed, 108 insertions(+), 3 deletions(-) diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index e90289e4ab1..5f5cb78ee65 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -9337,6 +9337,57 @@ select tableoid::regclass, * FROM locp; -- The executor should not let unexercised FDWs shut down update utrtest set a = 1 where b = 'foo'; +-- Runtime pruning of result relations must keep ModifyTable's per-relation +-- FDW arrays (fdwPrivLists, fdwDirectModifyPlans) aligned with the kept +-- resultRelations. Otherwise BeginForeignModify() reads the wrong +-- fdw_private and segfaults. +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; +prepare fdw_part_upd(int) as + update fdw_part_update set b = b + 1 where a = $1 + returning tableoid::regclass, a, b; +execute fdw_part_upd(2); + tableoid | a | b +--------------------+---+---- + fdw_part_update_p2 | 2 | 21 +(1 row) + +deallocate fdw_part_upd; +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; +execute fdw_part_upd2(2); + tableoid | a | b +--------------------+---+---- + fdw_part_update_p2 | 2 | 22 +(1 row) + +explain (analyze, verbose, costs off, timing off, summary off) + execute fdw_part_upd2(2); + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------- + Update on public.fdw_part_update (actual rows=1.00 loops=1) + 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 (actual rows=1.00 loops=1) + Subplans Removed: 1 + -> Foreign Scan on public.fdw_part_update_p2 fdw_part_update_2 (actual rows=1.00 loops=1) + 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) + +deallocate fdw_part_upd2; +reset plan_cache_mode; +drop table fdw_part_update; +drop table fdw_part_update_remote; -- Test that remote triggers work with update tuple routing create trigger loct_br_insert_trigger before insert on loct for each row execute procedure br_insert_trigfunc(); diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql index dfc58beb0d2..dc135573a21 100644 --- a/contrib/postgres_fdw/sql/postgres_fdw.sql +++ b/contrib/postgres_fdw/sql/postgres_fdw.sql @@ -2723,6 +2723,35 @@ select tableoid::regclass, * FROM locp; -- The executor should not let unexercised FDWs shut down update utrtest set a = 1 where b = 'foo'; +-- Runtime pruning of result relations must keep ModifyTable's per-relation +-- FDW arrays (fdwPrivLists, fdwDirectModifyPlans) aligned with the kept +-- resultRelations. Otherwise BeginForeignModify() reads the wrong +-- fdw_private and segfaults. +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; +prepare fdw_part_upd(int) as + update fdw_part_update set b = b + 1 where a = $1 + returning tableoid::regclass, a, b; +execute fdw_part_upd(2); +deallocate fdw_part_upd; +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; +execute fdw_part_upd2(2); +explain (analyze, verbose, costs off, timing off, summary off) + 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 that remote triggers work with update tuple routing create trigger loct_br_insert_trigger before insert on loct for each row execute procedure br_insert_trigfunc(); diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 112c17b0d64..3e43c97896e 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -4821,7 +4821,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->fdwPrivLists, j); fdwroutine->ExplainForeignModify(mtstate, resultRelInfo, diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 33a6735f08d..a631c345c5e 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -5105,6 +5105,13 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) List *resultRelations = NIL; List *withCheckOptionLists = NIL; List *returningLists = NIL; + + /* + * fdwPrivLists/fdwDirectModifyPlans are re-indexed to match + * resultRelations + */ + List *fdwPrivLists = NIL; + Bitmapset *fdwDirectModifyPlans = NULL; List *updateColnosLists = NIL; List *mergeActionLists = NIL; List *mergeJoinConditions = NIL; @@ -5150,6 +5157,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) if (keep_rel) { + int new_index = list_length(resultRelations); + resultRelations = lappend_int(resultRelations, rti); if (node->withCheckOptionLists) { @@ -5167,6 +5176,15 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) returningLists = lappend(returningLists, returningList); } + if (node->fdwPrivLists) + { + List *fdwPrivList = (List *) list_nth(node->fdwPrivLists, i); + + fdwPrivLists = lappend(fdwPrivLists, fdwPrivList); + } + if (bms_is_member(i, node->fdwDirectModifyPlans)) + fdwDirectModifyPlans = bms_add_member(fdwDirectModifyPlans, + new_index); if (node->updateColnosLists) { List *updateColnosList = list_nth(node->updateColnosLists, i); @@ -5213,6 +5231,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) mtstate->mt_updateColnosLists = updateColnosLists; mtstate->mt_mergeActionLists = mergeActionLists; mtstate->mt_mergeJoinConditions = mergeJoinConditions; + mtstate->fdwPrivLists = fdwPrivLists; /*---------- * Resolve the target relation. This is the same as: @@ -5288,7 +5307,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 @@ -5317,7 +5336,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, diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 53c138310db..f64c2cc5f34 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -1446,6 +1446,12 @@ typedef struct ModifyTableState int mt_nrels; /* number of entries in resultRelInfo[] */ ResultRelInfo *resultRelInfo; /* info about target relation(s) */ + /* + * Re-indexed fdw private data lists, aligned with resultRelInfo[] after + * pruning + */ + List *fdwPrivLists; + /* * Target relation mentioned in the original statement, used to fire * statement-level triggers and as the root for tuple routing. (This -- 2.50.1 (Apple Git-155) ^ permalink raw reply [nested|flat] 10+ messages in thread
* Re: BUG #19484: Segmentation fault triggered by FDW @ 2026-06-10 05:15 Ayush Tiwari <[email protected]> parent: Matheus Alcantara <[email protected]> 0 siblings, 1 reply; 10+ messages in thread From: Ayush Tiwari @ 2026-06-10 05:15 UTC (permalink / raw) To: Matheus Alcantara <[email protected]>; +Cc: Rafia Sabih <[email protected]>; [email protected]; [email protected]; Etsuro Fujita <[email protected]> Hi, On Tue, 9 Jun 2026 at 20:40, Matheus Alcantara <[email protected]> wrote: > > I think both patches should be squashed into a single one since they fix > the same underlying issue. I've done this locally and also ran pg_indent > over the result. Attached is the combined patch. > Thanks for this! > One minor naming observation: the new fdwPrivLists field in > ModifyTableState doesn't follow the mt_ prefix convention used by the > other re-indexed lists (mt_updateColnosLists, mt_mergeActionLists, > mt_mergeJoinConditions). Should we rename it to mt_fdwPrivLists for > consistency? > I think yes, it makes sense to rename it. Regards, Ayush ^ permalink raw reply [nested|flat] 10+ messages in thread
* Re: BUG #19484: Segmentation fault triggered by FDW @ 2026-06-10 11:08 Matheus Alcantara <[email protected]> parent: Ayush Tiwari <[email protected]> 0 siblings, 1 reply; 10+ messages in thread From: Matheus Alcantara @ 2026-06-10 11:08 UTC (permalink / raw) To: Ayush Tiwari <[email protected]>; +Cc: Rafia Sabih <[email protected]>; [email protected]; [email protected]; Etsuro Fujita <[email protected]>; Amit Langote <[email protected]> On Wed Jun 10, 2026 at 2:15 AM -03, Ayush Tiwari wrote: >> One minor naming observation: the new fdwPrivLists field in >> ModifyTableState doesn't follow the mt_ prefix convention used by the >> other re-indexed lists (mt_updateColnosLists, mt_mergeActionLists, >> mt_mergeJoinConditions). Should we rename it to mt_fdwPrivLists for >> consistency? >> > > I think yes, it makes sense to rename it. > Attached v2 renamed, thanks. (Also CC Amit on this since he committed cbc127917e0 which I believe that is when the issue started) -- Matheus Alcantara EDB: https://www.enterprisedb.com From 971d185cf4bfb75d0459f2c7848e6b77afa2ee85 Mon Sep 17 00:00:00 2001 From: Matheus Alcantara <[email protected]> Date: Tue, 9 Jun 2026 11:55:34 -0300 Subject: [PATCH v2] Re-index ModifyTable FDW arrays when pruning result relations ExecInitModifyTable() copies parallel per-result-relation lists from the plan node into a new "kept" set after dropping pruned result relations. That re-indexing was already done for withCheckOptionLists, returningLists, updateColnosLists, mergeActionLists and mergeJoinConditions, but fdwPrivLists and fdwDirectModifyPlans were missed. Additionally, show_modifytable_info() in explain.c was reading the plan-indexed node->fdwPrivLists using the post-pruning executor index, causing out-of-bounds access. Fix by saving the re-indexed list to mtstate->mt_fdwPrivLists and reading from there. Author: Ayush Tiwari <[email protected]> Author: Rafia Sabih <[email protected]> Co-authored-by: Matheus Alcantara <[email protected]> Reviewed-by: Ayush Tiwari <[email protected]> Reported-by: Chi Zhang <[email protected]> Discussion: https://www.postgresql.org/message-id/19484-a3cb82c8cde3c8fa%40postgresql.org --- .../postgres_fdw/expected/postgres_fdw.out | 51 +++++++++++++++++++ contrib/postgres_fdw/sql/postgres_fdw.sql | 29 +++++++++++ src/backend/commands/explain.c | 2 +- src/backend/executor/nodeModifyTable.c | 23 ++++++++- src/include/nodes/execnodes.h | 6 +++ 5 files changed, 108 insertions(+), 3 deletions(-) diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index e90289e4ab1..5f5cb78ee65 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -9337,6 +9337,57 @@ select tableoid::regclass, * FROM locp; -- The executor should not let unexercised FDWs shut down update utrtest set a = 1 where b = 'foo'; +-- Runtime pruning of result relations must keep ModifyTable's per-relation +-- FDW arrays (fdwPrivLists, fdwDirectModifyPlans) aligned with the kept +-- resultRelations. Otherwise BeginForeignModify() reads the wrong +-- fdw_private and segfaults. +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; +prepare fdw_part_upd(int) as + update fdw_part_update set b = b + 1 where a = $1 + returning tableoid::regclass, a, b; +execute fdw_part_upd(2); + tableoid | a | b +--------------------+---+---- + fdw_part_update_p2 | 2 | 21 +(1 row) + +deallocate fdw_part_upd; +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; +execute fdw_part_upd2(2); + tableoid | a | b +--------------------+---+---- + fdw_part_update_p2 | 2 | 22 +(1 row) + +explain (analyze, verbose, costs off, timing off, summary off) + execute fdw_part_upd2(2); + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------- + Update on public.fdw_part_update (actual rows=1.00 loops=1) + 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 (actual rows=1.00 loops=1) + Subplans Removed: 1 + -> Foreign Scan on public.fdw_part_update_p2 fdw_part_update_2 (actual rows=1.00 loops=1) + 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) + +deallocate fdw_part_upd2; +reset plan_cache_mode; +drop table fdw_part_update; +drop table fdw_part_update_remote; -- Test that remote triggers work with update tuple routing create trigger loct_br_insert_trigger before insert on loct for each row execute procedure br_insert_trigfunc(); diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql index dfc58beb0d2..dc135573a21 100644 --- a/contrib/postgres_fdw/sql/postgres_fdw.sql +++ b/contrib/postgres_fdw/sql/postgres_fdw.sql @@ -2723,6 +2723,35 @@ select tableoid::regclass, * FROM locp; -- The executor should not let unexercised FDWs shut down update utrtest set a = 1 where b = 'foo'; +-- Runtime pruning of result relations must keep ModifyTable's per-relation +-- FDW arrays (fdwPrivLists, fdwDirectModifyPlans) aligned with the kept +-- resultRelations. Otherwise BeginForeignModify() reads the wrong +-- fdw_private and segfaults. +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; +prepare fdw_part_upd(int) as + update fdw_part_update set b = b + 1 where a = $1 + returning tableoid::regclass, a, b; +execute fdw_part_upd(2); +deallocate fdw_part_upd; +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; +execute fdw_part_upd2(2); +explain (analyze, verbose, costs off, timing off, summary off) + 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 that remote triggers work with update tuple routing create trigger loct_br_insert_trigger before insert on loct for each row execute procedure br_insert_trigfunc(); diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 112c17b0d64..a40d03d35f3 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -4821,7 +4821,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, diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 33a6735f08d..feea28214d4 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -5105,6 +5105,13 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) List *resultRelations = NIL; List *withCheckOptionLists = NIL; List *returningLists = NIL; + + /* + * fdwPrivLists/fdwDirectModifyPlans are re-indexed to match + * resultRelations + */ + List *fdwPrivLists = NIL; + Bitmapset *fdwDirectModifyPlans = NULL; List *updateColnosLists = NIL; List *mergeActionLists = NIL; List *mergeJoinConditions = NIL; @@ -5150,6 +5157,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) if (keep_rel) { + int new_index = list_length(resultRelations); + resultRelations = lappend_int(resultRelations, rti); if (node->withCheckOptionLists) { @@ -5167,6 +5176,15 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) returningLists = lappend(returningLists, returningList); } + if (node->fdwPrivLists) + { + List *fdwPrivList = (List *) list_nth(node->fdwPrivLists, i); + + fdwPrivLists = lappend(fdwPrivLists, fdwPrivList); + } + if (bms_is_member(i, node->fdwDirectModifyPlans)) + fdwDirectModifyPlans = bms_add_member(fdwDirectModifyPlans, + new_index); if (node->updateColnosLists) { List *updateColnosList = list_nth(node->updateColnosLists, i); @@ -5213,6 +5231,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: @@ -5288,7 +5307,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 @@ -5317,7 +5336,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, diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 53c138310db..5871383961f 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -1446,6 +1446,12 @@ typedef struct ModifyTableState int mt_nrels; /* number of entries in resultRelInfo[] */ ResultRelInfo *resultRelInfo; /* info about target relation(s) */ + /* + * Re-indexed fdw private data lists, aligned with resultRelInfo[] after + * pruning + */ + List *mt_fdwPrivLists; + /* * Target relation mentioned in the original statement, used to fire * statement-level triggers and as the root for tuple routing. (This -- 2.50.1 (Apple Git-155) Attachments: [text/plain] v2-0001-Re-index-ModifyTable-FDW-arrays-when-pruning-resu.patch (10.4K, 2-v2-0001-Re-index-ModifyTable-FDW-arrays-when-pruning-resu.patch) download | inline diff: From 971d185cf4bfb75d0459f2c7848e6b77afa2ee85 Mon Sep 17 00:00:00 2001 From: Matheus Alcantara <[email protected]> Date: Tue, 9 Jun 2026 11:55:34 -0300 Subject: [PATCH v2] Re-index ModifyTable FDW arrays when pruning result relations ExecInitModifyTable() copies parallel per-result-relation lists from the plan node into a new "kept" set after dropping pruned result relations. That re-indexing was already done for withCheckOptionLists, returningLists, updateColnosLists, mergeActionLists and mergeJoinConditions, but fdwPrivLists and fdwDirectModifyPlans were missed. Additionally, show_modifytable_info() in explain.c was reading the plan-indexed node->fdwPrivLists using the post-pruning executor index, causing out-of-bounds access. Fix by saving the re-indexed list to mtstate->mt_fdwPrivLists and reading from there. Author: Ayush Tiwari <[email protected]> Author: Rafia Sabih <[email protected]> Co-authored-by: Matheus Alcantara <[email protected]> Reviewed-by: Ayush Tiwari <[email protected]> Reported-by: Chi Zhang <[email protected]> Discussion: https://www.postgresql.org/message-id/19484-a3cb82c8cde3c8fa%40postgresql.org --- .../postgres_fdw/expected/postgres_fdw.out | 51 +++++++++++++++++++ contrib/postgres_fdw/sql/postgres_fdw.sql | 29 +++++++++++ src/backend/commands/explain.c | 2 +- src/backend/executor/nodeModifyTable.c | 23 ++++++++- src/include/nodes/execnodes.h | 6 +++ 5 files changed, 108 insertions(+), 3 deletions(-) diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index e90289e4ab1..5f5cb78ee65 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -9337,6 +9337,57 @@ select tableoid::regclass, * FROM locp; -- The executor should not let unexercised FDWs shut down update utrtest set a = 1 where b = 'foo'; +-- Runtime pruning of result relations must keep ModifyTable's per-relation +-- FDW arrays (fdwPrivLists, fdwDirectModifyPlans) aligned with the kept +-- resultRelations. Otherwise BeginForeignModify() reads the wrong +-- fdw_private and segfaults. +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; +prepare fdw_part_upd(int) as + update fdw_part_update set b = b + 1 where a = $1 + returning tableoid::regclass, a, b; +execute fdw_part_upd(2); + tableoid | a | b +--------------------+---+---- + fdw_part_update_p2 | 2 | 21 +(1 row) + +deallocate fdw_part_upd; +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; +execute fdw_part_upd2(2); + tableoid | a | b +--------------------+---+---- + fdw_part_update_p2 | 2 | 22 +(1 row) + +explain (analyze, verbose, costs off, timing off, summary off) + execute fdw_part_upd2(2); + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------- + Update on public.fdw_part_update (actual rows=1.00 loops=1) + 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 (actual rows=1.00 loops=1) + Subplans Removed: 1 + -> Foreign Scan on public.fdw_part_update_p2 fdw_part_update_2 (actual rows=1.00 loops=1) + 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) + +deallocate fdw_part_upd2; +reset plan_cache_mode; +drop table fdw_part_update; +drop table fdw_part_update_remote; -- Test that remote triggers work with update tuple routing create trigger loct_br_insert_trigger before insert on loct for each row execute procedure br_insert_trigfunc(); diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql index dfc58beb0d2..dc135573a21 100644 --- a/contrib/postgres_fdw/sql/postgres_fdw.sql +++ b/contrib/postgres_fdw/sql/postgres_fdw.sql @@ -2723,6 +2723,35 @@ select tableoid::regclass, * FROM locp; -- The executor should not let unexercised FDWs shut down update utrtest set a = 1 where b = 'foo'; +-- Runtime pruning of result relations must keep ModifyTable's per-relation +-- FDW arrays (fdwPrivLists, fdwDirectModifyPlans) aligned with the kept +-- resultRelations. Otherwise BeginForeignModify() reads the wrong +-- fdw_private and segfaults. +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; +prepare fdw_part_upd(int) as + update fdw_part_update set b = b + 1 where a = $1 + returning tableoid::regclass, a, b; +execute fdw_part_upd(2); +deallocate fdw_part_upd; +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; +execute fdw_part_upd2(2); +explain (analyze, verbose, costs off, timing off, summary off) + 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 that remote triggers work with update tuple routing create trigger loct_br_insert_trigger before insert on loct for each row execute procedure br_insert_trigfunc(); diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 112c17b0d64..a40d03d35f3 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -4821,7 +4821,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, diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 33a6735f08d..feea28214d4 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -5105,6 +5105,13 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) List *resultRelations = NIL; List *withCheckOptionLists = NIL; List *returningLists = NIL; + + /* + * fdwPrivLists/fdwDirectModifyPlans are re-indexed to match + * resultRelations + */ + List *fdwPrivLists = NIL; + Bitmapset *fdwDirectModifyPlans = NULL; List *updateColnosLists = NIL; List *mergeActionLists = NIL; List *mergeJoinConditions = NIL; @@ -5150,6 +5157,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) if (keep_rel) { + int new_index = list_length(resultRelations); + resultRelations = lappend_int(resultRelations, rti); if (node->withCheckOptionLists) { @@ -5167,6 +5176,15 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) returningLists = lappend(returningLists, returningList); } + if (node->fdwPrivLists) + { + List *fdwPrivList = (List *) list_nth(node->fdwPrivLists, i); + + fdwPrivLists = lappend(fdwPrivLists, fdwPrivList); + } + if (bms_is_member(i, node->fdwDirectModifyPlans)) + fdwDirectModifyPlans = bms_add_member(fdwDirectModifyPlans, + new_index); if (node->updateColnosLists) { List *updateColnosList = list_nth(node->updateColnosLists, i); @@ -5213,6 +5231,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: @@ -5288,7 +5307,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 @@ -5317,7 +5336,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, diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 53c138310db..5871383961f 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -1446,6 +1446,12 @@ typedef struct ModifyTableState int mt_nrels; /* number of entries in resultRelInfo[] */ ResultRelInfo *resultRelInfo; /* info about target relation(s) */ + /* + * Re-indexed fdw private data lists, aligned with resultRelInfo[] after + * pruning + */ + List *mt_fdwPrivLists; + /* * Target relation mentioned in the original statement, used to fire * statement-level triggers and as the root for tuple routing. (This -- 2.50.1 (Apple Git-155) ^ permalink raw reply [nested|flat] 10+ messages in thread
* Re: BUG #19484: Segmentation fault triggered by FDW @ 2026-06-10 13:09 Amit Langote <[email protected]> parent: Matheus Alcantara <[email protected]> 0 siblings, 1 reply; 10+ messages in thread From: Amit Langote @ 2026-06-10 13:09 UTC (permalink / raw) To: Matheus Alcantara <[email protected]>; +Cc: Ayush Tiwari <[email protected]>; Rafia Sabih <[email protected]>; [email protected]; [email protected]; Etsuro Fujita <[email protected]>; Amit Langote <[email protected]> Hi Matheus, On Wed, Jun 10, 2026 at 8:09 PM Matheus Alcantara <[email protected]> wrote: > > On Wed Jun 10, 2026 at 2:15 AM -03, Ayush Tiwari wrote: > >> One minor naming observation: the new fdwPrivLists field in > >> ModifyTableState doesn't follow the mt_ prefix convention used by the > >> other re-indexed lists (mt_updateColnosLists, mt_mergeActionLists, > >> mt_mergeJoinConditions). Should we rename it to mt_fdwPrivLists for > >> consistency? > >> > > > > I think yes, it makes sense to rename it. > > > > Attached v2 renamed, thanks. > > (Also CC Amit on this since he committed cbc127917e0 which I believe > that is when the issue started) Thanks for adding me. I'll take a look at this early next week. -- Thanks, Amit Langote ^ permalink raw reply [nested|flat] 10+ messages in thread
* Re: BUG #19484: Segmentation fault triggered by FDW @ 2026-06-10 13:27 Amit Langote <[email protected]> parent: Amit Langote <[email protected]> 0 siblings, 0 replies; 10+ messages in thread From: Amit Langote @ 2026-06-10 13:27 UTC (permalink / raw) To: Matheus Alcantara <[email protected]>; +Cc: Ayush Tiwari <[email protected]>; Rafia Sabih <[email protected]>; [email protected]; [email protected]; Etsuro Fujita <[email protected]>; Amit Langote <[email protected]> On Wed, Jun 10, 2026 at 10:09 PM Amit Langote <[email protected]> wrote: > On Wed, Jun 10, 2026 at 8:09 PM Matheus Alcantara > <[email protected]> wrote: > > On Wed Jun 10, 2026 at 2:15 AM -03, Ayush Tiwari wrote: > > >> One minor naming observation: the new fdwPrivLists field in > > >> ModifyTableState doesn't follow the mt_ prefix convention used by the > > >> other re-indexed lists (mt_updateColnosLists, mt_mergeActionLists, > > >> mt_mergeJoinConditions). Should we rename it to mt_fdwPrivLists for > > >> consistency? > > >> > > > > > > I think yes, it makes sense to rename it. > > > > > > > Attached v2 renamed, thanks. > > > > (Also CC Amit on this since he committed cbc127917e0 which I believe > > that is when the issue started) > > Thanks for adding me. I'll take a look at this early next week. I looked, and the patch seems straightforward enough. Before committing it, I'd like to wait briefly to see if Fujita-san has any thoughts on the FDW-side concerns, since he has already chimed in on the thread. -- Thanks, Amit Langote ^ permalink raw reply [nested|flat] 10+ messages in thread
end of thread, other threads:[~2026-06-10 13:27 UTC | newest] Thread overview: 10+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2026-05-18 06:38 BUG #19484: Segmentation fault triggered by FDW PG Bug reporting form <[email protected]> 2026-05-20 12:37 ` Ayush Tiwari <[email protected]> 2026-05-20 17:46 ` Etsuro Fujita <[email protected]> 2026-05-22 20:56 ` Matheus Alcantara <[email protected]> 2026-05-30 06:18 ` Rafia Sabih <[email protected]> 2026-06-09 15:10 ` Matheus Alcantara <[email protected]> 2026-06-10 05:15 ` Ayush Tiwari <[email protected]> 2026-06-10 11:08 ` Matheus Alcantara <[email protected]> 2026-06-10 13:09 ` Amit Langote <[email protected]> 2026-06-10 13:27 ` Amit Langote <[email protected]>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox