diff options
author | Etsuro Fujita <efujita@postgresql.org> | 2022-01-27 16:15:02 +0900 |
---|---|---|
committer | Etsuro Fujita <efujita@postgresql.org> | 2022-01-27 16:15:02 +0900 |
commit | d1cca9441dc488b9e4d82cf34c9cacc998e6470f (patch) | |
tree | 148b18cf12b6ffc08604873505e4701023c4a551 | |
parent | d94a95cce9eb8582f3bb48fd745f21749b317e83 (diff) | |
download | postgresql-d1cca9441dc488b9e4d82cf34c9cacc998e6470f.tar.gz postgresql-d1cca9441dc488b9e4d82cf34c9cacc998e6470f.zip |
postgres_fdw: Fix handling of a pending asynchronous request in postgresReScanForeignScan().
Commit 27e1f1456 failed to process a pending asynchronous request made
for a given ForeignScan node in postgresReScanForeignScan() (if any) in
cases where we would only reset the next_tuple counter in that function,
contradicting the assumption that there should be no pending
asynchronous requests that have been made for async-capable subplans for
the parent Append node after ReScan. This led to an assert failure in
an assert-enabled build. I think this would also lead to mis-rewinding
the cursor in that function in the case where we have already fetched
one batch for the ForeignScan node and the asynchronous request has been
made for the second batch, because even in that case we would just reset
the counter when called from that function, so we would fail to execute
MOVE BACKWARD ALL.
To fix, modify that function to process the asynchronous request before
restarting the scan.
While at it, add a comment to a function to match other places.
Per bug #17344 from Alexander Lakhin. Back-patch to v14 where the
aforesaid commit came in.
Patch by me. Test case by Alexander Lakhin, adjusted by me. Reviewed
and tested by Alexander Lakhin and Dmitry Dolgov.
Discussion: https://postgr.es/m/17344-226b78b00de73a7e@postgresql.org
-rw-r--r-- | contrib/postgres_fdw/expected/postgres_fdw.out | 37 | ||||
-rw-r--r-- | contrib/postgres_fdw/postgres_fdw.c | 14 | ||||
-rw-r--r-- | contrib/postgres_fdw/sql/postgres_fdw.sql | 17 |
3 files changed, 68 insertions, 0 deletions
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index b62337bd970..fc8f8bae6f0 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -10657,6 +10657,43 @@ DROP TABLE base_tbl1; DROP TABLE base_tbl2; DROP TABLE result_tbl; DROP TABLE join_tbl; +-- Test that an asynchronous fetch is processed before restarting the scan in +-- ReScanForeignScan +CREATE TABLE base_tbl (a int, b int); +INSERT INTO base_tbl VALUES (1, 11), (2, 22), (3, 33); +CREATE FOREIGN TABLE foreign_tbl (b int) + SERVER loopback OPTIONS (table_name 'base_tbl'); +CREATE FOREIGN TABLE foreign_tbl2 () INHERITS (foreign_tbl) + SERVER loopback OPTIONS (table_name 'base_tbl'); +EXPLAIN (VERBOSE, COSTS OFF) +SELECT a FROM base_tbl WHERE a IN (SELECT a FROM foreign_tbl); + QUERY PLAN +----------------------------------------------------------------------------- + Seq Scan on public.base_tbl + Output: base_tbl.a + Filter: (SubPlan 1) + SubPlan 1 + -> Result + Output: base_tbl.a + -> Append + -> Async Foreign Scan on public.foreign_tbl foreign_tbl_1 + Remote SQL: SELECT NULL FROM public.base_tbl + -> Async Foreign Scan on public.foreign_tbl2 foreign_tbl_2 + Remote SQL: SELECT NULL FROM public.base_tbl +(11 rows) + +SELECT a FROM base_tbl WHERE a IN (SELECT a FROM foreign_tbl); + a +--- + 1 + 2 + 3 +(3 rows) + +-- Clean up +DROP FOREIGN TABLE foreign_tbl CASCADE; +NOTICE: drop cascades to foreign table foreign_tbl2 +DROP TABLE base_tbl; ALTER SERVER loopback OPTIONS (DROP async_capable); ALTER SERVER loopback2 OPTIONS (DROP async_capable); -- =================================================================== diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c index 24fba33c3c6..bf69f9d1da0 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -1652,6 +1652,18 @@ postgresReScanForeignScan(ForeignScanState *node) return; /* + * If the node is async-capable, and an asynchronous fetch for it has been + * begun, the asynchronous fetch might not have yet completed. Check if + * the node is async-capable, and an asynchronous fetch for it is still in + * progress; if so, complete the asynchronous fetch before restarting the + * scan. + */ + if (fsstate->async_capable && + fsstate->conn_state->pendingAreq && + fsstate->conn_state->pendingAreq->requestee == (PlanState *) node) + fetch_more_data(node); + + /* * If any internal parameters affecting this node have changed, we'd * better destroy and recreate the cursor. Otherwise, rewinding it should * be good enough. If we've only fetched zero or one batch, we needn't @@ -7003,6 +7015,8 @@ produce_tuple_asynchronously(AsyncRequest *areq, bool fetch) ExecAsyncRequestDone(areq, result); return; } + + /* We must have run out of tuples */ Assert(fsstate->next_tuple >= fsstate->num_tuples); /* Fetch some more tuples, if we've not detected EOF yet */ diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql index ee9ab37d565..da004397eda 100644 --- a/contrib/postgres_fdw/sql/postgres_fdw.sql +++ b/contrib/postgres_fdw/sql/postgres_fdw.sql @@ -3379,6 +3379,23 @@ DROP TABLE base_tbl2; DROP TABLE result_tbl; DROP TABLE join_tbl; +-- Test that an asynchronous fetch is processed before restarting the scan in +-- ReScanForeignScan +CREATE TABLE base_tbl (a int, b int); +INSERT INTO base_tbl VALUES (1, 11), (2, 22), (3, 33); +CREATE FOREIGN TABLE foreign_tbl (b int) + SERVER loopback OPTIONS (table_name 'base_tbl'); +CREATE FOREIGN TABLE foreign_tbl2 () INHERITS (foreign_tbl) + SERVER loopback OPTIONS (table_name 'base_tbl'); + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT a FROM base_tbl WHERE a IN (SELECT a FROM foreign_tbl); +SELECT a FROM base_tbl WHERE a IN (SELECT a FROM foreign_tbl); + +-- Clean up +DROP FOREIGN TABLE foreign_tbl CASCADE; +DROP TABLE base_tbl; + ALTER SERVER loopback OPTIONS (DROP async_capable); ALTER SERVER loopback2 OPTIONS (DROP async_capable); |