From feffe864407748612ee0bb114fbe2283f8acdf6d Mon Sep 17 00:00:00 2001 From: Marco Slot Date: Tue, 21 Nov 2017 18:41:53 +0100 Subject: [PATCH] Subqueries containing functions go through subquery pushdown --- .../planner/multi_logical_planner.c | 110 ++++++++++++++++-- ...ulti_subquery_complex_reference_clause.out | 11 ++ ...ulti_subquery_complex_reference_clause.sql | 6 + 3 files changed, 116 insertions(+), 11 deletions(-) diff --git a/src/backend/distributed/planner/multi_logical_planner.c b/src/backend/distributed/planner/multi_logical_planner.c index c6d39bae0..274e0444a 100644 --- a/src/backend/distributed/planner/multi_logical_planner.c +++ b/src/backend/distributed/planner/multi_logical_planner.c @@ -159,6 +159,9 @@ static MultiNode * ApplyCartesianProduct(MultiNode *leftNode, MultiNode *rightNo * Local functions forward declarations for subquery pushdown. Note that these * functions will be removed with upcoming subqery changes. */ +static bool ShouldUseSubqueryPushDown(Query *originalQuery, Query *rewrittenQuery); +static bool IsFunctionRTE(Node *node); +static bool FindNodeCheck(Node *node, bool (*check)(Node *)); static Node * ResolveExternalParams(Node *inputNode, ParamListInfo boundParams); static MultiNode * SubqueryMultiNodeTree(Query *originalQuery, Query *queryTree, @@ -199,17 +202,7 @@ MultiLogicalPlanCreate(Query *originalQuery, Query *queryTree, MultiNode *multiQueryNode = NULL; MultiTreeRoot *rootNode = NULL; - /* - * We check the existence of subqueries in FROM clause on the modified query - * given that if postgres already flattened the subqueries, MultiNodeTree() - * can plan corresponding distributed plan. - * - * We also check the existence of subqueries in WHERE clause. Note that - * this check needs to be done on the original query given that - * standard_planner() may replace the sublinks with anti/semi joins and - * MultiNodeTree() cannot plan such queries. - */ - if (SubqueryEntryList(queryTree) != NIL || SublinkList(originalQuery) != NIL) + if (ShouldUseSubqueryPushDown(originalQuery, queryTree)) { originalQuery = (Query *) ResolveExternalParams((Node *) originalQuery, boundParams); @@ -229,6 +222,101 @@ MultiLogicalPlanCreate(Query *originalQuery, Query *queryTree, } +/* + * ShouldUseSubqueryPushDown determines whether it's desirable to use + * subquery pushdown to plan the query based on the original and + * rewritten query. + */ +static bool +ShouldUseSubqueryPushDown(Query *originalQuery, Query *rewrittenQuery) +{ + /* + * We check the existence of subqueries in FROM clause on the modified query + * given that if postgres already flattened the subqueries, MultiPlanTree() + * can plan corresponding distributed plan. + */ + if (SubqueryEntryList(rewrittenQuery) != NIL) + { + return true; + } + + /* + * We also check the existence of subqueries in WHERE clause. Note that + * this check needs to be done on the original query given that + * standard_planner() may replace the sublinks with anti/semi joins and + * MultiPlanTree() cannot plan such queries. + */ + if (SublinkList(originalQuery) != NIL) + { + return true; + } + + /* + * We process function RTEs as subqueries, since the join order planner + * does not know how to handle them. + */ + if (FindNodeCheck((Node *) originalQuery, IsFunctionRTE)) + { + return true; + } + + return false; +} + + +/* + * IsFunctionRTE determines whether the given node is a function RTE. + */ +static bool +IsFunctionRTE(Node *node) +{ + if (IsA(node, RangeTblEntry)) + { + RangeTblEntry *rangeTblEntry = (RangeTblEntry *) node; + + if (rangeTblEntry->rtekind == RTE_FUNCTION) + { + return true; + } + } + + return false; +} + + +/* + * FindNodeCheck finds a node for which the check function returns true. + * + * To call this function directly with an RTE, use: + * range_table_walker(rte, FindNodeCheck, check, QTW_EXAMINE_RTES) + */ +static bool +FindNodeCheck(Node *node, bool (*check)(Node *)) +{ + if (node == NULL) + { + return false; + } + + if (check(node)) + { + return true; + } + + if (IsA(node, RangeTblEntry)) + { + /* query_tree_walker descends into RTEs */ + return false; + } + else if (IsA(node, Query)) + { + return query_tree_walker((Query *) node, FindNodeCheck, check, QTW_EXAMINE_RTES); + } + + return expression_tree_walker(node, FindNodeCheck, check); +} + + /* * ResolveExternalParams replaces the external parameters that appears * in the query with the corresponding entries in the boundParams. diff --git a/src/test/regress/expected/multi_subquery_complex_reference_clause.out b/src/test/regress/expected/multi_subquery_complex_reference_clause.out index 7dd1b14e6..df0f47cce 100644 --- a/src/test/regress/expected/multi_subquery_complex_reference_clause.out +++ b/src/test/regress/expected/multi_subquery_complex_reference_clause.out @@ -251,12 +251,23 @@ SELECT count(*) FROM 10 (1 row) +SELECT count(*) FROM user_buy_test_table LEFT JOIN (SELECT * FROM generate_series(1,10) id) users_ref_test_table +ON user_buy_test_table.item_id = users_ref_test_table.id; + count +------- + 4 +(1 row) + -- table function cannot be the outer relationship in an outer join SELECT count(*) FROM (SELECT random() FROM user_buy_test_table RIGHT JOIN generate_series(1,10) AS users_ref_test_table(id) ON user_buy_test_table.item_id > users_ref_test_table.id) subquery_1; ERROR: cannot pushdown the subquery DETAIL: There exist a table function in the outer part of the outer join +SELECT count(*) FROM user_buy_test_table RIGHT JOIN (SELECT * FROM generate_series(1,10) id) users_ref_test_table +ON user_buy_test_table.item_id = users_ref_test_table.id; +ERROR: cannot pushdown the subquery +DETAIL: There exist a table function in the outer part of the outer join -- volatile functions cannot be used as table expressions SELECT count(*) FROM (SELECT random() FROM user_buy_test_table JOIN random() AS users_ref_test_table(id) diff --git a/src/test/regress/sql/multi_subquery_complex_reference_clause.sql b/src/test/regress/sql/multi_subquery_complex_reference_clause.sql index 55dbb2e30..680491009 100644 --- a/src/test/regress/sql/multi_subquery_complex_reference_clause.sql +++ b/src/test/regress/sql/multi_subquery_complex_reference_clause.sql @@ -150,11 +150,17 @@ SELECT count(*) FROM (SELECT random() FROM user_buy_test_table LEFT JOIN generate_series(1,10) AS users_ref_test_table(id) ON user_buy_test_table.item_id > users_ref_test_table.id) subquery_1; +SELECT count(*) FROM user_buy_test_table LEFT JOIN (SELECT * FROM generate_series(1,10) id) users_ref_test_table +ON user_buy_test_table.item_id = users_ref_test_table.id; + -- table function cannot be the outer relationship in an outer join SELECT count(*) FROM (SELECT random() FROM user_buy_test_table RIGHT JOIN generate_series(1,10) AS users_ref_test_table(id) ON user_buy_test_table.item_id > users_ref_test_table.id) subquery_1; +SELECT count(*) FROM user_buy_test_table RIGHT JOIN (SELECT * FROM generate_series(1,10) id) users_ref_test_table +ON user_buy_test_table.item_id = users_ref_test_table.id; + -- volatile functions cannot be used as table expressions SELECT count(*) FROM (SELECT random() FROM user_buy_test_table JOIN random() AS users_ref_test_table(id)