0001-Add-DECLARE-CURSOR-.-FETCH-ALL-and-use-it-in-postgre.patch
text/x-diff
Filename: 0001-Add-DECLARE-CURSOR-.-FETCH-ALL-and-use-it-in-postgre.patch
Type: text/x-diff
Part: 0
Message:
postgres_fdw prefers fast plans
From cfb91702532fd30a1efefefe442dd8bc969b1b02 Mon Sep 17 00:00:00 2001
From: Alexander Pyhalov <a.pyhalov@postgrespro.ru>
Date: Thu, 2 Feb 2023 12:26:58 +0300
Subject: [PATCH] Add DECLARE CURSOR ... FETCH ALL and use it in postgres_fdw
Usual cursors are optimized for fast fetching of the first rows.
However, when we use them in postgres_fdw, we don't rely on
any guidance from user. In bad scenarios, this leads to
choosing nest loop joins and other suboptimal plans. So,
introduce FETCH ALL cursors and use it in postgres_fdw.
---
.../postgres_fdw/expected/postgres_fdw.out | 320 ++++++++++--------
contrib/postgres_fdw/postgres_fdw.c | 11 +-
contrib/postgres_fdw/sql/postgres_fdw.sql | 24 +-
src/backend/parser/gram.y | 5 +-
src/test/regress/expected/portals.out | 49 +++
src/test/regress/sql/portals.sql | 27 ++
6 files changed, 281 insertions(+), 155 deletions(-)
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 48e3185b227..bea5cb5904b 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -2505,61 +2505,71 @@ ANALYZE local_tbl;
SET enable_nestloop TO false;
SET enable_hashjoin TO false;
EXPLAIN (VERBOSE, COSTS OFF)
+WITH s AS MATERIALIZED (
SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1
- AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE;
- QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- LockRows
- Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3, local_tbl.c1, local_tbl.c2, local_tbl.c3, ft1.*, ft2.*, ft4.*, ft5.*, local_tbl.ctid
- -> Merge Join
- Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3, local_tbl.c1, local_tbl.c2, local_tbl.c3, ft1.*, ft2.*, ft4.*, ft5.*, local_tbl.ctid
- Inner Unique: true
- Merge Cond: (ft1.c2 = local_tbl.c1)
- -> Foreign Scan
- Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*, ft4.c1, ft4.c2, ft4.c3, ft4.*, ft5.c1, ft5.c2, ft5.c3, ft5.*
- Relations: (((public.ft1) INNER JOIN (public.ft2)) INNER JOIN (public.ft4)) INNER JOIN (public.ft5)
- Remote SQL: SELECT r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END, r3.c1, r3.c2, r3.c3, CASE WHEN (r3.*)::text IS NOT NULL THEN ROW(r3.c1, r3.c2, r3.c3) END, r4.c1, r4.c2, r4.c3, CASE WHEN (r4.*)::text IS NOT NULL THEN ROW(r4.c1, r4.c2, r4.c3) END FROM ((("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r2."C 1" = r1."C 1")) AND ((r2."C 1" < 100)) AND ((r1."C 1" < 100)))) INNER JOIN "S 1"."T 3" r3 ON (((r1.c2 = r3.c1)))) INNER JOIN "S 1"."T 4" r4 ON (((r1.c2 = r4.c1)))) ORDER BY r1.c2 ASC NULLS LAST FOR UPDATE OF r1 FOR UPDATE OF r2 FOR UPDATE OF r3 FOR UPDATE OF r4
- -> Merge Join
- Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*, ft4.c1, ft4.c2, ft4.c3, ft4.*, ft5.c1, ft5.c2, ft5.c3, ft5.*
- Merge Cond: (ft1.c2 = ft5.c1)
- -> Merge Join
- Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*, ft4.c1, ft4.c2, ft4.c3, ft4.*
- Merge Cond: (ft1.c2 = ft4.c1)
- -> Sort
- Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*
- Sort Key: ft1.c2
- -> Merge Join
- Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*
- Merge Cond: (ft1.c1 = ft2.c1)
- -> Sort
- Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*
- Sort Key: ft1.c1
- -> Foreign Scan on public.ft1
- Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*
- Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) FOR UPDATE
- -> Materialize
- Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*
- -> Foreign Scan on public.ft2
- Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*
- Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) ORDER BY "C 1" ASC NULLS LAST FOR UPDATE
- -> Sort
- Output: ft4.c1, ft4.c2, ft4.c3, ft4.*
- Sort Key: ft4.c1
- -> Foreign Scan on public.ft4
- Output: ft4.c1, ft4.c2, ft4.c3, ft4.*
- Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3" FOR UPDATE
- -> Sort
- Output: ft5.c1, ft5.c2, ft5.c3, ft5.*
- Sort Key: ft5.c1
- -> Foreign Scan on public.ft5
- Output: ft5.c1, ft5.c2, ft5.c3, ft5.*
- Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4" FOR UPDATE
- -> Index Scan using local_tbl_pkey on public.local_tbl
- Output: local_tbl.c1, local_tbl.c2, local_tbl.c3, local_tbl.ctid
-(47 rows)
-
+ AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE)
+SELECT * FROM s ORDER BY 1;
+ QUERY PLAN
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Sort
+ Output: s.c1, s.c2, s.c3, s.c4, s.c5, s.c6, s.c7, s.c8, s.c1_1, s.c2_1, s.c3_1, s.c4_1, s.c5_1, s.c6_1, s.c7_1, s.c8_1, s.c1_2, s.c2_2, s.c3_2, s.c1_3, s.c2_3, s.c3_3, s.c1_4, s.c2_4, s.c3_4
+ Sort Key: s.c1
+ CTE s
+ -> LockRows
+ Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3, local_tbl.c1, local_tbl.c2, local_tbl.c3, ft1.*, ft2.*, ft4.*, ft5.*, local_tbl.ctid
+ -> Merge Join
+ Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3, local_tbl.c1, local_tbl.c2, local_tbl.c3, ft1.*, ft2.*, ft4.*, ft5.*, local_tbl.ctid
+ Inner Unique: true
+ Merge Cond: (ft1.c2 = local_tbl.c1)
+ -> Foreign Scan
+ Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*, ft4.c1, ft4.c2, ft4.c3, ft4.*, ft5.c1, ft5.c2, ft5.c3, ft5.*
+ Relations: (((public.ft1) INNER JOIN (public.ft2)) INNER JOIN (public.ft4)) INNER JOIN (public.ft5)
+ Remote SQL: SELECT r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END, r3.c1, r3.c2, r3.c3, CASE WHEN (r3.*)::text IS NOT NULL THEN ROW(r3.c1, r3.c2, r3.c3) END, r4.c1, r4.c2, r4.c3, CASE WHEN (r4.*)::text IS NOT NULL THEN ROW(r4.c1, r4.c2, r4.c3) END FROM ((("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r2."C 1" = r1."C 1")) AND ((r2."C 1" < 100)) AND ((r1."C 1" < 100)))) INNER JOIN "S 1"."T 3" r3 ON (((r1.c2 = r3.c1)))) INNER JOIN "S 1"."T 4" r4 ON (((r1.c2 = r4.c1)))) ORDER BY r1.c2 ASC NULLS LAST FOR UPDATE OF r1 FOR UPDATE OF r2 FOR UPDATE OF r3 FOR UPDATE OF r4
+ -> Merge Join
+ Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*, ft4.c1, ft4.c2, ft4.c3, ft4.*, ft5.c1, ft5.c2, ft5.c3, ft5.*
+ Merge Cond: (ft1.c2 = ft5.c1)
+ -> Merge Join
+ Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*, ft4.c1, ft4.c2, ft4.c3, ft4.*
+ Merge Cond: (ft1.c2 = ft4.c1)
+ -> Sort
+ Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*
+ Sort Key: ft1.c2
+ -> Merge Join
+ Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*
+ Merge Cond: (ft1.c1 = ft2.c1)
+ -> Sort
+ Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*
+ Sort Key: ft1.c1
+ -> Foreign Scan on public.ft1
+ Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*
+ Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) FOR UPDATE
+ -> Materialize
+ Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*
+ -> Foreign Scan on public.ft2
+ Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*
+ Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) ORDER BY "C 1" ASC NULLS LAST FOR UPDATE
+ -> Sort
+ Output: ft4.c1, ft4.c2, ft4.c3, ft4.*
+ Sort Key: ft4.c1
+ -> Foreign Scan on public.ft4
+ Output: ft4.c1, ft4.c2, ft4.c3, ft4.*
+ Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3" FOR UPDATE
+ -> Sort
+ Output: ft5.c1, ft5.c2, ft5.c3, ft5.*
+ Sort Key: ft5.c1
+ -> Foreign Scan on public.ft5
+ Output: ft5.c1, ft5.c2, ft5.c3, ft5.*
+ Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4" FOR UPDATE
+ -> Index Scan using local_tbl_pkey on public.local_tbl
+ Output: local_tbl.c1, local_tbl.c2, local_tbl.c3, local_tbl.ctid
+ -> CTE Scan on s
+ Output: s.c1, s.c2, s.c3, s.c4, s.c5, s.c6, s.c7, s.c8, s.c1_1, s.c2_1, s.c3_1, s.c4_1, s.c5_1, s.c6_1, s.c7_1, s.c8_1, s.c1_2, s.c2_2, s.c3_2, s.c1_3, s.c2_3, s.c3_3, s.c1_4, s.c2_4, s.c3_4
+(53 rows)
+
+WITH s AS MATERIALIZED (
SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1
- AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE;
+ AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE)
+SELECT * FROM s ORDER BY 1;
c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c1 | c2 | c3 | c1 | c2 | c3 | c1 | c2 | c3
----+----+-------+------------------------------+--------------------------+----+------------+-----+----+----+-------+------------------------------+--------------------------+----+------------+-----+----+----+--------+----+----+--------+----+----+------
6 | 6 | 00006 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 6 | 6 | 00006 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006
@@ -2575,67 +2585,77 @@ SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = f
(10 rows)
EXPLAIN (VERBOSE, COSTS OFF)
+WITH s AS MATERIALIZED (
SELECT * FROM ft1, ft4, ft5, local_tbl WHERE ft1.c1 = ft4.c1 AND ft1.c1 = ft5.c1
- AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft5.c1 < 100 FOR UPDATE;
- QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- LockRows
- Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3, local_tbl.c1, local_tbl.c2, local_tbl.c3, ft1.*, ft4.*, ft5.*, local_tbl.ctid
- -> Merge Join
- Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3, local_tbl.c1, local_tbl.c2, local_tbl.c3, ft1.*, ft4.*, ft5.*, local_tbl.ctid
- Merge Cond: (local_tbl.c1 = ft1.c2)
- -> Index Scan using local_tbl_pkey on public.local_tbl
- Output: local_tbl.c1, local_tbl.c2, local_tbl.c3, local_tbl.ctid
- -> Sort
- Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft4.c1, ft4.c2, ft4.c3, ft4.*, ft5.c1, ft5.c2, ft5.c3, ft5.*
- Sort Key: ft1.c2
- -> Foreign Scan
- Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft4.c1, ft4.c2, ft4.c3, ft4.*, ft5.c1, ft5.c2, ft5.c3, ft5.*
- Relations: ((public.ft1) INNER JOIN (public.ft4)) INNER JOIN (public.ft5)
- Remote SQL: SELECT r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, r2.c1, r2.c2, r2.c3, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2.c1, r2.c2, r2.c3) END, r3.c1, r3.c2, r3.c3, CASE WHEN (r3.*)::text IS NOT NULL THEN ROW(r3.c1, r3.c2, r3.c3) END FROM (("S 1"."T 1" r1 INNER JOIN "S 1"."T 3" r2 ON (((r1."C 1" = r2.c1)) AND ((r1."C 1" < 100)))) INNER JOIN "S 1"."T 4" r3 ON (((r1."C 1" = r3.c1)) AND ((r3.c1 < 100)))) FOR UPDATE OF r1 FOR UPDATE OF r2 FOR UPDATE OF r3
- -> Merge Join
- Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft4.c1, ft4.c2, ft4.c3, ft4.*, ft5.c1, ft5.c2, ft5.c3, ft5.*
- Merge Cond: (ft1.c1 = ft5.c1)
- -> Merge Join
- Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft4.c1, ft4.c2, ft4.c3, ft4.*
- Merge Cond: (ft1.c1 = ft4.c1)
- -> Sort
- Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*
- Sort Key: ft1.c1
- -> Foreign Scan on public.ft1
- Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*
- Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) FOR UPDATE
- -> Sort
- Output: ft4.c1, ft4.c2, ft4.c3, ft4.*
- Sort Key: ft4.c1
- -> Foreign Scan on public.ft4
- Output: ft4.c1, ft4.c2, ft4.c3, ft4.*
- Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3" FOR UPDATE
- -> Sort
- Output: ft5.c1, ft5.c2, ft5.c3, ft5.*
- Sort Key: ft5.c1
- -> Foreign Scan on public.ft5
- Output: ft5.c1, ft5.c2, ft5.c3, ft5.*
- Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4" WHERE ((c1 < 100)) FOR UPDATE
-(38 rows)
-
+ AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft5.c1 < 100 FOR UPDATE)
+SELECT * FROM s ORDER BY 1;
+ QUERY PLAN
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Sort
+ Output: s.c1, s.c2, s.c3, s.c4, s.c5, s.c6, s.c7, s.c8, s.c1_1, s.c2_1, s.c3_1, s.c1_2, s.c2_2, s.c3_2, s.c1_3, s.c2_3, s.c3_3
+ Sort Key: s.c1
+ CTE s
+ -> LockRows
+ Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3, local_tbl.c1, local_tbl.c2, local_tbl.c3, ft1.*, ft4.*, ft5.*, local_tbl.ctid
+ -> Merge Join
+ Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3, local_tbl.c1, local_tbl.c2, local_tbl.c3, ft1.*, ft4.*, ft5.*, local_tbl.ctid
+ Merge Cond: (local_tbl.c1 = ft1.c2)
+ -> Index Scan using local_tbl_pkey on public.local_tbl
+ Output: local_tbl.c1, local_tbl.c2, local_tbl.c3, local_tbl.ctid
+ -> Sort
+ Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft4.c1, ft4.c2, ft4.c3, ft4.*, ft5.c1, ft5.c2, ft5.c3, ft5.*
+ Sort Key: ft1.c2
+ -> Foreign Scan
+ Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft4.c1, ft4.c2, ft4.c3, ft4.*, ft5.c1, ft5.c2, ft5.c3, ft5.*
+ Relations: ((public.ft1) INNER JOIN (public.ft4)) INNER JOIN (public.ft5)
+ Remote SQL: SELECT r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, r2.c1, r2.c2, r2.c3, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2.c1, r2.c2, r2.c3) END, r3.c1, r3.c2, r3.c3, CASE WHEN (r3.*)::text IS NOT NULL THEN ROW(r3.c1, r3.c2, r3.c3) END FROM (("S 1"."T 1" r1 INNER JOIN "S 1"."T 3" r2 ON (((r1."C 1" = r2.c1)) AND ((r1."C 1" < 100)))) INNER JOIN "S 1"."T 4" r3 ON (((r1."C 1" = r3.c1)) AND ((r3.c1 < 100)))) FOR UPDATE OF r1 FOR UPDATE OF r2 FOR UPDATE OF r3
+ -> Merge Join
+ Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft4.c1, ft4.c2, ft4.c3, ft4.*, ft5.c1, ft5.c2, ft5.c3, ft5.*
+ Merge Cond: (ft1.c1 = ft5.c1)
+ -> Merge Join
+ Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft4.c1, ft4.c2, ft4.c3, ft4.*
+ Merge Cond: (ft1.c1 = ft4.c1)
+ -> Sort
+ Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*
+ Sort Key: ft1.c1
+ -> Foreign Scan on public.ft1
+ Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*
+ Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) FOR UPDATE
+ -> Sort
+ Output: ft4.c1, ft4.c2, ft4.c3, ft4.*
+ Sort Key: ft4.c1
+ -> Foreign Scan on public.ft4
+ Output: ft4.c1, ft4.c2, ft4.c3, ft4.*
+ Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3" FOR UPDATE
+ -> Sort
+ Output: ft5.c1, ft5.c2, ft5.c3, ft5.*
+ Sort Key: ft5.c1
+ -> Foreign Scan on public.ft5
+ Output: ft5.c1, ft5.c2, ft5.c3, ft5.*
+ Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4" WHERE ((c1 < 100)) FOR UPDATE
+ -> CTE Scan on s
+ Output: s.c1, s.c2, s.c3, s.c4, s.c5, s.c6, s.c7, s.c8, s.c1_1, s.c2_1, s.c3_1, s.c1_2, s.c2_2, s.c3_2, s.c1_3, s.c2_3, s.c3_3
+(44 rows)
+
+WITH s AS MATERIALIZED (
SELECT * FROM ft1, ft4, ft5, local_tbl WHERE ft1.c1 = ft4.c1 AND ft1.c1 = ft5.c1
- AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft5.c1 < 100 FOR UPDATE;
+ AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft5.c1 < 100 FOR UPDATE)
+SELECT * FROM s ORDER BY 1;
c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c1 | c2 | c3 | c1 | c2 | c3 | c1 | c2 | c3
----+----+-------+------------------------------+--------------------------+----+------------+-----+----+----+--------+----+----+--------+----+----+------
+ 6 | 6 | 00006 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006
12 | 2 | 00012 | Tue Jan 13 00:00:00 1970 PST | Tue Jan 13 00:00:00 1970 | 2 | 2 | foo | 12 | 13 | AAA012 | 12 | 13 | AAA012 | 2 | 2 | 0002
- 42 | 2 | 00042 | Thu Feb 12 00:00:00 1970 PST | Thu Feb 12 00:00:00 1970 | 2 | 2 | foo | 42 | 43 | AAA042 | 42 | 43 | AAA042 | 2 | 2 | 0002
- 72 | 2 | 00072 | Sat Mar 14 00:00:00 1970 PST | Sat Mar 14 00:00:00 1970 | 2 | 2 | foo | 72 | 73 | AAA072 | 72 | 73 | | 2 | 2 | 0002
+ 18 | 8 | 00018 | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo | 18 | 19 | AAA018 | 18 | 19 | | 8 | 8 | 0008
24 | 4 | 00024 | Sun Jan 25 00:00:00 1970 PST | Sun Jan 25 00:00:00 1970 | 4 | 4 | foo | 24 | 25 | AAA024 | 24 | 25 | AAA024 | 4 | 4 | 0004
- 54 | 4 | 00054 | Tue Feb 24 00:00:00 1970 PST | Tue Feb 24 00:00:00 1970 | 4 | 4 | foo | 54 | 55 | AAA054 | 54 | 55 | | 4 | 4 | 0004
- 84 | 4 | 00084 | Thu Mar 26 00:00:00 1970 PST | Thu Mar 26 00:00:00 1970 | 4 | 4 | foo | 84 | 85 | AAA084 | 84 | 85 | AAA084 | 4 | 4 | 0004
- 96 | 6 | 00096 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 96 | 97 | AAA096 | 96 | 97 | AAA096 | 6 | 6 | 0006
36 | 6 | 00036 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 36 | 37 | AAA036 | 36 | 37 | | 6 | 6 | 0006
- 66 | 6 | 00066 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 66 | 67 | AAA066 | 66 | 67 | AAA066 | 6 | 6 | 0006
- 6 | 6 | 00006 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006
+ 42 | 2 | 00042 | Thu Feb 12 00:00:00 1970 PST | Thu Feb 12 00:00:00 1970 | 2 | 2 | foo | 42 | 43 | AAA042 | 42 | 43 | AAA042 | 2 | 2 | 0002
48 | 8 | 00048 | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo | 48 | 49 | AAA048 | 48 | 49 | AAA048 | 8 | 8 | 0008
- 18 | 8 | 00018 | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo | 18 | 19 | AAA018 | 18 | 19 | | 8 | 8 | 0008
+ 54 | 4 | 00054 | Tue Feb 24 00:00:00 1970 PST | Tue Feb 24 00:00:00 1970 | 4 | 4 | foo | 54 | 55 | AAA054 | 54 | 55 | | 4 | 4 | 0004
+ 66 | 6 | 00066 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 66 | 67 | AAA066 | 66 | 67 | AAA066 | 6 | 6 | 0006
+ 72 | 2 | 00072 | Sat Mar 14 00:00:00 1970 PST | Sat Mar 14 00:00:00 1970 | 2 | 2 | foo | 72 | 73 | AAA072 | 72 | 73 | | 2 | 2 | 0002
78 | 8 | 00078 | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo | 78 | 79 | AAA078 | 78 | 79 | AAA078 | 8 | 8 | 0008
+ 84 | 4 | 00084 | Thu Mar 26 00:00:00 1970 PST | Thu Mar 26 00:00:00 1970 | 4 | 4 | foo | 84 | 85 | AAA084 | 84 | 85 | AAA084 | 4 | 4 | 0004
+ 96 | 6 | 00096 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 96 | 97 | AAA096 | 96 | 97 | AAA096 | 6 | 6 | 0006
(13 rows)
RESET enable_nestloop;
@@ -6345,65 +6365,75 @@ UPDATE ft2 SET c3 = 'foo'
BEGIN;
EXPLAIN (verbose, costs off)
+ WITH s AS MATERIALIZED (
UPDATE ft2 SET c3 = 'bar'
FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1)
WHERE ft2.c1 > 1200 AND ft2.c2 = ft4.c1
- RETURNING old, new, ft2, ft2.*, ft4, ft4.*; -- can't be pushed down
- QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- Update on public.ft2
- Output: old.*, new.*, ft2.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft4.*, ft4.c1, ft4.c2, ft4.c3
- Remote SQL: UPDATE "S 1"."T 1" SET c3 = $2 WHERE ctid = $1 RETURNING "C 1", c2, c3, c4, c5, c6, c7, c8
- -> Foreign Scan
- Output: 'bar'::text, ft2.ctid, ft2.*, ft4.*, ft5.*, ft4.c1, ft4.c2, ft4.c3
- Relations: ((public.ft2) INNER JOIN (public.ft4)) INNER JOIN (public.ft5)
- Remote SQL: SELECT r1.ctid, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2.c1, r2.c2, r2.c3) END, CASE WHEN (r3.*)::text IS NOT NULL THEN ROW(r3.c1, r3.c2, r3.c3) END, r2.c1, r2.c2, r2.c3 FROM (("S 1"."T 1" r1 INNER JOIN "S 1"."T 3" r2 ON (((r1.c2 = r2.c1)) AND ((r1."C 1" > 1200)))) INNER JOIN "S 1"."T 4" r3 ON (((r2.c1 = r3.c1)))) FOR UPDATE OF r1
- -> Nested Loop
- Output: ft2.ctid, ft2.*, ft4.*, ft5.*, ft4.c1, ft4.c2, ft4.c3
- Join Filter: (ft4.c1 = ft5.c1)
- -> Sort
- Output: ft2.ctid, ft2.*, ft2.c2, ft4.*, ft4.c1, ft4.c2, ft4.c3
- Sort Key: ft2.c2
- -> Hash Join
- Output: ft2.ctid, ft2.*, ft2.c2, ft4.*, ft4.c1, ft4.c2, ft4.c3
- Hash Cond: (ft2.c2 = ft4.c1)
- -> Foreign Scan on public.ft2
- Output: ft2.ctid, ft2.*, ft2.c2
- Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" WHERE (("C 1" > 1200)) FOR UPDATE
- -> Hash
- Output: ft4.*, ft4.c1, ft4.c2, ft4.c3
- -> Foreign Scan on public.ft4
- Output: ft4.*, ft4.c1, ft4.c2, ft4.c3
- Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3"
- -> Materialize
- Output: ft5.*, ft5.c1
- -> Foreign Scan on public.ft5
- Output: ft5.*, ft5.c1
- Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4"
-(29 rows)
-
+ RETURNING old, new, ft2, ft2.*, ft4, ft4.* -- can't be pushed down
+ ) SELECT * FROM s ORDER BY 1;
+ QUERY PLAN
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Sort
+ Output: s.old, s.new, s.ft2, s.c1, s.c2, s.c3, s.c4, s.c5, s.c6, s.c7, s.c8, s.ft4, s.c1_1, s.c2_1, s.c3_1
+ Sort Key: s.old
+ CTE s
+ -> Update on public.ft2
+ Output: old.*, new.*, ft2.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft4.*, ft4.c1, ft4.c2, ft4.c3
+ Remote SQL: UPDATE "S 1"."T 1" SET c3 = $2 WHERE ctid = $1 RETURNING "C 1", c2, c3, c4, c5, c6, c7, c8
+ -> Foreign Scan
+ Output: 'bar'::text, ft2.ctid, ft2.*, ft4.*, ft5.*, ft4.c1, ft4.c2, ft4.c3
+ Relations: ((public.ft2) INNER JOIN (public.ft4)) INNER JOIN (public.ft5)
+ Remote SQL: SELECT r1.ctid, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2.c1, r2.c2, r2.c3) END, CASE WHEN (r3.*)::text IS NOT NULL THEN ROW(r3.c1, r3.c2, r3.c3) END, r2.c1, r2.c2, r2.c3 FROM (("S 1"."T 1" r1 INNER JOIN "S 1"."T 3" r2 ON (((r1.c2 = r2.c1)) AND ((r1."C 1" > 1200)))) INNER JOIN "S 1"."T 4" r3 ON (((r2.c1 = r3.c1)))) FOR UPDATE OF r1
+ -> Nested Loop
+ Output: ft2.ctid, ft2.*, ft4.*, ft5.*, ft4.c1, ft4.c2, ft4.c3
+ Join Filter: (ft4.c1 = ft5.c1)
+ -> Sort
+ Output: ft2.ctid, ft2.*, ft2.c2, ft4.*, ft4.c1, ft4.c2, ft4.c3
+ Sort Key: ft2.c2
+ -> Hash Join
+ Output: ft2.ctid, ft2.*, ft2.c2, ft4.*, ft4.c1, ft4.c2, ft4.c3
+ Hash Cond: (ft2.c2 = ft4.c1)
+ -> Foreign Scan on public.ft2
+ Output: ft2.ctid, ft2.*, ft2.c2
+ Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" WHERE (("C 1" > 1200)) FOR UPDATE
+ -> Hash
+ Output: ft4.*, ft4.c1, ft4.c2, ft4.c3
+ -> Foreign Scan on public.ft4
+ Output: ft4.*, ft4.c1, ft4.c2, ft4.c3
+ Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3"
+ -> Materialize
+ Output: ft5.*, ft5.c1
+ -> Foreign Scan on public.ft5
+ Output: ft5.*, ft5.c1
+ Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4"
+ -> CTE Scan on s
+ Output: s.old, s.new, s.ft2, s.c1, s.c2, s.c3, s.c4, s.c5, s.c6, s.c7, s.c8, s.ft4, s.c1_1, s.c2_1, s.c3_1
+(35 rows)
+
+ WITH s AS MATERIALIZED (
UPDATE ft2 SET c3 = 'bar'
FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1)
WHERE ft2.c1 > 1200 AND ft2.c2 = ft4.c1
- RETURNING old, new, ft2, ft2.*, ft4, ft4.*;
+ RETURNING old, new, ft2, ft2.*, ft4, ft4.*
+ ) SELECT * FROM s ORDER BY 1;
old | new | ft2 | c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | ft4 | c1 | c2 | c3
--------------------------------+--------------------------------+--------------------------------+------+----+-----+----+----+----+------------+----+----------------+----+----+--------
(1206,6,foo,,,,"ft2 ",) | (1206,6,bar,,,,"ft2 ",) | (1206,6,bar,,,,"ft2 ",) | 1206 | 6 | bar | | | | ft2 | | (6,7,AAA006) | 6 | 7 | AAA006
(1212,12,foo,,,,"ft2 ",) | (1212,12,bar,,,,"ft2 ",) | (1212,12,bar,,,,"ft2 ",) | 1212 | 12 | bar | | | | ft2 | | (12,13,AAA012) | 12 | 13 | AAA012
+ (1218,18,foo,,,,"ft2 ",) | (1218,18,bar,,,,"ft2 ",) | (1218,18,bar,,,,"ft2 ",) | 1218 | 18 | bar | | | | ft2 | | (18,19,AAA018) | 18 | 19 | AAA018
(1224,24,foo,,,,"ft2 ",) | (1224,24,bar,,,,"ft2 ",) | (1224,24,bar,,,,"ft2 ",) | 1224 | 24 | bar | | | | ft2 | | (24,25,AAA024) | 24 | 25 | AAA024
(1230,30,foo,,,,"ft2 ",) | (1230,30,bar,,,,"ft2 ",) | (1230,30,bar,,,,"ft2 ",) | 1230 | 30 | bar | | | | ft2 | | (30,31,AAA030) | 30 | 31 | AAA030
+ (1236,36,foo,,,,"ft2 ",) | (1236,36,bar,,,,"ft2 ",) | (1236,36,bar,,,,"ft2 ",) | 1236 | 36 | bar | | | | ft2 | | (36,37,AAA036) | 36 | 37 | AAA036
(1242,42,foo,,,,"ft2 ",) | (1242,42,bar,,,,"ft2 ",) | (1242,42,bar,,,,"ft2 ",) | 1242 | 42 | bar | | | | ft2 | | (42,43,AAA042) | 42 | 43 | AAA042
(1248,48,foo,,,,"ft2 ",) | (1248,48,bar,,,,"ft2 ",) | (1248,48,bar,,,,"ft2 ",) | 1248 | 48 | bar | | | | ft2 | | (48,49,AAA048) | 48 | 49 | AAA048
+ (1254,54,foo,,,,"ft2 ",) | (1254,54,bar,,,,"ft2 ",) | (1254,54,bar,,,,"ft2 ",) | 1254 | 54 | bar | | | | ft2 | | (54,55,AAA054) | 54 | 55 | AAA054
(1260,60,foo,,,,"ft2 ",) | (1260,60,bar,,,,"ft2 ",) | (1260,60,bar,,,,"ft2 ",) | 1260 | 60 | bar | | | | ft2 | | (60,61,AAA060) | 60 | 61 | AAA060
(1266,66,foo,,,,"ft2 ",) | (1266,66,bar,,,,"ft2 ",) | (1266,66,bar,,,,"ft2 ",) | 1266 | 66 | bar | | | | ft2 | | (66,67,AAA066) | 66 | 67 | AAA066
+ (1272,72,foo,,,,"ft2 ",) | (1272,72,bar,,,,"ft2 ",) | (1272,72,bar,,,,"ft2 ",) | 1272 | 72 | bar | | | | ft2 | | (72,73,AAA072) | 72 | 73 | AAA072
(1278,78,foo,,,,"ft2 ",) | (1278,78,bar,,,,"ft2 ",) | (1278,78,bar,,,,"ft2 ",) | 1278 | 78 | bar | | | | ft2 | | (78,79,AAA078) | 78 | 79 | AAA078
(1284,84,foo,,,,"ft2 ",) | (1284,84,bar,,,,"ft2 ",) | (1284,84,bar,,,,"ft2 ",) | 1284 | 84 | bar | | | | ft2 | | (84,85,AAA084) | 84 | 85 | AAA084
- (1296,96,foo,,,,"ft2 ",) | (1296,96,bar,,,,"ft2 ",) | (1296,96,bar,,,,"ft2 ",) | 1296 | 96 | bar | | | | ft2 | | (96,97,AAA096) | 96 | 97 | AAA096
- (1218,18,foo,,,,"ft2 ",) | (1218,18,bar,,,,"ft2 ",) | (1218,18,bar,,,,"ft2 ",) | 1218 | 18 | bar | | | | ft2 | | (18,19,AAA018) | 18 | 19 | AAA018
- (1236,36,foo,,,,"ft2 ",) | (1236,36,bar,,,,"ft2 ",) | (1236,36,bar,,,,"ft2 ",) | 1236 | 36 | bar | | | | ft2 | | (36,37,AAA036) | 36 | 37 | AAA036
- (1254,54,foo,,,,"ft2 ",) | (1254,54,bar,,,,"ft2 ",) | (1254,54,bar,,,,"ft2 ",) | 1254 | 54 | bar | | | | ft2 | | (54,55,AAA054) | 54 | 55 | AAA054
- (1272,72,foo,,,,"ft2 ",) | (1272,72,bar,,,,"ft2 ",) | (1272,72,bar,,,,"ft2 ",) | 1272 | 72 | bar | | | | ft2 | | (72,73,AAA072) | 72 | 73 | AAA072
(1290,90,foo,,,,"ft2 ",) | (1290,90,bar,,,,"ft2 ",) | (1290,90,bar,,,,"ft2 ",) | 1290 | 90 | bar | | | | ft2 | | (90,91,AAA090) | 90 | 91 | AAA090
+ (1296,96,foo,,,,"ft2 ",) | (1296,96,bar,,,,"ft2 ",) | (1296,96,bar,,,,"ft2 ",) | 1296 | 96 | bar | | | | ft2 | | (96,97,AAA096) | 96 | 97 | AAA096
(16 rows)
ROLLBACK;
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 06b52c65300..34b6e2d7cb8 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -3731,6 +3731,8 @@ create_cursor(ForeignScanState *node)
PGconn *conn = fsstate->conn;
StringInfoData buf;
PGresult *res;
+ int server_version_num;
+ bool use_fetch_all_cursors = false;
/* First, process a pending asynchronous request, if any. */
if (fsstate->conn_state->pendingAreq)
@@ -3755,10 +3757,15 @@ create_cursor(ForeignScanState *node)
MemoryContextSwitchTo(oldcontext);
}
+ /* Determine if we have to use fetch all cursors */
+ server_version_num = PQserverVersion(conn);
+ if (server_version_num >= 190000)
+ use_fetch_all_cursors = true;
+
/* Construct the DECLARE CURSOR command */
initStringInfo(&buf);
- appendStringInfo(&buf, "DECLARE c%u CURSOR FOR\n%s",
- fsstate->cursor_number, fsstate->query);
+ appendStringInfo(&buf, "DECLARE c%u %sCURSOR FOR\n%s",
+ fsstate->cursor_number, use_fetch_all_cursors? "FETCH ALL ": "", fsstate->query);
/*
* Notice that we pass NULL for paramTypes, thus forcing the remote server
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 9a8f9e28135..6d4447a742a 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -708,15 +708,23 @@ ANALYZE local_tbl;
SET enable_nestloop TO false;
SET enable_hashjoin TO false;
EXPLAIN (VERBOSE, COSTS OFF)
+WITH s AS MATERIALIZED (
SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1
- AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE;
+ AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE)
+SELECT * FROM s ORDER BY 1;
+WITH s AS MATERIALIZED (
SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1
- AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE;
+ AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE)
+SELECT * FROM s ORDER BY 1;
EXPLAIN (VERBOSE, COSTS OFF)
+WITH s AS MATERIALIZED (
SELECT * FROM ft1, ft4, ft5, local_tbl WHERE ft1.c1 = ft4.c1 AND ft1.c1 = ft5.c1
- AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft5.c1 < 100 FOR UPDATE;
+ AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft5.c1 < 100 FOR UPDATE)
+SELECT * FROM s ORDER BY 1;
+WITH s AS MATERIALIZED (
SELECT * FROM ft1, ft4, ft5, local_tbl WHERE ft1.c1 = ft4.c1 AND ft1.c1 = ft5.c1
- AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft5.c1 < 100 FOR UPDATE;
+ AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft5.c1 < 100 FOR UPDATE)
+SELECT * FROM s ORDER BY 1;
RESET enable_nestloop;
RESET enable_hashjoin;
@@ -1552,14 +1560,18 @@ UPDATE ft2 SET c3 = 'foo'
RETURNING ft2, ft2.*, ft4, ft4.*;
BEGIN;
EXPLAIN (verbose, costs off)
+ WITH s AS MATERIALIZED (
UPDATE ft2 SET c3 = 'bar'
FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1)
WHERE ft2.c1 > 1200 AND ft2.c2 = ft4.c1
- RETURNING old, new, ft2, ft2.*, ft4, ft4.*; -- can't be pushed down
+ RETURNING old, new, ft2, ft2.*, ft4, ft4.* -- can't be pushed down
+ ) SELECT * FROM s ORDER BY 1;
+ WITH s AS MATERIALIZED (
UPDATE ft2 SET c3 = 'bar'
FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1)
WHERE ft2.c1 > 1200 AND ft2.c2 = ft4.c1
- RETURNING old, new, ft2, ft2.*, ft4, ft4.*;
+ RETURNING old, new, ft2, ft2.*, ft4, ft4.*
+ ) SELECT * FROM s ORDER BY 1;
ROLLBACK;
EXPLAIN (verbose, costs off)
DELETE FROM ft2
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c3a0a354a9c..cd3924f905b 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -12866,8 +12866,8 @@ DeclareCursorStmt: DECLARE cursor_name cursor_options CURSOR opt_hold FOR Select
DeclareCursorStmt *n = makeNode(DeclareCursorStmt);
n->portalname = $2;
- /* currently we always set FAST_PLAN option */
- n->options = $3 | $5 | CURSOR_OPT_FAST_PLAN;
+ /* set FAST_PLAN option if FETCH ALL option is missing */
+ n->options = ($3 | $5 ) ^ CURSOR_OPT_FAST_PLAN;
n->query = $7;
$$ = (Node *) n;
}
@@ -12882,6 +12882,7 @@ cursor_options: /*EMPTY*/ { $$ = 0; }
| cursor_options BINARY { $$ = $1 | CURSOR_OPT_BINARY; }
| cursor_options ASENSITIVE { $$ = $1 | CURSOR_OPT_ASENSITIVE; }
| cursor_options INSENSITIVE { $$ = $1 | CURSOR_OPT_INSENSITIVE; }
+ | cursor_options FETCH ALL { $$ = $1 | CURSOR_OPT_FAST_PLAN; }
;
opt_hold: /* EMPTY */ { $$ = 0; }
diff --git a/src/test/regress/expected/portals.out b/src/test/regress/expected/portals.out
index 31f77abc446..1eef90a3b1e 100644
--- a/src/test/regress/expected/portals.out
+++ b/src/test/regress/expected/portals.out
@@ -1561,3 +1561,52 @@ fetch all in held_portal;
(1 row)
reset default_toast_compression;
+-- Check fetch all cursors
+create table item(item_id int primary key, i_data text);
+create table order_line(ol_id int primary key, item_id int, ol_quantity int);
+insert into order_line select i, i%1000, i%20 from generate_series(1,10000) i;
+create index on item(i_data);
+insert into item select i , 'blah' || i from generate_series(1,1000) i;
+analyze item, order_line;
+begin;
+-- Ordinary cursors optimize for selection of the first records
+explain (costs off)
+declare c1 cursor for select item.item_id, avg(ol_quantity) as a
+from item, order_line
+where i_data like '%b'
+and order_line.item_id = item.item_id
+group by item.item_id;
+ QUERY PLAN
+----------------------------------------------------------
+ GroupAggregate
+ Group Key: item.item_id
+ -> Nested Loop
+ Join Filter: (item.item_id = order_line.item_id)
+ -> Index Scan using item_pkey on item
+ Filter: (i_data ~~ '%b'::text)
+ -> Seq Scan on order_line
+(7 rows)
+
+-- Fetch all cursors expect that all records are selected
+explain (costs off)
+declare c1 fetch all cursor for select item.item_id, avg(ol_quantity) as a
+from item, order_line
+where i_data like '%b'
+and order_line.item_id = item.item_id
+group by item.item_id;
+ QUERY PLAN
+--------------------------------------------------------------
+ GroupAggregate
+ Group Key: item.item_id
+ -> Sort
+ Sort Key: item.item_id
+ -> Hash Join
+ Hash Cond: (order_line.item_id = item.item_id)
+ -> Seq Scan on order_line
+ -> Hash
+ -> Seq Scan on item
+ Filter: (i_data ~~ '%b'::text)
+(10 rows)
+
+commit;
+drop table item, order_line;
diff --git a/src/test/regress/sql/portals.sql b/src/test/regress/sql/portals.sql
index fc4cccb96c0..b5f331d1d13 100644
--- a/src/test/regress/sql/portals.sql
+++ b/src/test/regress/sql/portals.sql
@@ -605,3 +605,30 @@ drop table toasted_data;
fetch all in held_portal;
reset default_toast_compression;
+
+-- Check fetch all cursors
+create table item(item_id int primary key, i_data text);
+create table order_line(ol_id int primary key, item_id int, ol_quantity int);
+insert into order_line select i, i%1000, i%20 from generate_series(1,10000) i;
+create index on item(i_data);
+insert into item select i , 'blah' || i from generate_series(1,1000) i;
+analyze item, order_line;
+
+begin;
+-- Ordinary cursors optimize for selection of the first records
+explain (costs off)
+declare c1 cursor for select item.item_id, avg(ol_quantity) as a
+from item, order_line
+where i_data like '%b'
+and order_line.item_id = item.item_id
+group by item.item_id;
+-- Fetch all cursors expect that all records are selected
+explain (costs off)
+declare c1 fetch all cursor for select item.item_id, avg(ol_quantity) as a
+from item, order_line
+where i_data like '%b'
+and order_line.item_id = item.item_id
+group by item.item_id;
+commit;
+
+drop table item, order_line;
--
2.43.0