diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index e3ee30f1aa..402882f0d2 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -10456,6 +10456,88 @@ DROP TABLE local_tbl; DROP INDEX base_tbl1_idx; DROP INDEX base_tbl2_idx; DROP INDEX async_p3_idx; +-- UNION queries +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO result_tbl +(SELECT * FROM async_p1 ORDER BY a LIMIT 10) +UNION +(SELECT * FROM async_p2 WHERE b < 10); + QUERY PLAN +----------------------------------------------------------------------------------------------------------------- + Insert on public.result_tbl + -> HashAggregate + Output: async_p1.a, async_p1.b, async_p1.c + Group Key: async_p1.a, async_p1.b, async_p1.c + -> Append + -> Async Foreign Scan on public.async_p1 + Output: async_p1.a, async_p1.b, async_p1.c + Remote SQL: SELECT a, b, c FROM public.base_tbl1 ORDER BY a ASC NULLS LAST LIMIT 10::bigint + -> Async Foreign Scan on public.async_p2 + Output: async_p2.a, async_p2.b, async_p2.c + Remote SQL: SELECT a, b, c FROM public.base_tbl2 WHERE ((b < 10)) +(11 rows) + +INSERT INTO result_tbl +(SELECT * FROM async_p1 ORDER BY a LIMIT 10) +UNION +(SELECT * FROM async_p2 WHERE b < 10); +SELECT * FROM result_tbl ORDER BY a; + a | b | c +------+----+------ + 1000 | 0 | 0000 + 1005 | 5 | 0005 + 1010 | 10 | 0010 + 1015 | 15 | 0015 + 1020 | 20 | 0020 + 1025 | 25 | 0025 + 1030 | 30 | 0030 + 1035 | 35 | 0035 + 1040 | 40 | 0040 + 1045 | 45 | 0045 + 2000 | 0 | 0000 + 2005 | 5 | 0005 +(12 rows) + +DELETE FROM result_tbl; +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO result_tbl +(SELECT * FROM async_p1 ORDER BY a LIMIT 10) +UNION ALL +(SELECT * FROM async_p2 WHERE b < 10); + QUERY PLAN +----------------------------------------------------------------------------------------------------------- + Insert on public.result_tbl + -> Append + -> Async Foreign Scan on public.async_p1 + Output: async_p1.a, async_p1.b, async_p1.c + Remote SQL: SELECT a, b, c FROM public.base_tbl1 ORDER BY a ASC NULLS LAST LIMIT 10::bigint + -> Async Foreign Scan on public.async_p2 + Output: async_p2.a, async_p2.b, async_p2.c + Remote SQL: SELECT a, b, c FROM public.base_tbl2 WHERE ((b < 10)) +(8 rows) + +INSERT INTO result_tbl +(SELECT * FROM async_p1 ORDER BY a LIMIT 10) +UNION ALL +(SELECT * FROM async_p2 WHERE b < 10); +SELECT * FROM result_tbl ORDER BY a; + a | b | c +------+----+------ + 1000 | 0 | 0000 + 1005 | 5 | 0005 + 1010 | 10 | 0010 + 1015 | 15 | 0015 + 1020 | 20 | 0020 + 1025 | 25 | 0025 + 1030 | 30 | 0030 + 1035 | 35 | 0035 + 1040 | 40 | 0040 + 1045 | 45 | 0045 + 2000 | 0 | 0000 + 2005 | 5 | 0005 +(12 rows) + +DELETE FROM result_tbl; -- Test that pending requests are processed properly SET enable_mergejoin TO false; SET enable_hashjoin TO false; diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql index 30b5175da5..22848710c5 100644 --- a/contrib/postgres_fdw/sql/postgres_fdw.sql +++ b/contrib/postgres_fdw/sql/postgres_fdw.sql @@ -3322,6 +3322,33 @@ DROP INDEX base_tbl1_idx; DROP INDEX base_tbl2_idx; DROP INDEX async_p3_idx; +-- UNION queries +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO result_tbl +(SELECT * FROM async_p1 ORDER BY a LIMIT 10) +UNION +(SELECT * FROM async_p2 WHERE b < 10); +INSERT INTO result_tbl +(SELECT * FROM async_p1 ORDER BY a LIMIT 10) +UNION +(SELECT * FROM async_p2 WHERE b < 10); + +SELECT * FROM result_tbl ORDER BY a; +DELETE FROM result_tbl; + +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO result_tbl +(SELECT * FROM async_p1 ORDER BY a LIMIT 10) +UNION ALL +(SELECT * FROM async_p2 WHERE b < 10); +INSERT INTO result_tbl +(SELECT * FROM async_p1 ORDER BY a LIMIT 10) +UNION ALL +(SELECT * FROM async_p2 WHERE b < 10); + +SELECT * FROM result_tbl ORDER BY a; +DELETE FROM result_tbl; + -- Test that pending requests are processed properly SET enable_mergejoin TO false; SET enable_hashjoin TO false; diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 38251c2b8e..5ef1888407 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -626,6 +626,7 @@ _copySubqueryScan(const SubqueryScan *from) * copy remainder of node */ COPY_NODE_FIELD(subplan); + COPY_SCALAR_FIELD(status); return newnode; } diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 87561cbb6f..0d41a9ab4f 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -629,6 +629,7 @@ _outSubqueryScan(StringInfo str, const SubqueryScan *node) _outScanInfo(str, (const Scan *) node); WRITE_NODE_FIELD(subplan); + WRITE_ENUM_FIELD(status, SubqueryScanStatus); } static void diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 0dd1ad7dfc..ebee3b6acf 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -1967,6 +1967,7 @@ _readSubqueryScan(void) ReadCommonScan(&local_node->scan); READ_NODE_FIELD(subplan); + READ_ENUM_FIELD(status, SubqueryScanStatus); READ_DONE(); } diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index a5f6d678cc..9b6a8cfb74 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -82,7 +82,7 @@ static List *get_gating_quals(PlannerInfo *root, List *quals); static Plan *create_gating_plan(PlannerInfo *root, Path *path, Plan *plan, List *gating_quals); static Plan *create_join_plan(PlannerInfo *root, JoinPath *best_path); -static bool is_async_capable_path(Path *path); +static bool mark_async_capable_plan(Plan *plan, Path *path); static Plan *create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags); static Plan *create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path, @@ -1091,14 +1091,25 @@ create_join_plan(PlannerInfo *root, JoinPath *best_path) } /* - * is_async_capable_path - * Check whether a given Path node is async-capable. + * mark_async_capable_plan + * Check whether a given Path node is async-capable, and if so, mark the + * Plan node created from it as such. */ static bool -is_async_capable_path(Path *path) +mark_async_capable_plan(Plan *plan, Path *path) { switch (nodeTag(path)) { + case T_SubqueryScanPath: + { + SubqueryScan *splan = (SubqueryScan *) plan; + + if (trivial_subqueryscan(splan) && + mark_async_capable_plan(splan->subplan, + ((SubqueryScanPath *) path)->subpath)) + break; + return false; + } case T_ForeignPath: { FdwRoutine *fdwroutine = path->parent->fdwroutine; @@ -1106,13 +1117,15 @@ is_async_capable_path(Path *path) Assert(fdwroutine != NULL); if (fdwroutine->IsForeignPathAsyncCapable != NULL && fdwroutine->IsForeignPathAsyncCapable((ForeignPath *) path)) - return true; + break; + return false; } - break; default: - break; + return false; } - return false; + + plan->async_capable = true; + return true; } /* @@ -1278,9 +1291,9 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags) subplans = lappend(subplans, subplan); /* Check to see if subplan can be executed asynchronously */ - if (consider_async && is_async_capable_path(subpath)) + if (consider_async && mark_async_capable_plan(subplan, subpath)) { - subplan->async_capable = true; + Assert(subplan->async_capable); ++nasyncplans; } } @@ -5551,6 +5564,7 @@ make_subqueryscan(List *qptlist, plan->righttree = NULL; node->scan.scanrelid = scanrelid; node->subplan = subplan; + node->status = SUBQUERY_SCAN_UNKNOWN; return node; } diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index e50624c465..a4455b8a44 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -115,7 +115,6 @@ static Plan *set_indexonlyscan_references(PlannerInfo *root, static Plan *set_subqueryscan_references(PlannerInfo *root, SubqueryScan *plan, int rtoffset); -static bool trivial_subqueryscan(SubqueryScan *plan); static Plan *clean_up_removed_plan_level(Plan *parent, Plan *child); static void set_foreignscan_references(PlannerInfo *root, ForeignScan *fscan, @@ -1206,13 +1205,22 @@ set_subqueryscan_references(PlannerInfo *root, * We can delete it if it has no qual to check and the targetlist just * regurgitates the output of the child plan. */ -static bool +bool trivial_subqueryscan(SubqueryScan *plan) { int attrno; ListCell *lp, *lc; + /* We might have detected this already */ + if (plan->status == SUBQUERY_SCAN_TRIVIAL) + return true; + if (plan->status == SUBQUERY_SCAN_NONTRIVIAL) + return false; + Assert(plan->status == SUBQUERY_SCAN_UNKNOWN); + + plan->status = SUBQUERY_SCAN_NONTRIVIAL; + if (plan->scan.plan.qual != NIL) return false; @@ -1254,6 +1262,7 @@ trivial_subqueryscan(SubqueryScan *plan) attrno++; } + plan->status = SUBQUERY_SCAN_TRIVIAL; return true; } diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index ec9a8b0c81..67f21080df 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -518,16 +518,28 @@ typedef struct TidRangeScan * relation, we make this a descendant of Scan anyway for code-sharing * purposes. * + * SubqueryScanStatus caches the trivial_subqueryscan property of the node. + * SUBQUERY_SCAN_UNKNOWN means not yet determined. This is only used during + * planning. + * * Note: we store the sub-plan in the type-specific subplan field, not in * the generic lefttree field as you might expect. This is because we do * not want plan-tree-traversal routines to recurse into the subplan without * knowing that they are changing Query contexts. * ---------------- */ +typedef enum SubqueryScanStatus +{ + SUBQUERY_SCAN_UNKNOWN, + SUBQUERY_SCAN_TRIVIAL, + SUBQUERY_SCAN_NONTRIVIAL +} SubqueryScanStatus; + typedef struct SubqueryScan { Scan scan; Plan *subplan; + SubqueryScanStatus status; } SubqueryScan; /* ---------------- diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h index bf1adfc52a..c908a49490 100644 --- a/src/include/optimizer/planmain.h +++ b/src/include/optimizer/planmain.h @@ -112,6 +112,7 @@ extern bool innerrel_is_unique(PlannerInfo *root, * prototypes for plan/setrefs.c */ extern Plan *set_plan_references(PlannerInfo *root, Plan *plan); +extern bool trivial_subqueryscan(SubqueryScan *plan); extern void record_plan_function_dependency(PlannerInfo *root, Oid funcid); extern void record_plan_type_dependency(PlannerInfo *root, Oid typid); extern bool extract_query_dependencies_walker(Node *node, PlannerInfo *root);