diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 296dd75c1b6..22405bef5fc 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -40,8 +40,10 @@ #include "optimizer/paths.h" #include "optimizer/plancat.h" #include "optimizer/planner.h" +#include "optimizer/planmain.h" #include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" +#include "optimizer/subselect.h" #include "parser/parse_clause.h" #include "parser/parsetree.h" #include "partitioning/partbounds.h" @@ -3895,6 +3897,68 @@ generate_partitionwise_join_paths(PlannerInfo *root, RelOptInfo *rel) list_free(live_children); } +bool +try_push_outer_qual_to_sublink_query(PlannerInfo *parent, Query *subquery, List *conditions) +{ + pushdown_safety_info safetyInfo; + ListCell *lc1; + bool found = false; + bool query_is_pushdown_safe = false; + + if (conditions == NIL) + return false; + + memset(&safetyInfo, 0, sizeof(safetyInfo)); + safetyInfo.unsafeColumns = (bool *) + palloc0((list_length(subquery->targetList) + 1) * sizeof(bool)); + + /* Check whether pushdown qual to sublink query is safe. */ + query_is_pushdown_safe = subquery_is_pushdown_safe(subquery, subquery, &safetyInfo); + pfree(safetyInfo.unsafeColumns); + if (!query_is_pushdown_safe) + return false; + + /* + * Currently, we have some conditional expressions in sublink (out var = local var) + * Now, the outer query looks for related equivalent expressions that have been generated (outer var = const). + * If out var = local var and outer var = const, then we get local var = const and push it down to sublink + */ + foreach(lc1, conditions) + { + pushdown_expr_info *expr_info = (pushdown_expr_info *) lfirst(lc1); + Index levelsup = 0; + RelOptInfo *rel; + ListCell *lc2; + PlannerInfo *tmproot = parent; + + /* The outer var could exist in any of the upper-level queries so find these roots */ + for (levelsup = expr_info->outer->varlevelsup - 1; levelsup > 0; levelsup--) + tmproot = tmproot->parent_root; + + /* Flatten varLevelsup, for find conditions from BaserestrictInfo. */ + expr_info->outer->varlevelsup = 0; + + /* Find if there is an available qual in relation of this var from root */ + rel = find_base_rel(tmproot, expr_info->outer->varno); + if (rel == NULL || rel->baserestrictinfo == NULL) + continue; + + foreach(lc2, rel->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc2); + + /* Make sure that qual in restrictInfo that is var = const and can safely pushdown */ + if (condition_is_safe_pushdown_to_sublink(rinfo, expr_info->outer)) + { + /* replace qual expr from outer var = const to var = const and push down to sublink query */ + sublink_query_push_qual(subquery, (Node *)copyObject(rinfo->clause), expr_info->outer, expr_info->inner); + found = true; + } + } + } + + return found; +} /***************************************************************************** * DEBUG SUPPORT diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index 6f1abbe47d6..f4aeb716a59 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -388,6 +388,7 @@ process_equivalence(PlannerInfo *root, restrictinfo->security_level); ec1->ec_max_security = Max(ec1->ec_max_security, restrictinfo->security_level); + ec1->ec_processed = false; /* mark the RI as associated with this eclass */ restrictinfo->left_ec = ec1; restrictinfo->right_ec = ec1; @@ -450,6 +451,7 @@ process_equivalence(PlannerInfo *root, ec->ec_min_security = restrictinfo->security_level; ec->ec_max_security = restrictinfo->security_level; ec->ec_merged = NULL; + ec->ec_processed = false; em1 = add_eq_member(ec, item1, item1_relids, item1_nullable_relids, false, item1_type); em2 = add_eq_member(ec, item2, item2_relids, item2_nullable_relids, @@ -574,6 +576,7 @@ add_eq_member(EquivalenceClass *ec, Expr *expr, Relids relids, ec->ec_relids = bms_add_members(ec->ec_relids, relids); } ec->ec_members = lappend(ec->ec_members, em); + ec->ec_processed = false; return em; } @@ -711,6 +714,7 @@ get_eclass_for_sort_expr(PlannerInfo *root, newec->ec_min_security = UINT_MAX; newec->ec_max_security = 0; newec->ec_merged = NULL; + newec->ec_processed = false; if (newec->ec_has_volatile && sortref == 0) /* should not happen */ elog(ERROR, "volatile EquivalenceClass has no sortref"); @@ -1114,7 +1118,12 @@ generate_base_implied_equalities(PlannerInfo *root) * Single-member ECs won't generate any deductions, either here or at * the join level. */ - if (list_length(ec->ec_members) > 1) + if (ec->ec_processed) + { + ec_index++; + continue; + } + else if (list_length(ec->ec_members) > 1) { if (ec->ec_has_const) generate_base_implied_equalities_const(root, ec); @@ -1151,6 +1160,7 @@ generate_base_implied_equalities(PlannerInfo *root) rel->has_eclass_joins = true; } + ec->ec_processed = true; ec_index++; } } diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index f6a202d900f..08ca51b9960 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -30,10 +30,12 @@ #include "optimizer/planner.h" #include "optimizer/prep.h" #include "optimizer/restrictinfo.h" +#include "optimizer/subselect.h" #include "parser/analyze.h" #include "rewrite/rewriteManip.h" #include "utils/lsyscache.h" #include "utils/typcache.h" +#include "utils/guc.h" /* These parameters are set by GUC */ int from_collapse_limit; @@ -80,6 +82,16 @@ static void check_mergejoinable(RestrictInfo *restrictinfo); static void check_hashjoinable(RestrictInfo *restrictinfo); static void check_memoizable(RestrictInfo *restrictinfo); +static void remember_qual_info_for_lazy_process_sublink(PlannerInfo *root, + Node *clause, + bool below_outer_join, + JoinType jointype, + Index security_level, + Relids qualscope, + Relids ojscope, + Relids outerjoin_nonnullable, + List *postponed_qual_list); +static void *search_sublink_from_lazy_process_list(PlannerInfo *root, Node *node); /***************************************************************************** * @@ -262,7 +274,16 @@ add_vars_to_targetlist(PlannerInfo *root, List *vars, else if (IsA(node, PlaceHolderVar)) { PlaceHolderVar *phv = (PlaceHolderVar *) node; - PlaceHolderInfo *phinfo = find_placeholder_info(root, phv, + PlaceHolderInfo *phinfo = NULL; + + /* + * Since there may be an unexpanded sublink in the targetList, + * we'll skip it for now. Don't worry let lazy_process_sublinks do it later. + */ + if (has_unexpanded_sublink(root) && checkExprHasSubLink(node)) + continue; + + phinfo = find_placeholder_info(root, phv, create_new_ph); phinfo->ph_needed = bms_add_members(phinfo->ph_needed, @@ -1621,6 +1642,17 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, Relids nullable_relids; RestrictInfo *restrictinfo; + /* Before lazy transform sublink has not been converted, so backup it */ + if (checkExprHasSubLink(clause)) + { + remember_qual_info_for_lazy_process_sublink(root, clause, below_outer_join, jointype, security_level, + qualscope, ojscope, outerjoin_nonnullable, *postponed_qual_list); + + relids = pull_varnos(root, clause); + Assert(bms_is_subset(relids, qualscope)); + return; + } + /* * Retrieve all relids mentioned within the clause. */ @@ -2750,3 +2782,184 @@ check_memoizable(RestrictInfo *restrictinfo) if (OidIsValid(typentry->hash_proc) && OidIsValid(typentry->eq_opr)) restrictinfo->right_hasheqoperator = typentry->eq_opr; } + +/* + * query at this level has sublink and It is safe to try lazy process and pushdown qual. + * Use a switch to control it. This is a minimal subset, then try to support more scenarios. + */ +bool +query_has_sublink_try_pushdown_qual(PlannerInfo *root) +{ + Query *parse = root->parse; + + if (!parse->hasSubLinks) + return false; + + if (parse->commandType != CMD_SELECT || + parse->hasWindowFuncs || + parse->hasTargetSRFs || + parse->hasRecursive || + parse->hasModifyingCTE || + parse->hasForUpdate || + parse->hasRowSecurity || + parse->setOperations || + parse->havingQual || + parse->cteList != NIL) + return false; + + return lazy_process_sublink; +} + +/* + * Handle sublink that is not expanded. + * Convert these sublinks to subplans and handles the associated targetList expr and equivalence classes. + */ +void +lazy_process_sublinks(PlannerInfo *root, bool single_result_rte) +{ + Query *parse = root->parse; + List *tlist_vars; + + /* Exit the function if no unprocessed sublink is recorded. */ + if (!has_unexpanded_sublink(root)) + return; + + /* process sublink in targetlist */ + root->processed_tlist = (List *)SS_process_sublinks(root, (Node *)root->processed_tlist, false, true, true); + if (root->query_level > 1) + root->processed_tlist = (List *)SS_replace_correlation_vars(root, (Node *)root->processed_tlist); + + /* process sublink in where clause */ + if (parse->jointree && parse->jointree->quals) + { + FromExpr *f = parse->jointree; + List *newquals = NIL; + ListCell *l; + + Assert(IsA(f->quals, List)); + foreach(l, (List *) f->quals) + { + Node *qual = (Node *) lfirst(l); + + if (checkExprHasSubLink(qual)) + qual = lazy_process_sublink_qual(root, qual); + + newquals = lappend(newquals, qual); + } + + f->quals = (Node *)newquals; + } + + /* process agg functions */ + if(parse->hasAggs) + { + preprocess_aggrefs(root, (Node *) root->processed_tlist); + preprocess_minmax_aggregates(root, true); + } + + /* empty from clause no need prcess targetlist or from clause */ + if (!single_result_rte) + { + /* Put the mutated sublink info into the targetList */ + tlist_vars = pull_var_clause((Node *) root->processed_tlist, + PVC_RECURSE_AGGREGATES | + PVC_RECURSE_WINDOWFUNCS | + PVC_INCLUDE_PLACEHOLDERS); + + if (tlist_vars != NIL) + { + add_vars_to_targetlist(root, tlist_vars, bms_make_singleton(0), true); + list_free(tlist_vars); + } + + generate_base_implied_equalities(root); + } + + /* Make sure all sublinks are processed. */ + if (has_unexpanded_sublink(root)) + elog(ERROR, "sublink is not fully expanded yet"); + + return; +} + +typedef struct sublink_node +{ + Node *expr; + bool below_outer_join; + JoinType jointype; + Index security_level; + Relids qualscope; + Relids ojscope; + Relids outerjoin_nonnullable; + List *postponed_qual_list; +} sublink_node; + +/* Log unexpanded sublink for future do distribute_qual_to_rels in lazy process sublink */ +static void +remember_qual_info_for_lazy_process_sublink(PlannerInfo *root, + Node *clause, + bool below_outer_join, + JoinType jointype, + Index security_level, + Relids qualscope, + Relids ojscope, + Relids outerjoin_nonnullable, + List *postponed_qual_list) +{ + sublink_node *sublink_info = palloc0(sizeof(sublink_node)); + + sublink_info->expr= copyObject(clause); + sublink_info->below_outer_join = below_outer_join; + sublink_info->jointype = jointype; + sublink_info->security_level = security_level; + sublink_info->qualscope = bms_copy(qualscope); + sublink_info->ojscope = bms_copy(ojscope); + sublink_info->outerjoin_nonnullable = bms_copy(outerjoin_nonnullable); + sublink_info->postponed_qual_list = list_copy_deep(postponed_qual_list); + + root->unexpanded_sublink_expr_list = lappend(root->unexpanded_sublink_expr_list, sublink_info); + + return; +} + +Node * +lazy_process_sublink_qual(PlannerInfo *root, Node *node) +{ + Node *qual = NULL; + sublink_node *sublink_info = NULL; + + qual = SS_process_sublinks(root, node, true, true, true); + sublink_info = (sublink_node *)search_sublink_from_lazy_process_list(root, node); + if (sublink_info) + { + List *postponed_qual_list = NIL; + distribute_qual_to_rels(root, qual, sublink_info->below_outer_join, sublink_info->jointype, sublink_info->security_level, + sublink_info->qualscope, sublink_info->ojscope, sublink_info->outerjoin_nonnullable, + &postponed_qual_list); + + Assert(postponed_qual_list == NIL); + root->unexpanded_sublink_expr_list = list_delete(root->unexpanded_sublink_expr_list, sublink_info); + } + + return qual; +} + +static void * +search_sublink_from_lazy_process_list(PlannerInfo *root, Node *node) +{ + ListCell *lc = NULL; + sublink_node *sublink_info = NULL; + + foreach(lc, root->unexpanded_sublink_expr_list) + { + sublink_node *tmp = lfirst(lc); + Assert(tmp->expr); + if (equal(tmp->expr, node)) + { + sublink_info = tmp; + break; + } + } + + return (void *)sublink_info; +} diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c index c1634d16669..7f83f58479f 100644 --- a/src/backend/optimizer/plan/planagg.c +++ b/src/backend/optimizer/plan/planagg.c @@ -49,7 +49,7 @@ static bool can_minmax_aggs(PlannerInfo *root, List **context); static bool build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo, - Oid eqop, Oid sortop, bool nulls_first); + Oid eqop, Oid sortop, bool nulls_first, bool lazy_process_sublink); static void minmax_qp_callback(PlannerInfo *root, void *extra); static Oid fetch_agg_sort_op(Oid aggfnoid); @@ -70,7 +70,7 @@ static Oid fetch_agg_sort_op(Oid aggfnoid); * root->agginfos, so preprocess_aggrefs() must have been called already, too. */ void -preprocess_minmax_aggregates(PlannerInfo *root) +preprocess_minmax_aggregates(PlannerInfo *root, bool lazy_process_sublink) { Query *parse = root->parse; FromExpr *jtnode; @@ -173,9 +173,9 @@ preprocess_minmax_aggregates(PlannerInfo *root) * FIRST is more likely to be available if the operator is a * reverse-sort operator, so try that first if reverse. */ - if (build_minmax_path(root, mminfo, eqop, mminfo->aggsortop, reverse)) + if (build_minmax_path(root, mminfo, eqop, mminfo->aggsortop, reverse, lazy_process_sublink)) continue; - if (build_minmax_path(root, mminfo, eqop, mminfo->aggsortop, !reverse)) + if (build_minmax_path(root, mminfo, eqop, mminfo->aggsortop, !reverse, lazy_process_sublink)) continue; /* No indexable path for this aggregate, so fail */ @@ -315,7 +315,7 @@ can_minmax_aggs(PlannerInfo *root, List **context) */ static bool build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo, - Oid eqop, Oid sortop, bool nulls_first) + Oid eqop, Oid sortop, bool nulls_first, bool lazy_process_sublink) { PlannerInfo *subroot; Query *parse; @@ -352,12 +352,23 @@ build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo, /* append_rel_list might contain outer Vars? */ subroot->append_rel_list = copyObject(root->append_rel_list); IncrementVarSublevelsUp((Node *) subroot->append_rel_list, 1, 1); + + if (lazy_process_sublink) + { + /* under lazy process sublink, parent root may have some data that child does not need, so set it to NIL */ + subroot->join_info_list = NIL; + subroot->eq_classes = NIL; + subroot->placeholder_list = NIL; + } + else + { /* There shouldn't be any OJ info to translate, as yet */ Assert(subroot->join_info_list == NIL); /* and we haven't made equivalence classes, either */ Assert(subroot->eq_classes == NIL); /* and we haven't created PlaceHolderInfos, either */ Assert(subroot->placeholder_list == NIL); + } /*---------- * Generate modified query of the form diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index 273ac0acf7e..7042c96b09b 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -102,6 +102,8 @@ query_planner(PlannerInfo *root, Assert(rte != NULL); if (rte->rtekind == RTE_RESULT) { + lazy_process_sublinks(root, true); + /* Make the RelOptInfo for it directly */ final_rel = build_simple_rel(root, varno, NULL); @@ -197,6 +199,8 @@ query_planner(PlannerInfo *root, */ generate_base_implied_equalities(root); + lazy_process_sublinks(root, false); + /* * We have completed merging equivalence sets, so it's now possible to * generate pathkeys in canonical form; so compute query_pathkeys and diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index bd01ec0526f..0b978106868 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -64,6 +64,7 @@ #include "utils/rel.h" #include "utils/selfuncs.h" #include "utils/syscache.h" +#include "utils/guc.h" /* GUC parameters */ double cursor_tuple_fraction = DEFAULT_CURSOR_TUPLE_FRACTION; @@ -128,8 +129,9 @@ typedef struct /* Local functions */ static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind); -static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode); +static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode, bool istop); static void grouping_planner(PlannerInfo *root, double tuple_fraction); +static Node *preprocess_expression_ext(PlannerInfo *root, Node *expr, int kind, bool process_sublink); static grouping_sets_data *preprocess_grouping_sets(PlannerInfo *root); static List *remap_to_groupclause_idx(List *groupClause, List *gsets, int *tleref_to_colnum_map); @@ -641,6 +643,8 @@ subquery_planner(PlannerGlobal *glob, Query *parse, root->wt_param_id = -1; root->non_recursive_path = NULL; root->partColsUpdated = false; + root->unexpanded_sublink_counter = 0; + root->unexpanded_sublink_expr_list = NIL; /* * If there is a WITH list, process each WITH query and either convert it @@ -784,8 +788,8 @@ subquery_planner(PlannerGlobal *glob, Query *parse, * part of the targetlist. */ parse->targetList = (List *) - preprocess_expression(root, (Node *) parse->targetList, - EXPRKIND_TARGET); + preprocess_expression_ext(root, (Node *) parse->targetList, + EXPRKIND_TARGET, false); /* Constant-folding might have removed all set-returning functions */ if (parse->hasTargetSRFs) @@ -807,7 +811,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, preprocess_expression(root, (Node *) parse->returningList, EXPRKIND_TARGET); - preprocess_qual_conditions(root, (Node *) parse->jointree); + preprocess_qual_conditions(root, (Node *) parse->jointree, true); parse->havingQual = preprocess_expression(root, parse->havingQual, EXPRKIND_QUAL); @@ -1049,14 +1053,24 @@ subquery_planner(PlannerGlobal *glob, Query *parse, return root; } +static Node * +preprocess_expression(PlannerInfo *root, Node *expr, int kind) +{ + return preprocess_expression_ext(root, expr, kind, true); +} + /* * preprocess_expression * Do subquery_planner's preprocessing work for an expression, * which can be a targetlist, a WHERE clause (including JOIN/ON * conditions), a HAVING clause, or a few other things. + * + * if process_sublink = false + * This means that sublink in an expression will try to defer processing. + * see lazy_process_sublinks() */ static Node * -preprocess_expression(PlannerInfo *root, Node *expr, int kind) +preprocess_expression_ext(PlannerInfo *root, Node *expr, int kind, bool process_sublink) { /* * Fall out quickly if expression is empty. This occurs often enough to @@ -1129,7 +1143,7 @@ preprocess_expression(PlannerInfo *root, Node *expr, int kind) /* Expand SubLinks to SubPlans */ if (root->parse->hasSubLinks) - expr = SS_process_sublinks(root, expr, (kind == EXPRKIND_QUAL)); + expr = SS_process_sublinks(root, expr, (kind == EXPRKIND_QUAL), false, process_sublink); /* * XXX do not insert anything here unless you have grokked the comments in @@ -1158,7 +1172,7 @@ preprocess_expression(PlannerInfo *root, Node *expr, int kind) * preprocessing work on each qual condition found therein. */ static void -preprocess_qual_conditions(PlannerInfo *root, Node *jtnode) +preprocess_qual_conditions(PlannerInfo *root, Node *jtnode, bool istop) { if (jtnode == NULL) return; @@ -1172,17 +1186,24 @@ preprocess_qual_conditions(PlannerInfo *root, Node *jtnode) ListCell *l; foreach(l, f->fromlist) - preprocess_qual_conditions(root, lfirst(l)); + preprocess_qual_conditions(root, lfirst(l), false); - f->quals = preprocess_expression(root, f->quals, EXPRKIND_QUAL); + /* + * istop = true means that this is qual in the WHERE clause + * istop = false means that this is the join qual on the Join on clause + * For now, only sublink on the WHERE clause can be deferred, + */ + if (istop) + f->quals = preprocess_expression_ext(root, f->quals, EXPRKIND_QUAL, false); + else + f->quals = preprocess_expression_ext(root, f->quals, EXPRKIND_QUAL, true); } else if (IsA(jtnode, JoinExpr)) { JoinExpr *j = (JoinExpr *) jtnode; - preprocess_qual_conditions(root, j->larg); - preprocess_qual_conditions(root, j->rarg); - + preprocess_qual_conditions(root, j->larg, false); + preprocess_qual_conditions(root, j->rarg, false); j->quals = preprocess_expression(root, j->quals, EXPRKIND_QUAL); } else @@ -1384,11 +1405,11 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) * pathtargets, else some copies of the Aggref nodes might escape * being marked. */ - if (parse->hasAggs) - { + if (parse->hasAggs && !has_unexpanded_sublink(root)) preprocess_aggrefs(root, (Node *) root->processed_tlist); + + if (parse->hasAggs) preprocess_aggrefs(root, (Node *) parse->havingQual); - } /* * Locate any window functions in the tlist. (We don't need to look @@ -1412,8 +1433,8 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) * that is needed in MIN/MAX-optimizable cases will have to be * duplicated in planagg.c. */ - if (parse->hasAggs) - preprocess_minmax_aggregates(root); + if (parse->hasAggs && !has_unexpanded_sublink(root)) + preprocess_minmax_aggregates(root, false); /* * Figure out whether there's a hard limit on the number of rows that diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c index c9f7a09d102..aadf1fd52e0 100644 --- a/src/backend/optimizer/plan/subselect.c +++ b/src/backend/optimizer/plan/subselect.c @@ -32,11 +32,13 @@ #include "optimizer/planner.h" #include "optimizer/prep.h" #include "optimizer/subselect.h" +#include "optimizer/paths.h" #include "parser/parse_relation.h" #include "rewrite/rewriteManip.h" #include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/syscache.h" +#include "utils/ruleutils.h" typedef struct convert_testexpr_context @@ -49,6 +51,8 @@ typedef struct process_sublinks_context { PlannerInfo *root; bool isTopQual; + bool lazy_process; + bool force_process; } process_sublinks_context; typedef struct finalize_primnode_context @@ -65,6 +69,13 @@ typedef struct inline_cte_walker_context Query *ctequery; /* query to substitute */ } inline_cte_walker_context; +typedef struct equal_expr_info_context +{ + bool has_unexpected_expr; + bool has_const; + Var *outer_var; + Var *inner_var; +} equal_expr_info_context; static Node *build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot, List *plan_params, @@ -105,6 +116,11 @@ static Bitmapset *finalize_plan(PlannerInfo *root, static bool finalize_primnode(Node *node, finalize_primnode_context *context); static bool finalize_agg_primnode(Node *node, finalize_primnode_context *context); +static Node *replace_vars_mutator(Node *node, void *context); +static List *find_equal_conditions_contain_uplevelvar_in_sublink_query(Query *orig_subquery); +static bool equal_expr_analyze_walker(Node *node, void *context); +static bool equal_expr_safety_check(Node *node, equal_expr_info_context *context); + /* * Get the datatype/typmod/collation of the first column of the plan's output. @@ -162,7 +178,7 @@ get_first_col_type(Plan *plan, Oid *coltype, int32 *coltypmod, static Node * make_subplan(PlannerInfo *root, Query *orig_subquery, SubLinkType subLinkType, int subLinkId, - Node *testexpr, bool isTopQual) + Node *testexpr, bool isTopQual, bool lazy_process) { Query *subquery; bool simple_exists = false; @@ -173,6 +189,8 @@ make_subplan(PlannerInfo *root, Query *orig_subquery, Plan *plan; List *plan_params; Node *result; + Query *optimized_subquery = NULL; + Query *optimized_subquery_copy = NULL; /* * Copy the source Query node. This is a quick and dirty kluge to resolve @@ -218,8 +236,32 @@ make_subplan(PlannerInfo *root, Query *orig_subquery, /* plan_params should not be in use in current query level */ Assert(root->plan_params == NIL); + if (lazy_process) + { + List *conditions = NIL; + Query *subquery_copy = copyObject(orig_subquery); + + /* + * Search sublink query. + * If the query contains an outer condition equivalent expression, + * this means that there may be external conditions that can be pushed down to optimize the subquery. + */ + conditions = find_equal_conditions_contain_uplevelvar_in_sublink_query(subquery_copy); + if (conditions) + { + /* Search outer queries, and if relevant equivalent expressions are found, push them down into subqueries. */ + if (try_push_outer_qual_to_sublink_query(root, subquery_copy, conditions)) + { + optimized_subquery = subquery_copy; + optimized_subquery_copy = copyObject(optimized_subquery); + } + list_free(conditions); + } + } + /* Generate Paths for the subquery */ - subroot = subquery_planner(root->glob, subquery, + subroot = subquery_planner(root->glob, + (optimized_subquery != NULL) ? optimized_subquery : subquery, root, false, tuple_fraction); @@ -256,7 +298,7 @@ make_subplan(PlannerInfo *root, Query *orig_subquery, List *paramIds; /* Make a second copy of the original subquery */ - subquery = copyObject(orig_subquery); + subquery = copyObject((optimized_subquery_copy != NULL) ? optimized_subquery_copy : orig_subquery); /* and re-simplify */ simple_exists = simplify_EXISTS_query(root, subquery); Assert(simple_exists); @@ -365,7 +407,7 @@ build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot, */ if (IsA(arg, PlaceHolderVar) || IsA(arg, Aggref)) - arg = SS_process_sublinks(root, arg, false); + arg = SS_process_sublinks(root, arg, false, false, true); splan->parParam = lappend_int(splan->parParam, pitem->paramId); splan->args = lappend(splan->args, arg); @@ -1915,12 +1957,14 @@ replace_correlation_vars_mutator(Node *node, PlannerInfo *root) * not distinguish FALSE from UNKNOWN return values. */ Node * -SS_process_sublinks(PlannerInfo *root, Node *expr, bool isQual) +SS_process_sublinks(PlannerInfo *root, Node *expr, bool isQual, bool lazy_process, bool force_process) { process_sublinks_context context; context.root = root; context.isTopQual = isQual; + context.lazy_process = lazy_process; + context.force_process = force_process; return process_sublinks_mutator(expr, &context); } @@ -1930,20 +1974,34 @@ process_sublinks_mutator(Node *node, process_sublinks_context *context) process_sublinks_context locContext; locContext.root = context->root; + locContext.lazy_process = context->lazy_process; + locContext.force_process = context->force_process; if (node == NULL) return NULL; if (IsA(node, SubLink)) { SubLink *sublink = (SubLink *) node; - Node *testexpr; /* * First, recursively process the lefthand-side expressions, if any. * They're not top-level anymore. */ locContext.isTopQual = false; - testexpr = process_sublinks_mutator(sublink->testexpr, &locContext); + locContext.lazy_process = context->lazy_process; + locContext.force_process = context->force_process; + sublink->testexpr = process_sublinks_mutator(sublink->testexpr, &locContext); + + if (!context->force_process && + query_has_sublink_try_pushdown_qual(context->root)) + { + Assert(context->lazy_process == false); + context->root->unexpanded_sublink_counter++; + return node; + } + + if (context->lazy_process) + context->root->unexpanded_sublink_counter--; /* * Now build the SubPlan node and make the expr to return. @@ -1952,8 +2010,8 @@ process_sublinks_mutator(Node *node, process_sublinks_context *context) (Query *) sublink->subselect, sublink->subLinkType, sublink->subLinkId, - testexpr, - context->isTopQual); + sublink->testexpr, + context->isTopQual, locContext.lazy_process); } /* @@ -1978,8 +2036,8 @@ process_sublinks_mutator(Node *node, process_sublinks_context *context) * the very routine that creates 'em to begin with). We shouldn't find * ourselves invoked directly on a Query, either. */ - Assert(!IsA(node, SubPlan)); - Assert(!IsA(node, AlternativeSubPlan)); + Assert(!IsA(node, SubPlan) || context->lazy_process); + Assert(!IsA(node, AlternativeSubPlan) || context->lazy_process); Assert(!IsA(node, Query)); /* @@ -2003,6 +2061,8 @@ process_sublinks_mutator(Node *node, process_sublinks_context *context) /* Still at qual top-level */ locContext.isTopQual = context->isTopQual; + locContext.lazy_process = context->lazy_process; + locContext.force_process = context->force_process; foreach(l, ((BoolExpr *) node)->args) { @@ -2024,6 +2084,8 @@ process_sublinks_mutator(Node *node, process_sublinks_context *context) /* Still at qual top-level */ locContext.isTopQual = context->isTopQual; + locContext.lazy_process = context->lazy_process; + locContext.force_process = context->force_process; foreach(l, ((BoolExpr *) node)->args) { @@ -2989,3 +3051,184 @@ SS_make_initplan_from_plan(PlannerInfo *root, /* Set costs of SubPlan using info from the plan tree */ cost_subplan(subroot, node, plan); } + +void +sublink_query_push_qual(Query *subquery, Node *qual, Var *outer, Var *inner) +{ + pushdown_expr_info context; + Node *new_qual; + + context.outer = outer; + context.inner = inner; + + new_qual = expression_tree_mutator(qual, replace_vars_mutator, (void *)&context); + subquery->jointree->quals = make_and_qual(subquery->jointree->quals, new_qual); +} + +static Node * +replace_vars_mutator(Node *node, void *context) +{ + pushdown_expr_info *info = (pushdown_expr_info *) context; + + if (IsA(node, Var) && equal(node, (Node *)info->outer)) + { + node = copyObject((Node *)info->inner); + return node; + } + + return expression_tree_mutator(node, replace_vars_mutator, context); +} + +/* condition has to be (var = const value) */ +bool +condition_is_safe_pushdown_to_sublink(RestrictInfo *rinfo, Var *var) +{ + Node *clause = (Node *) rinfo->clause; + equal_expr_info_context context; + + if (clause == NULL) + return false; + + if (rinfo->pseudoconstant) + return false; + + if (contain_leaked_vars(clause)) + return false; + + memset(&context, 0, sizeof(equal_expr_info_context)); + if (equal_expr_safety_check(clause, &context)) + { + /* + * RestrictInfo clause must be like inner var = const. + * It cannot contain any out var and references the same columns as var. + * Finally, system columns are not supported for now. + */ + if (context.inner_var && + context.outer_var == NULL && + context.has_unexpected_expr == false && + context.has_const && + context.inner_var->varattno > 0 && + equal(context.inner_var, var)) + return true; + } + + return false; +} + +static List * +find_equal_conditions_contain_uplevelvar_in_sublink_query(Query *orig_subquery) +{ + Node *quals; + ListCell *lc; + List *conditions = NIL; + + if (orig_subquery->jointree == NULL || + orig_subquery->jointree->quals == NULL) + return NIL; + + quals = copyObject(orig_subquery->jointree->quals); + quals = (Node *) canonicalize_qual((Expr *) quals, false); + quals = (Node *) make_ands_implicit((Expr *) quals); + + foreach(lc, (List *)quals) + { + Node *node = (Node *) lfirst(lc); + equal_expr_info_context context; + pushdown_expr_info *expr_info = NULL; + + memset(&context, 0, sizeof(equal_expr_info_context)); + if (equal_expr_safety_check(node, &context)) + { + /* It needs to be something like outer var = inner var */ + if (context.inner_var && + context.outer_var && + context.has_unexpected_expr == false && + context.has_const == false) + { + expr_info = palloc0(sizeof(pushdown_expr_info)); + expr_info->inner = context.inner_var; + expr_info->outer = context.outer_var; + conditions = lappend(conditions, expr_info); + } + } + } + + return conditions; +} + +static bool +equal_expr_safety_check(Node *node, equal_expr_info_context *context) +{ + const char *op; + + if (!IsA(node, OpExpr)) + return false; + + op = get_simple_binary_op_name((OpExpr *) node); + if (op == NULL || strcmp(op, "=") != 0) + return false; + + if (contain_volatile_functions(node) || + contain_mutable_functions(node) || + contain_nonstrict_functions(node)) + return false; + + equal_expr_analyze_walker(node, context); + + return true; +} + +static bool +equal_expr_analyze_walker(Node *node, void *context) +{ + equal_expr_info_context *info = (equal_expr_info_context *)context; + + if (node == NULL) + return false; + + switch (nodeTag(node)) + { + case T_Var: + { + if (((Var *) node)->varlevelsup > 0) + { + if (info->outer_var) + info->has_unexpected_expr = true; + else + info->outer_var = (Var *)copyObject(node); + + return info->has_unexpected_expr; + } + else + { + if (info->inner_var) + info->has_unexpected_expr = true; + else + info->inner_var = (Var *)copyObject(node); + + return info->has_unexpected_expr; + } + } + break; + + case T_Const: + { + info->has_const = true; + return false; + } + break; + + case T_Param: + case T_FuncExpr: + { + info->has_unexpected_expr = true; + return true; + } + break; + + default: + break; + } + + return expression_tree_walker(node, equal_expr_analyze_walker, context); +} diff --git a/src/backend/optimizer/util/placeholder.c b/src/backend/optimizer/util/placeholder.c index 1c4202d864c..0e11ed22522 100644 --- a/src/backend/optimizer/util/placeholder.c +++ b/src/backend/optimizer/util/placeholder.c @@ -22,6 +22,7 @@ #include "optimizer/placeholder.h" #include "optimizer/planmain.h" #include "utils/lsyscache.h" +#include "rewrite/rewriteManip.h" /* Local functions */ static void find_placeholders_recurse(PlannerInfo *root, Node *jtnode); @@ -87,6 +88,10 @@ find_placeholder_info(PlannerInfo *root, PlaceHolderVar *phv, if (!create_new_ph) elog(ERROR, "too late to create a new PlaceHolderInfo"); + /* Unprocessed sublink is not accepted, it needs to go through SS_process_sublinks first */ + if (checkExprHasSubLink((Node *)phv)) + elog(ERROR, "can not add sublink to placeholder_list"); + phinfo = makeNode(PlaceHolderInfo); phinfo->phid = phv->phid; diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 8da525c715b..f847e898e68 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -429,7 +429,6 @@ static void resolve_special_varno(Node *node, deparse_context *context, static Node *find_param_referent(Param *param, deparse_context *context, deparse_namespace **dpns_p, ListCell **ancestor_cell_p); static void get_parameter(Param *param, deparse_context *context); -static const char *get_simple_binary_op_name(OpExpr *expr); static bool isSimpleNode(Node *node, Node *parentNode, int prettyFlags); static void appendContextKeyword(deparse_context *context, const char *str, int indentBefore, int indentAfter, int indentPlus); @@ -7983,7 +7982,7 @@ get_parameter(Param *param, deparse_context *context) * helper function for isSimpleNode * will return single char binary operator name, or NULL if it's not */ -static const char * +const char * get_simple_binary_op_name(OpExpr *expr) { List *args = expr->args; diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 7b030463013..cd4f6fd51b4 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -684,6 +684,7 @@ static char *recovery_target_lsn_string; /* should be static, but commands/variable.c needs to get at this */ char *role_string; +bool lazy_process_sublink = true; /* * Displayable names for context types (enum GucContext) @@ -973,6 +974,17 @@ static const unit_conversion time_unit_conversion_table[] = static struct config_bool ConfigureNamesBool[] = { + { + {"lazy_process_sublink", PGC_USERSET, QUERY_TUNING_METHOD, + gettext_noop("enable lazy process sublink."), + NULL, + GUC_EXPLAIN + }, + &lazy_process_sublink, + true, + NULL, NULL, NULL + }, + { {"enable_seqscan", PGC_USERSET, QUERY_TUNING_METHOD, gettext_noop("Enables the planner's use of sequential-scan plans."), diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 324d92880b5..fd8f6c995d6 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -377,6 +377,9 @@ struct PlannerInfo /* Does this query modify any partition key columns? */ bool partColsUpdated; + + int unexpanded_sublink_counter; + List *unexpanded_sublink_expr_list; }; @@ -995,6 +998,7 @@ typedef struct EquivalenceClass bool ec_has_volatile; /* the (sole) member is a volatile expr */ bool ec_below_outer_join; /* equivalence applies below an OJ */ bool ec_broken; /* failed to generate needed clauses? */ + bool ec_processed; Index ec_sortref; /* originating sortclause label, or 0 */ Index ec_min_security; /* minimum security_level in ec_sources */ Index ec_max_security; /* maximum security_level in ec_sources */ diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index f1d111063c2..425b5c68131 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -25,6 +25,12 @@ extern PGDLLIMPORT int geqo_threshold; extern PGDLLIMPORT int min_parallel_table_scan_size; extern PGDLLIMPORT int min_parallel_index_scan_size; +typedef struct pushdown_expr_info +{ + Var *outer; + Var *inner; +} pushdown_expr_info; + /* Hook for plugins to get control in set_rel_pathlist() */ typedef void (*set_rel_pathlist_hook_type) (PlannerInfo *root, RelOptInfo *rel, @@ -62,7 +68,7 @@ extern void create_partial_bitmap_paths(PlannerInfo *root, RelOptInfo *rel, Path *bitmapqual); extern void generate_partitionwise_join_paths(PlannerInfo *root, RelOptInfo *rel); - +extern bool try_push_outer_qual_to_sublink_query(PlannerInfo *parent, Query *subquery, List *conditions); #ifdef OPTIMIZER_DEBUG extern void debug_print_rel(PlannerInfo *root, RelOptInfo *rel); #endif diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h index bf1adfc52ac..784dfbfc42e 100644 --- a/src/include/optimizer/planmain.h +++ b/src/include/optimizer/planmain.h @@ -28,12 +28,13 @@ typedef void (*query_pathkeys_callback) (PlannerInfo *root, void *extra); * prototypes for plan/planmain.c */ extern RelOptInfo *query_planner(PlannerInfo *root, - query_pathkeys_callback qp_callback, void *qp_extra); + query_pathkeys_callback qp_callback, + void *qp_extra); /* * prototypes for plan/planagg.c */ -extern void preprocess_minmax_aggregates(PlannerInfo *root); +extern void preprocess_minmax_aggregates(PlannerInfo *root, bool lazy_process_sublink); /* * prototypes for plan/createplan.c @@ -67,6 +68,8 @@ extern Limit *make_limit(Plan *lefttree, Node *limitOffset, Node *limitCount, extern int from_collapse_limit; extern int join_collapse_limit; +#define has_unexpanded_sublink(root) ((root)->unexpanded_sublink_counter != 0) + extern void add_base_rels_to_query(PlannerInfo *root, Node *jtnode); extern void add_other_rels_to_query(PlannerInfo *root); extern void build_base_rel_tlists(PlannerInfo *root, List *final_tlist); @@ -96,6 +99,9 @@ extern RestrictInfo *build_implied_join_equality(PlannerInfo *root, Relids nullable_relids, Index security_level); extern void match_foreign_keys_to_quals(PlannerInfo *root); +extern void lazy_process_sublinks(PlannerInfo *root, bool single_result_rte); +extern bool query_has_sublink_try_pushdown_qual(PlannerInfo *root); +extern Node *lazy_process_sublink_qual(PlannerInfo *root, Node *node); /* * prototypes for plan/analyzejoins.c diff --git a/src/include/optimizer/subselect.h b/src/include/optimizer/subselect.h index 059bdf941ef..396c4c6117e 100644 --- a/src/include/optimizer/subselect.h +++ b/src/include/optimizer/subselect.h @@ -25,7 +25,7 @@ extern JoinExpr *convert_EXISTS_sublink_to_join(PlannerInfo *root, bool under_not, Relids available_rels); extern Node *SS_replace_correlation_vars(PlannerInfo *root, Node *expr); -extern Node *SS_process_sublinks(PlannerInfo *root, Node *expr, bool isQual); +extern Node *SS_process_sublinks(PlannerInfo *root, Node *expr, bool isQual, bool lazy_process, bool force_process); extern void SS_identify_outer_params(PlannerInfo *root); extern void SS_charge_for_initplans(PlannerInfo *root, RelOptInfo *final_rel); extern void SS_attach_initplans(PlannerInfo *root, Plan *plan); @@ -36,5 +36,7 @@ extern Param *SS_make_initplan_output_param(PlannerInfo *root, extern void SS_make_initplan_from_plan(PlannerInfo *root, PlannerInfo *subroot, Plan *plan, Param *prm); +extern bool condition_is_safe_pushdown_to_sublink(RestrictInfo *rinfo, Var *var); +extern void sublink_query_push_qual(Query *subquery, Node *qual, Var *var, Var *replace); #endif /* SUBSELECT_H */ diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h index aa18d304ac0..92bfc1b806e 100644 --- a/src/include/utils/guc.h +++ b/src/include/utils/guc.h @@ -288,6 +288,8 @@ extern int tcp_user_timeout; extern bool trace_sort; #endif +extern bool lazy_process_sublink; + /* * Functions exported by guc.c */ diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h index d333e5e8a56..d4ccca3fe3c 100644 --- a/src/include/utils/ruleutils.h +++ b/src/include/utils/ruleutils.h @@ -42,5 +42,6 @@ extern char *generate_opclass_name(Oid opclass); extern char *get_range_partbound_string(List *bound_datums); extern char *pg_get_statisticsobjdef_string(Oid statextid); +extern const char *get_simple_binary_op_name(OpExpr *expr); #endif /* RULEUTILS_H */ diff --git a/src/test/regress/expected/join_hash.out b/src/test/regress/expected/join_hash.out index 3a91c144a27..232ee6d15a1 100644 --- a/src/test/regress/expected/join_hash.out +++ b/src/test/regress/expected/join_hash.out @@ -926,9 +926,9 @@ WHERE -> Result Output: (hjtest_1.b * 5) -> Hash - Output: hjtest_2.a, hjtest_2.tableoid, hjtest_2.id, hjtest_2.c, hjtest_2.b + Output: hjtest_2.a, hjtest_2.tableoid, hjtest_2.b, hjtest_2.id, hjtest_2.c -> Seq Scan on public.hjtest_2 - Output: hjtest_2.a, hjtest_2.tableoid, hjtest_2.id, hjtest_2.c, hjtest_2.b + Output: hjtest_2.a, hjtest_2.tableoid, hjtest_2.b, hjtest_2.id, hjtest_2.c Filter: ((SubPlan 5) < 55) SubPlan 5 -> Result @@ -974,7 +974,7 @@ WHERE Hash Cond: (((SubPlan 1) = hjtest_1.id) AND ((SubPlan 3) = (SubPlan 2))) Join Filter: (hjtest_1.a <> hjtest_2.b) -> Seq Scan on public.hjtest_2 - Output: hjtest_2.a, hjtest_2.tableoid, hjtest_2.id, hjtest_2.c, hjtest_2.b + Output: hjtest_2.a, hjtest_2.tableoid, hjtest_2.b, hjtest_2.id, hjtest_2.c Filter: ((SubPlan 5) < 55) SubPlan 5 -> Result diff --git a/src/test/regress/expected/qual_pushdown_to_sublink.out b/src/test/regress/expected/qual_pushdown_to_sublink.out new file mode 100644 index 00000000000..b4d68ac3a12 --- /dev/null +++ b/src/test/regress/expected/qual_pushdown_to_sublink.out @@ -0,0 +1,221 @@ +CREATE SCHEMA IF NOT EXISTS test_push_qual_to_sublink; +SET search_path=test_push_qual_to_sublink,sys; +show lazy_process_sublink; + lazy_process_sublink +---------------------- + on +(1 row) + +create table ab (a int not null, b int not null) partition by list (a); +create table ab_a2 partition of ab for values in(2) partition by list (b); +create table ab_a2_b1 partition of ab_a2 for values in (1); +create table ab_a2_b2 partition of ab_a2 for values in (2); +create table ab_a2_b3 partition of ab_a2 for values in (3); +create table ab_a1 partition of ab for values in(1) partition by list (b); +create table ab_a1_b1 partition of ab_a1 for values in (1); +create table ab_a1_b2 partition of ab_a1 for values in (2); +create table ab_a1_b3 partition of ab_a1 for values in (3); +INSERT INTO ab VALUES (1,1); +INSERT INTO ab VALUES (1,2); +INSERT INTO ab VALUES (1,3); +INSERT INTO ab VALUES (2,1); +INSERT INTO ab VALUES (2,2); +INSERT INTO ab VALUES (2,3); +--1 sublink in select clause can do pushdown qual(a=1 and b=1) +EXPLAIN (VERBOSE, COSTS OFF) SELECT +y.a, (SELECT x.b FROM ab x WHERE y.a=x.a AND y.b=x.b) AS b +FROM ab y WHERE a = 1 AND b = 1; + QUERY PLAN +---------------------------------------------------------------- + Seq Scan on test_push_qual_to_sublink.ab_a1_b1 y + Output: y.a, (SubPlan 1) + Filter: ((y.a = 1) AND (y.b = 1)) + SubPlan 1 + -> Result + Output: x.b + One-Time Filter: ((y.b = 1) AND (y.a = 1)) + -> Seq Scan on test_push_qual_to_sublink.ab_a1_b1 x + Output: x.b + Filter: ((x.b = 1) AND (x.a = 1)) +(10 rows) + +SELECT +y.a, (SELECT x.b FROM ab x WHERE y.a=x.a AND y.b=x.b) AS b +FROM ab y WHERE a = 1 AND b = 1; + a | b +---+--- + 1 | 1 +(1 row) + +--2 sublink in where clause can do pushdown qual(a=1 and b=1) +EXPLAIN (VERBOSE, COSTS OFF) SELECT y.a FROM ab y +WHERE a = 1 AND b = 1 AND a in (SELECT x.b FROM ab x WHERE y.a=x.a AND y.b=x.b); + QUERY PLAN +---------------------------------------------------------------- + Seq Scan on test_push_qual_to_sublink.ab_a1_b1 y + Output: y.a + Filter: ((y.a = 1) AND (y.b = 1) AND (SubPlan 1)) + SubPlan 1 + -> Result + Output: x.b + One-Time Filter: ((y.b = 1) AND (y.a = 1)) + -> Seq Scan on test_push_qual_to_sublink.ab_a1_b1 x + Output: x.b + Filter: ((x.b = 1) AND (x.a = 1)) +(10 rows) + +SELECT y.a FROM ab y +WHERE a = 1 AND b = 1 AND a in (SELECT x.b FROM ab x WHERE y.a=x.a AND y.b=x.b); + a +--- + 1 +(1 row) + +--3 Nested sublink also supports pushdown qual +EXPLAIN (VERBOSE, COSTS OFF) +SELECT a.a, a.b, +(SELECT max(b.a) AS max + FROM ab b, ab c + WHERE b.a=c.a AND b.b=c.b AND b.a=a.a AND b.b=a.b AND + (EXISTS + (SELECT a1.a + FROM ab a1 + WHERE + a1.a = a.a AND --from uplevel 1 + a1.b = b.b AND --frem uplevel 2 + clock_timestamp() > '2020-12-11' --Keep sublink not eliminated + ) + ) +) AS c +FROM ab a WHERE a.a=1 AND a.b=1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Seq Scan on test_push_qual_to_sublink.ab_a1_b1 a + Output: a.a, a.b, (SubPlan 2) + Filter: ((a.a = 1) AND (a.b = 1)) + SubPlan 2 + -> Aggregate + Output: max(b.a) + -> Result + Output: b.a + One-Time Filter: ((a.b = 1) AND (a.a = 1)) + -> Nested Loop + Output: b.a + -> Seq Scan on test_push_qual_to_sublink.ab_a1_b1 b + Output: b.a, b.b + Filter: ((b.b = 1) AND (b.a = 1) AND (SubPlan 1)) + SubPlan 1 + -> Result + Output: a1.a + One-Time Filter: ((b.b = 1) AND (a.a = 1)) + -> Seq Scan on test_push_qual_to_sublink.ab_a1_b1 a1 + Output: a1.a + Filter: ((a1.b = 1) AND (a1.a = 1) AND (clock_timestamp() > 'Fri Dec 11 00:00:00 2020 PST'::timestamp with time zone)) + -> Seq Scan on test_push_qual_to_sublink.ab_a1_b1 c + Output: c.a, c.b + Filter: ((c.b = 1) AND (c.a = 1)) +(24 rows) + +--4 This feature does not conflict with pullUp sublink +EXPLAIN (VERBOSE, COSTS OFF) +SELECT a.a, a.b +FROM ab a +WHERE EXISTS (SELECT b.a + FROM ab b, ab c + WHERE b.a=c.a AND b.b=c.b AND b.a=a.a AND b.b=a.b AND + (EXISTS + (SELECT a1.a + FROM ab a1 + WHERE a1.a = b.a AND a1.b = b.b + ) + ) +) AND +a.a=1 AND a.b=1; + QUERY PLAN +-------------------------------------------------------------------- + Nested Loop Semi Join + Output: a.a, a.b + -> Seq Scan on test_push_qual_to_sublink.ab_a1_b1 a + Output: a.a, a.b + Filter: ((a.a = 1) AND (a.b = 1)) + -> Nested Loop Semi Join + Output: b.a, b.b + -> Nested Loop + Output: b.a, b.b + -> Seq Scan on test_push_qual_to_sublink.ab_a1_b1 b + Output: b.a, b.b + Filter: ((b.b = 1) AND (b.a = 1)) + -> Seq Scan on test_push_qual_to_sublink.ab_a1_b1 c + Output: c.a, c.b + Filter: ((c.b = 1) AND (c.a = 1)) + -> Seq Scan on test_push_qual_to_sublink.ab_a1_b1 a1 + Output: a1.a, a1.b + Filter: ((a1.b = 1) AND (a1.a = 1)) +(18 rows) + +--5 aggrefs with multiple agglevelsup +EXPLAIN (VERBOSE, COSTS OFF) +SELECT + (SELECT + (SELECT sum(foo.a + bar.b) FROM ab jazz WHERE jazz.a=foo.a AND jazz.b=foo.b) + FROM ab bar WHERE bar.a=foo.a AND bar.b=foo.b + ) FROM ab foo WHERE foo.a=1 AND foo.b=1 GROUP BY a, b; + QUERY PLAN +--------------------------------------------------------------------------- + Group + Output: (SubPlan 2), foo.a, foo.b + Group Key: foo.a, foo.b + -> Seq Scan on test_push_qual_to_sublink.ab_a1_b1 foo + Output: foo.a, foo.b + Filter: ((foo.a = 1) AND (foo.b = 1)) + SubPlan 2 + -> Aggregate + Output: (SubPlan 1) + -> Result + Output: bar.b + One-Time Filter: ((foo.b = 1) AND (foo.a = 1)) + -> Seq Scan on test_push_qual_to_sublink.ab_a1_b1 bar + Output: bar.b + Filter: ((bar.b = 1) AND (bar.a = 1)) + SubPlan 1 + -> Result + Output: sum((foo.a + bar.b)) + One-Time Filter: ((foo.b = 1) AND (foo.a = 1)) + -> Seq Scan on test_push_qual_to_sublink.ab_a1_b1 jazz + Filter: ((jazz.b = 1) AND (jazz.a = 1)) +(21 rows) + +--6 sublink in join on clause can not do pushdown +EXPLAIN (VERBOSE, COSTS OFF) SELECT y.a +FROM ab y JOIN ab z on ((y.a=z.a) AND (y.b=z.b) AND exists (SELECT count(*) FROM ab x WHERE x.a=y.a AND x.b=y.b)) +WHERE y.a = 1 AND y.b = 1; + QUERY PLAN +------------------------------------------------------------------------------ + Nested Loop + Output: y.a + -> Seq Scan on test_push_qual_to_sublink.ab_a1_b1 y + Output: y.a, y.b + Filter: ((y.b = 1) AND (y.a = 1) AND (SubPlan 1)) + SubPlan 1 + -> Aggregate + Output: count(*) + -> Append + -> Seq Scan on test_push_qual_to_sublink.ab_a1_b1 x_1 + Filter: ((x_1.a = y.a) AND (x_1.b = y.b)) + -> Seq Scan on test_push_qual_to_sublink.ab_a1_b2 x_2 + Filter: ((x_2.a = y.a) AND (x_2.b = y.b)) + -> Seq Scan on test_push_qual_to_sublink.ab_a1_b3 x_3 + Filter: ((x_3.a = y.a) AND (x_3.b = y.b)) + -> Seq Scan on test_push_qual_to_sublink.ab_a2_b1 x_4 + Filter: ((x_4.a = y.a) AND (x_4.b = y.b)) + -> Seq Scan on test_push_qual_to_sublink.ab_a2_b2 x_5 + Filter: ((x_5.a = y.a) AND (x_5.b = y.b)) + -> Seq Scan on test_push_qual_to_sublink.ab_a2_b3 x_6 + Filter: ((x_6.a = y.a) AND (x_6.b = y.b)) + -> Seq Scan on test_push_qual_to_sublink.ab_a1_b1 z + Output: z.a, z.b + Filter: ((z.b = 1) AND (z.a = 1)) +(24 rows) + +DROP SCHEMA test_push_qual_to_sublink CASCADE; +NOTICE: drop cascades to table ab diff --git a/src/test/regress/expected/subselect.out b/src/test/regress/expected/subselect.out index 4e8ddc70613..2df4d6e15b5 100644 --- a/src/test/regress/expected/subselect.out +++ b/src/test/regress/expected/subselect.out @@ -1063,7 +1063,7 @@ where o.ten = 0; SubPlan 1 -> Seq Scan on public.int4_tbl Output: int4_tbl.f1 - Filter: (int4_tbl.f1 <= $0) + Filter: (int4_tbl.f1 <= $1) (14 rows) select sum(ss.tst::int) from diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 5b0c73d7e37..3060801f2f1 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -134,3 +134,4 @@ test: fast_default # run stats by itself because its delay may be insufficient under heavy load test: stats +test: qual_pushdown_to_sublink diff --git a/src/test/regress/sql/qual_pushdown_to_sublink.sql b/src/test/regress/sql/qual_pushdown_to_sublink.sql new file mode 100644 index 00000000000..375e9aef91b --- /dev/null +++ b/src/test/regress/sql/qual_pushdown_to_sublink.sql @@ -0,0 +1,86 @@ +CREATE SCHEMA IF NOT EXISTS test_push_qual_to_sublink; +SET search_path=test_push_qual_to_sublink,sys; + +show lazy_process_sublink; + +create table ab (a int not null, b int not null) partition by list (a); +create table ab_a2 partition of ab for values in(2) partition by list (b); +create table ab_a2_b1 partition of ab_a2 for values in (1); +create table ab_a2_b2 partition of ab_a2 for values in (2); +create table ab_a2_b3 partition of ab_a2 for values in (3); +create table ab_a1 partition of ab for values in(1) partition by list (b); +create table ab_a1_b1 partition of ab_a1 for values in (1); +create table ab_a1_b2 partition of ab_a1 for values in (2); +create table ab_a1_b3 partition of ab_a1 for values in (3); + +INSERT INTO ab VALUES (1,1); +INSERT INTO ab VALUES (1,2); +INSERT INTO ab VALUES (1,3); +INSERT INTO ab VALUES (2,1); +INSERT INTO ab VALUES (2,2); +INSERT INTO ab VALUES (2,3); + +--1 sublink in select clause can do pushdown qual(a=1 and b=1) +EXPLAIN (VERBOSE, COSTS OFF) SELECT +y.a, (SELECT x.b FROM ab x WHERE y.a=x.a AND y.b=x.b) AS b +FROM ab y WHERE a = 1 AND b = 1; + +SELECT +y.a, (SELECT x.b FROM ab x WHERE y.a=x.a AND y.b=x.b) AS b +FROM ab y WHERE a = 1 AND b = 1; + +--2 sublink in where clause can do pushdown qual(a=1 and b=1) +EXPLAIN (VERBOSE, COSTS OFF) SELECT y.a FROM ab y +WHERE a = 1 AND b = 1 AND a in (SELECT x.b FROM ab x WHERE y.a=x.a AND y.b=x.b); + +SELECT y.a FROM ab y +WHERE a = 1 AND b = 1 AND a in (SELECT x.b FROM ab x WHERE y.a=x.a AND y.b=x.b); + +--3 Nested sublink also supports pushdown qual +EXPLAIN (VERBOSE, COSTS OFF) +SELECT a.a, a.b, +(SELECT max(b.a) AS max + FROM ab b, ab c + WHERE b.a=c.a AND b.b=c.b AND b.a=a.a AND b.b=a.b AND + (EXISTS + (SELECT a1.a + FROM ab a1 + WHERE + a1.a = a.a AND --from uplevel 1 + a1.b = b.b AND --frem uplevel 2 + clock_timestamp() > '2020-12-11' --Keep sublink not eliminated + ) + ) +) AS c +FROM ab a WHERE a.a=1 AND a.b=1; + +--4 This feature does not conflict with pullUp sublink +EXPLAIN (VERBOSE, COSTS OFF) +SELECT a.a, a.b +FROM ab a +WHERE EXISTS (SELECT b.a + FROM ab b, ab c + WHERE b.a=c.a AND b.b=c.b AND b.a=a.a AND b.b=a.b AND + (EXISTS + (SELECT a1.a + FROM ab a1 + WHERE a1.a = b.a AND a1.b = b.b + ) + ) +) AND +a.a=1 AND a.b=1; + +--5 aggrefs with multiple agglevelsup +EXPLAIN (VERBOSE, COSTS OFF) +SELECT + (SELECT + (SELECT sum(foo.a + bar.b) FROM ab jazz WHERE jazz.a=foo.a AND jazz.b=foo.b) + FROM ab bar WHERE bar.a=foo.a AND bar.b=foo.b + ) FROM ab foo WHERE foo.a=1 AND foo.b=1 GROUP BY a, b; + +--6 sublink in join on clause can not do pushdown +EXPLAIN (VERBOSE, COSTS OFF) SELECT y.a +FROM ab y JOIN ab z on ((y.a=z.a) AND (y.b=z.b) AND exists (SELECT count(*) FROM ab x WHERE x.a=y.a AND x.b=y.b)) +WHERE y.a = 1 AND y.b = 1; + +DROP SCHEMA test_push_qual_to_sublink CASCADE; -- 2.32.0 (Apple Git-132)