diff --git a/contrib/postgres_fdw/.gitignore b/contrib/postgres_fdw/.gitignore index 5dcb3ff9723..b4903eba657 100644 --- a/contrib/postgres_fdw/.gitignore +++ b/contrib/postgres_fdw/.gitignore @@ -1,4 +1,6 @@ # Generated subdirectories /log/ /results/ +/output_iso/ /tmp_check/ +/tmp_check_iso/ diff --git a/contrib/postgres_fdw/Makefile b/contrib/postgres_fdw/Makefile index adfbd2ef758..8eaf4d263b6 100644 --- a/contrib/postgres_fdw/Makefile +++ b/contrib/postgres_fdw/Makefile @@ -17,6 +17,8 @@ EXTENSION = postgres_fdw DATA = postgres_fdw--1.0.sql postgres_fdw--1.0--1.1.sql postgres_fdw--1.1--1.2.sql REGRESS = postgres_fdw query_cancel +ISOLATION = eval_plan_qual +ISOLATION_OPTS = --load-extension=postgres_fdw TAP_TESTS = 1 ifdef USE_PGXS 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..f3e3a22b336 --- /dev/null +++ b/contrib/postgres_fdw/expected/eval_plan_qual.out @@ -0,0 +1,37 @@ +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: + -- Verify if the sub-select has a foreign-join plan + EXPLAIN (VERBOSE, COSTS OFF) + SELECT a.i, + (SELECT 1 FROM fb, fc WHERE a.i = fb.i AND fb.i = fc.i) + FROM a FOR UPDATE; + SELECT a.i, + (SELECT 1 FROM fb, fc WHERE a.i = fb.i AND fb.i = fc.i) + FROM a FOR UPDATE; + +step s0_commit: COMMIT; +step s1_tuplock: <... completed> +QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------- +LockRows + Output: a.i, ((SubPlan expr_1)), a.ctid + -> Seq Scan on public.a + Output: a.i, (SubPlan expr_1), a.ctid + SubPlan expr_1 + -> Foreign Scan + Output: 1 + Relations: (public.fb) INNER JOIN (public.fc) + Remote SQL: SELECT NULL FROM (public.b r1 INNER JOIN public.c r2 ON (((r2.i = $1::integer)) AND ((r1.i = $1::integer)))) +(9 rows) + +i|?column? +-+-------- +2| +(1 row) + +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..30a83e04058 --- /dev/null +++ b/contrib/postgres_fdw/specs/eval_plan_qual.spec @@ -0,0 +1,55 @@ +# 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_update { UPDATE a SET i = i + 1; } +step s0_commit { COMMIT; } + +session s1 +step s1_begin { BEGIN ISOLATION LEVEL READ COMMITTED; } +step s1_tuplock { + -- Verify if the sub-select has a foreign-join plan + EXPLAIN (VERBOSE, COSTS OFF) + SELECT a.i, + (SELECT 1 FROM fb, fc WHERE a.i = fb.i AND fb.i = fc.i) + FROM a FOR UPDATE; + SELECT a.i, + (SELECT 1 FROM fb, fc WHERE a.i = fb.i AND fb.i = fc.i) + FROM 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..2003cbc7ed5 100644 --- a/src/include/executor/execScan.h +++ b/src/include/executor/execScan.h @@ -49,16 +49,24 @@ 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]) {