From 99f332fc9a5ad546a0660cd6a7a4cdb76369c79f Mon Sep 17 00:00:00 2001 From: Masahiko Sawada Date: Tue, 23 Sep 2025 12:23:16 -0700 Subject: [PATCH v5] postgres_fdw: Fix assertion failure when EvalPlanQual with a SubLink sub-select. Reported-by: Kristian Lejao Author: Masahiko Sawada Reviewed-by: Etsuro Fujita Reviewed-by: Michael Paquier Discussion: https://postgr.es/m/CAD21AoBpo6Gx55FBOW+9s5X=nUw3Xpq64v35fpDEKsTERnc4TQ@mail.gmail.com --- contrib/postgres_fdw/Makefile | 3 + .../postgres_fdw/expected/eval_plan_qual.out | 25 ++++++++ contrib/postgres_fdw/meson.build | 6 ++ .../postgres_fdw/specs/eval_plan_qual.spec | 58 +++++++++++++++++++ src/include/executor/execScan.h | 23 +++++--- 5 files changed, 107 insertions(+), 8 deletions(-) create mode 100644 contrib/postgres_fdw/expected/eval_plan_qual.out create mode 100644 contrib/postgres_fdw/specs/eval_plan_qual.spec diff --git a/contrib/postgres_fdw/Makefile b/contrib/postgres_fdw/Makefile index adfbd2ef758..81df6ca9085 100644 --- a/contrib/postgres_fdw/Makefile +++ b/contrib/postgres_fdw/Makefile @@ -16,6 +16,9 @@ SHLIB_LINK_INTERNAL = $(libpq) EXTENSION = postgres_fdw DATA = postgres_fdw--1.0.sql postgres_fdw--1.0--1.1.sql postgres_fdw--1.1--1.2.sql +ISOLATION = eval_plan_qual +ISOLATION_OPTS = --load-extension=postgres_fdw + REGRESS = postgres_fdw query_cancel TAP_TESTS = 1 diff --git a/contrib/postgres_fdw/expected/eval_plan_qual.out b/contrib/postgres_fdw/expected/eval_plan_qual.out new file mode 100644 index 00000000000..385b1a03981 --- /dev/null +++ b/contrib/postgres_fdw/expected/eval_plan_qual.out @@ -0,0 +1,25 @@ +Parsed test spec with 2 sessions + +starting permutation: s0_begin s0_update s1_begin s1_tuplock s0_commit s1_commit +step s0_begin: BEGIN ISOLATION LEVEL READ COMMITTED; +step s0_update: UPDATE a SET i = i + 1; +step s1_begin: BEGIN ISOLATION LEVEL READ COMMITTED; +step s1_tuplock: + EXPLAIN (COSTS OFF, ANALYZE ON, TIMING OFF, SUMMARY OFF, BUFFERS OFF) + SELECT a.i, + (SELECT 1 FROM fb, fc WHERE a.i = fb.i AND fb.i = fc.i) + FROM a as a + FOR UPDATE; + +step s0_commit: COMMIT; +step s1_tuplock: <... completed> +QUERY PLAN +----------------------------------------------------- +LockRows (actual rows=1.00 loops=1) + -> Seq Scan on a (actual rows=1.00 loops=1) + SubPlan 1 + -> Foreign Scan (actual rows=1.00 loops=1) + Relations: (fb) INNER JOIN (fc) +(5 rows) + +step s1_commit: COMMIT; diff --git a/contrib/postgres_fdw/meson.build b/contrib/postgres_fdw/meson.build index 5c11bc6496f..aac89ffdde8 100644 --- a/contrib/postgres_fdw/meson.build +++ b/contrib/postgres_fdw/meson.build @@ -41,6 +41,12 @@ tests += { ], 'regress_args': ['--dlpath', meson.project_build_root() / 'src/test/regress'], }, + 'isolation': { + 'specs': [ + 'eval_plan_qual', + ], + 'regress_args': ['--load-extension=postgres_fdw'], + }, 'tap': { 'tests': [ 't/001_auth_scram.pl', diff --git a/contrib/postgres_fdw/specs/eval_plan_qual.spec b/contrib/postgres_fdw/specs/eval_plan_qual.spec new file mode 100644 index 00000000000..6552bd605f4 --- /dev/null +++ b/contrib/postgres_fdw/specs/eval_plan_qual.spec @@ -0,0 +1,58 @@ +# Tests for the EvalPlanQual mechanism involving foreign tables + +setup +{ + 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 TABLE a (i int); + CREATE TABLE b (i int); + CREATE TABLE c (i int); + CREATE FOREIGN TABLE fb (i int) SERVER loopback OPTIONS (table_name 'b'); + CREATE FOREIGN TABLE fc (i int) SERVER loopback OPTIONS (table_name 'c'); + + INSERT INTO a VALUES (1); + INSERT INTO b VALUES (1); + INSERT INTO c VALUES (1); +} + +teardown +{ + DROP TABLE a; + DROP TABLE b; + DROP TABLE c; + DROP SERVER loopback CASCADE; +} + +session s0 +step s0_begin { BEGIN ISOLATION LEVEL READ COMMITTED; } +step s0_commit { COMMIT; } +step s0_update { UPDATE a SET i = i + 1; } + + +session s1 +step s1_begin { BEGIN ISOLATION LEVEL READ COMMITTED; } +step s1_tuplock { + -- Verify if the sub-select has a foreign-join + EXPLAIN (VERBOSE, COSTS OFF) + SELECT a.i, + (SELECT 1 FROM fb, fc WHERE a.i = fb.i AND fb.i = fc.i) + FROM a as a + FOR UPDATE; + SELECT a.i, + (SELECT 1 FROM fb, fc WHERE a.i = fb.i AND fb.i = fc.i) + FROM a as a + FOR UPDATE; +} +step s1_commit { COMMIT; } + +# This test exercises EvalPlanQual with a SubLink sub-select (which should +# be unaffected by any EPQ recheck behavior in the outer query) +permutation s0_begin s0_update s1_begin s1_tuplock s0_commit s1_commit diff --git a/src/include/executor/execScan.h b/src/include/executor/execScan.h index 837ea7785bb..95866e13ed4 100644 --- a/src/include/executor/execScan.h +++ b/src/include/executor/execScan.h @@ -49,16 +49,23 @@ ExecScanFetch(ScanState *node, { /* * This is a ForeignScan or CustomScan which has pushed down a - * join to the remote side. The recheck method is responsible not - * only for rechecking the scan/join quals but also for storing - * the correct tuple in the slot. + * join to the remote side. If it is a descendant node in the EPQ + * recheck plan tree, run the recheck method function. Otherwise, + * run the access method function below. */ + if (bms_is_member(epqstate->epqParam, node->ps.plan->extParam)) + { + /* + * The recheck method is responsible not only for rechecking + * the scan/join quals but also for storing the correct tuple + * in the slot. + */ + TupleTableSlot *slot = node->ss_ScanTupleSlot; - TupleTableSlot *slot = node->ss_ScanTupleSlot; - - if (!(*recheckMtd) (node, slot)) - ExecClearTuple(slot); /* would not be returned by scan */ - return slot; + if (!(*recheckMtd) (node, slot)) + ExecClearTuple(slot); /* would not be returned by scan */ + return slot; + } } else if (epqstate->relsubs_done[scanrelid - 1]) { -- 2.47.3