v2-0001-Fix-assertion-failure-in-postgresGetForeignJoinPa.patch
application/octet-stream
Filename: v2-0001-Fix-assertion-failure-in-postgresGetForeignJoinPa.patch
Type: application/octet-stream
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v2-0001
Subject: Fix assertion failure in postgresGetForeignJoinPaths().
| File | + | − |
|---|---|---|
| contrib/postgres_fdw/expected/foreign_recheck.out | 27 | 0 |
| contrib/postgres_fdw/Makefile | 8 | 0 |
| contrib/postgres_fdw/meson.build | 13 | 1 |
| contrib/postgres_fdw/postgres_fdw.c | 13 | 3 |
| contrib/postgres_fdw/specs/foreign_recheck.spec | 50 | 0 |
| src/backend/access/heap/heapam_handler.c | 3 | 0 |
From c2367bfb3e2b6e3b592b2bda08d5825cf2b95bd8 Mon Sep 17 00:00:00 2001
From: Masahiko Sawada <sawada.mshk@gmail.com>
Date: Thu, 7 Aug 2025 23:37:31 -0700
Subject: [PATCH v2] Fix assertion failure in postgresGetForeignJoinPaths().
---
contrib/postgres_fdw/Makefile | 8 +++
.../postgres_fdw/expected/foreign_recheck.out | 27 ++++++++++
contrib/postgres_fdw/meson.build | 14 +++++-
contrib/postgres_fdw/postgres_fdw.c | 16 ++++--
.../postgres_fdw/specs/foreign_recheck.spec | 50 +++++++++++++++++++
src/backend/access/heap/heapam_handler.c | 3 ++
6 files changed, 114 insertions(+), 4 deletions(-)
create mode 100644 contrib/postgres_fdw/expected/foreign_recheck.out
create mode 100644 contrib/postgres_fdw/specs/foreign_recheck.spec
diff --git a/contrib/postgres_fdw/Makefile b/contrib/postgres_fdw/Makefile
index adfbd2ef758..fd1133f00dd 100644
--- a/contrib/postgres_fdw/Makefile
+++ b/contrib/postgres_fdw/Makefile
@@ -28,5 +28,13 @@ SHLIB_PREREQS = submake-libpq
subdir = contrib/postgres_fdw
top_builddir = ../..
include $(top_builddir)/src/Makefile.global
+
+# We have the sole isolation test case that requires injection points,
+# so invoke the isolation test if injection points are enabled.
+ifeq ($(enable_injection_points),yes)
+EXTRA_INSTALL=src/test/modules/injection_points
+ISOLATION = foreign_recheck
+endif
+
include $(top_srcdir)/contrib/contrib-global.mk
endif
diff --git a/contrib/postgres_fdw/expected/foreign_recheck.out b/contrib/postgres_fdw/expected/foreign_recheck.out
new file mode 100644
index 00000000000..bab3b61c0c2
--- /dev/null
+++ b/contrib/postgres_fdw/expected/foreign_recheck.out
@@ -0,0 +1,27 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s0_lock s1_update s1_wakeup
+injection_points_attach
+-----------------------
+
+(1 row)
+
+step s0_lock:
+ SELECT
+ (SELECT 1 FROM sch1.b AS b, sch1.c AS c WHERE a.i = b.i AND b.i = c.i)
+ FROM sch1.a as a
+ FOR UPDATE;
+ <waiting ...>
+step s1_update: UPDATE sch1.a SET i = i + 100;
+step s1_wakeup: SELECT injection_points_wakeup('heapam_lock_tuple-before-lock');
+injection_points_wakeup
+-----------------------
+
+(1 row)
+
+step s0_lock: <... completed>
+?column?
+--------
+
+(1 row)
+
diff --git a/contrib/postgres_fdw/meson.build b/contrib/postgres_fdw/meson.build
index 5c11bc6496f..6dc40d7f1af 100644
--- a/contrib/postgres_fdw/meson.build
+++ b/contrib/postgres_fdw/meson.build
@@ -30,7 +30,7 @@ install_data(
kwargs: contrib_data_args,
)
-tests += {
+postgres_fdw_test = {
'name': 'postgres_fdw',
'sd': meson.current_source_dir(),
'bd': meson.current_build_dir(),
@@ -47,3 +47,15 @@ tests += {
],
},
}
+
+if get_option('injection_points')
+ postgres_fdw_test += {
+ 'isolation': {
+ 'specs': [
+ 'foreign_recheck',
+ ]
+ }
+ }
+endif
+
+tests += postgres_fdw_test
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 456b267f70b..23c1b956048 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -6276,6 +6276,7 @@ postgresGetForeignJoinPaths(PlannerInfo *root,
Cost total_cost;
Path *epq_path; /* Path to create plan to be executed when
* EvalPlanQual gets triggered. */
+ bool need_epq = false;
/*
* Skip if this join combination has been considered already.
@@ -6313,9 +6314,18 @@ postgresGetForeignJoinPaths(PlannerInfo *root,
* calling foreign_join_ok(), since that function updates fpinfo and marks
* it as pushable if the join is found to be pushable.
*/
- if (root->parse->commandType == CMD_DELETE ||
- root->parse->commandType == CMD_UPDATE ||
- root->rowMarks)
+ for (PlannerInfo *proot = root; proot != NULL; proot = proot->parent_root)
+ {
+ if (proot->parse->commandType == CMD_DELETE ||
+ proot->parse->commandType == CMD_UPDATE ||
+ proot->rowMarks)
+ {
+ need_epq = true;
+ break;
+ }
+ }
+
+ if (need_epq)
{
epq_path = GetExistingLocalJoinPath(joinrel);
if (!epq_path)
diff --git a/contrib/postgres_fdw/specs/foreign_recheck.spec b/contrib/postgres_fdw/specs/foreign_recheck.spec
new file mode 100644
index 00000000000..9cc48e6a48c
--- /dev/null
+++ b/contrib/postgres_fdw/specs/foreign_recheck.spec
@@ -0,0 +1,50 @@
+setup
+{
+ CREATE EXTENSION injection_points;
+ CREATE EXTENSION postgres_fdw;
+ DO $d$
+ BEGIN
+ EXECUTE $$CREATE SERVER loopback FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '$$||current_database()||$$',
+ port '$$||current_setting('port')||$$'
+ )$$;
+ END;
+ $d$;
+ CREATE USER MAPPING FOR public SERVER loopback;
+
+ CREATE SCHEMA sch1;
+ CREATE TABLE sch1.a (i int);
+ CREATE FOREIGN TABLE sch1.b (i int) SERVER loopback OPTIONS (schema_name 'sch2', table_name 'b');
+ CREATE FOREIGN TABLE sch1.c (i int) SERVER loopback OPTIONS (schema_name 'sch2', table_name 'c');
+
+ CREATE SCHEMA sch2;
+ CREATE TABLE sch2.b (i int);
+ CREATE TABLE sch2.c (i int);
+
+ INSERT INTO sch1.a VALUES (1);
+ INSERT INTO sch2.b VALUES (1);
+ INSERT INTO sch2.c VALUES (1);
+}
+
+session "s0"
+setup {
+ SELECT injection_points_set_local();
+ SELECT injection_points_attach('heapam_lock_tuple-before-lock', 'wait');
+}
+step "s0_lock" {
+ SELECT
+ (SELECT 1 FROM sch1.b AS b, sch1.c AS c WHERE a.i = b.i AND b.i = c.i)
+ FROM sch1.a as a
+ FOR UPDATE;
+}
+
+session "s1"
+step "s1_update" { UPDATE sch1.a SET i = i + 100; }
+step "s1_wakeup" { SELECT injection_points_wakeup('heapam_lock_tuple-before-lock'); }
+
+# "s0_lock" execute a FOR UPDATE query but it stops before locking the result
+# tuple because of the injection point. "s1_update" updates the same tuple
+# concurrently and we wake up the session "s0", resulting that the FOR UPDATE
+# query rechecks the tuple via EPQ. Verify that postgresRecheckForeignScan()
+# function can check the tuple properly.
+permutation "s0_lock" "s1_update" "s1_wakeup"
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index cb4bc35c93e..45b1cafc01d 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -44,6 +44,7 @@
#include "storage/procarray.h"
#include "storage/smgr.h"
#include "utils/builtins.h"
+#include "utils/injection_point.h"
#include "utils/rel.h"
static void reform_and_rewrite_tuple(HeapTuple tuple,
@@ -375,6 +376,8 @@ heapam_tuple_lock(Relation relation, ItemPointer tid, Snapshot snapshot,
Assert(TTS_IS_BUFFERTUPLE(slot));
+ INJECTION_POINT("heapam_lock_tuple-before-lock", NULL);
+
tuple_lock_retry:
tuple->t_self = *tid;
result = heap_lock_tuple(relation, tuple, cid, mode, wait_policy,
--
2.39.5 (Apple Git-154)