diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c index f1636a5..fd1077a 100644 --- a/src/backend/executor/execAmi.c +++ b/src/backend/executor/execAmi.c @@ -443,6 +443,21 @@ ExecSupportsMarkRestore(Path *pathnode) return false; /* childless Result */ } + case T_Append: + { + AppendPath *appendPath = castNode(AppendPath, pathnode); + + /* + * AppendPaths acting as a proxy will never appear in the + * final plan, so just return the mark/restore capability of + * the proxied subpath. + */ + if (IS_PROXY_PATH(appendPath)) + return ExecSupportsMarkRestore( + (Path *) linitial(appendPath->subpaths)); + + return false; + } default: break; } diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 4e565b3..70dae4f 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -135,6 +135,8 @@ static void recurse_push_qual(Node *setOp, Query *topquery, static void remove_unused_subquery_outputs(Query *subquery, RelOptInfo *rel); static void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, List *live_childrels); +static void generate_proxy_paths(PlannerInfo *root, RelOptInfo *rel, + RelOptInfo *childrel); /* @@ -1476,11 +1478,30 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, /* * If we found unparameterized paths for all children, build an unordered, * unparameterized Append path for the rel. (Note: this is correct even - * if we have zero or one live subpath due to constraint exclusion.) + * if we have no live subpaths due to constraint exclusion.) */ if (subpaths_valid) - add_path(rel, (Path *) create_append_path(rel, subpaths, NULL, 0, - partitioned_rels)); + { + /* + * If only a single subpath exists then we've no need for the Append + * at all. Here we'll generate a "proxy" path in rel for each of the + * subpath rel's paths. + */ + if (list_length(subpaths) == 1) + { + generate_proxy_paths(root, rel, + ((Path *) linitial(subpaths))->parent); + + /* no need to generate any other paths */ + return; + } + else + { + add_path(rel, (Path *) create_append_path(rel, subpaths, NULL, 0, + partitioned_rels, + NIL, NIL, false)); + } + } /* * Consider an append of partial unordered, unparameterized partial paths. @@ -1507,7 +1528,8 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, /* Generate a partial append path. */ appendpath = create_append_path(rel, partial_subpaths, NULL, - parallel_workers, partitioned_rels); + parallel_workers, partitioned_rels, + NIL, NIL, false); add_partial_path(rel, (Path *) appendpath); } @@ -1559,9 +1581,60 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, } if (subpaths_valid) - add_path(rel, (Path *) - create_append_path(rel, subpaths, required_outer, 0, - partitioned_rels)); + { + if (list_length(subpaths) == 1) + generate_proxy_paths(root, rel, + ((Path *) linitial(subpaths))->parent); + else + add_path(rel, (Path *) + create_append_path(rel, subpaths, required_outer, 0, + partitioned_rels, + NIL, NIL, false)); + } + } +} + +/* + * generate_proxy_paths + * Add all of childrel's paths to rel as proxy paths. + */ +static void +generate_proxy_paths(PlannerInfo *root, RelOptInfo *rel, RelOptInfo *childrel) +{ + ListCell *l; + List *oldtlist; + List *newtlist; + + /* + * Make a copy of the current target list. Each path can share the same + * copy. + */ + oldtlist = list_copy(rel->reltarget->exprs); + + /* Perform translation to determine what the new tlist will be */ + newtlist = (List *) adjust_appendrel_attrs_multilevel(root, + (Node *) rel->reltarget->exprs, + childrel->relids, + rel->relids); + + foreach(l, childrel->pathlist) + { + Path *path = (Path *) lfirst(l); + + add_path(rel, (Path *) create_append_path(rel, + list_make1(path), PATH_REQ_OUTER(path), + path->parallel_workers, NULL, + oldtlist, newtlist, true)); + } + + foreach(l, childrel->partial_pathlist) + { + Path *path = (Path *) lfirst(l); + + add_partial_path(rel, (Path *) create_append_path(rel, + list_make1(path), PATH_REQ_OUTER(path), + path->parallel_workers, NULL, + oldtlist, newtlist, true)); } } @@ -1797,7 +1870,8 @@ set_dummy_rel_pathlist(RelOptInfo *rel) rel->pathlist = NIL; rel->partial_pathlist = NIL; - add_path(rel, (Path *) create_append_path(rel, NIL, NULL, 0, NIL)); + add_path(rel, (Path *) create_append_path(rel, NIL, NULL, 0, NIL, NIL, + NIL, false)); /* * We set the cheapest path immediately, to ensure that IS_DUMMY_REL() diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index a225414..7a5bd14 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -2094,6 +2094,55 @@ match_eclasses_to_foreign_key_col(PlannerInfo *root, /* + * promote_child_rel_equivalences + * Remove em_is_child marker from any eclass members belonging to + * childrelids + */ +void +promote_child_rel_equivalences(PlannerInfo *root, Relids childrelids) +{ + ListCell *lc1; + + foreach(lc1, root->eq_classes) + { + EquivalenceClass *cur_ec = (EquivalenceClass *) lfirst(lc1); + ListCell *lc2; + + /* + * No need to search in eclasses with volatile expressions, there will + * be no child exprs in here. + */ + if (cur_ec->ec_has_volatile) + continue; + + foreach(lc2, cur_ec->ec_members) + { + EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc2); + + if (!cur_em->em_is_child) + continue; /* ignore non-child members */ + + if (cur_em->em_is_const) + continue; /* ignore consts here */ + + /* skip if it doesn't reference these childrelids */ + if (!bms_overlap(cur_em->em_relids, childrelids)) + continue; + + cur_em->em_is_child = false; + + /* + * The eclass will need to be updated to say which relids it knows + * about. These were previously not set due to the member + * belonging to a child rel. + */ + cur_ec->ec_relids = bms_add_members(cur_ec->ec_relids, + cur_em->em_relids); + } + } +} + +/* * add_child_rel_equivalences * Search for EC members that reference the parent_rel, and * add transformed members referencing the child_rel. diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 2b868c5..cd8c9f1 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -1232,7 +1232,8 @@ mark_dummy_rel(RelOptInfo *rel) rel->partial_pathlist = NIL; /* Set up the dummy path */ - add_path(rel, (Path *) create_append_path(rel, NIL, NULL, 0, NIL)); + add_path(rel, (Path *) create_append_path(rel, NIL, NULL, 0, NIL, NIL, + NIL, false)); /* Set or update cheapest_total_path and related fields */ set_cheapest(rel); diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index c802d61..0a484d7 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -35,6 +35,7 @@ #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/predtest.h" +#include "optimizer/prep.h" #include "optimizer/restrictinfo.h" #include "optimizer/subselect.h" #include "optimizer/tlist.h" @@ -73,12 +74,13 @@ static Plan *create_plan_recurse(PlannerInfo *root, Path *best_path, static Plan *create_scan_plan(PlannerInfo *root, Path *best_path, int flags); static List *build_path_tlist(PlannerInfo *root, Path *path); +static List *finalize_plan_tlist(PlannerInfo *root, List *tlist); static bool use_physical_tlist(PlannerInfo *root, Path *path, int flags); 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 Plan *create_append_plan(PlannerInfo *root, AppendPath *best_path); +static Plan *create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags); static Plan *create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path); static Result *create_result_plan(PlannerInfo *root, ResultPath *best_path); static ProjectSet *create_project_set_plan(PlannerInfo *root, ProjectSetPath *best_path); @@ -383,7 +385,7 @@ create_plan_recurse(PlannerInfo *root, Path *best_path, int flags) break; case T_Append: plan = create_append_plan(root, - (AppendPath *) best_path); + (AppendPath *) best_path, flags); break; case T_MergeAppend: plan = create_merge_append_plan(root, @@ -747,6 +749,12 @@ build_path_tlist(PlannerInfo *root, Path *path) if (path->param_info) node = replace_nestloop_params(root, node); + /* + * Perform any translations required from any Append nodes which + * were removed. + */ + node = replace_translatable_exprs(root, node); + tle = makeTargetEntry((Expr *) node, resno, NULL, @@ -761,6 +769,18 @@ build_path_tlist(PlannerInfo *root, Path *path) } /* + * finalize_plan_tlist + * Finalize the plan's targetlist. This must be used in places where + * build_path_tlist is called before create_plan_recurse is called for + * any of the plan's subplans. + */ +static List * +finalize_plan_tlist(PlannerInfo *root, List *tlist) +{ + return (List *) replace_translatable_exprs(root, (Node *) tlist); +} + +/* * use_physical_tlist * Decide whether to use a tlist matching relation structure, * rather than only those Vars actually referenced. @@ -793,9 +813,13 @@ use_physical_tlist(PlannerInfo *root, Path *path, int flags) /* * Can't do it with inheritance cases either (mainly because Append * doesn't project; this test may be unnecessary now that - * create_append_plan instructs its children to return an exact tlist). + * create_append_plan instructs its children to return an exact tlist), + * however, we must allow any inherited children that have been pulled up + * from below and Append. */ - if (rel->reloptkind != RELOPT_BASEREL) + if (rel->reloptkind != RELOPT_BASEREL && + (rel->reloptkind != RELOPT_OTHER_MEMBER_REL || + !bms_is_member(rel->relid, root->translated_childrelids))) return false; /* @@ -1000,14 +1024,56 @@ create_join_plan(PlannerInfo *root, JoinPath *best_path) * Returns a Plan node. */ static Plan * -create_append_plan(PlannerInfo *root, AppendPath *best_path) +create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags) { Append *plan; - List *tlist = build_path_tlist(root, &best_path->path); + List *tlist; List *subplans = NIL; ListCell *subpaths; /* + * If the AppendPath is acting as a proxy to a single subpath then we + * don't creating the Append node at all. Instead we'll just generate the + * plan node for the only subpath. + */ + if (IS_PROXY_PATH(best_path)) + { + Path *subpath = (Path *) linitial(best_path->subpaths); + Plan *plan; + + /* Just verify we've only one, otherwise the planner messed up */ + Assert(list_length(best_path->subpaths) == 1); + + /* + * Make the required changes to the child relation to allow it to + * be scanned in place of its parent. + */ + promote_child_relation(root, best_path->path.parent, subpath->parent, + best_path->translate_from, + best_path->translate_to); + + /* Generate a new PathTarget using the Append relation's one */ + subpath->pathtarget = copy_pathtarget(best_path->path.pathtarget); + subpath->pathtarget->exprs = (List *) replace_translatable_exprs(root, + (Node *) subpath->pathtarget->exprs); + + plan = create_plan_recurse(root, subpath, flags); + + /* + * If we require an exact tlist then we'd better use a translated + * version of the, would be, Append node's tlist. + */ + if (flags & CP_EXACT_TLIST) + plan->targetlist = build_path_tlist(root, &best_path->path); + + copy_generic_path_info(plan, (Path *) subpath); + + return plan; + } + + tlist = build_path_tlist(root, &best_path->path); + + /* * The subpaths list could be empty, if every child was proven empty by * constraint exclusion. In that case generate a dummy plan that returns * no rows. @@ -1043,13 +1109,6 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path) subplans = lappend(subplans, subplan); } - /* - * XXX ideally, if there's just one child, we'd not bother to generate an - * Append node but just return the single child. At the moment this does - * not work because the varno of the child scan plan won't match the - * parent-rel Vars it'll be asked to emit. - */ - plan = make_append(subplans, tlist, best_path->partitioned_rels); copy_generic_path_info(&plan->plan, (Path *) best_path); @@ -1161,6 +1220,8 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path) node->partitioned_rels = best_path->partitioned_rels; node->mergeplans = subplans; + plan->targetlist = finalize_plan_tlist(root, plan->targetlist); + return (Plan *) node; } @@ -1184,6 +1245,8 @@ create_result_plan(PlannerInfo *root, ResultPath *best_path) /* best_path->quals is just bare clauses */ quals = order_qual_clauses(root, best_path->quals); + quals = (List *) replace_translatable_exprs(root, (Node *) quals); + plan = make_result(tlist, (Node *) quals, NULL); copy_generic_path_info(&plan->plan, (Path *) best_path); @@ -1290,7 +1353,8 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path, int flags) * sorting or stuff has to be added. */ in_operators = best_path->in_operators; - uniq_exprs = best_path->uniq_exprs; + uniq_exprs = (List *) replace_translatable_exprs(root, + (Node *) best_path->uniq_exprs); /* initialize modified subplan tlist as just the "required" vars */ newtlist = build_path_tlist(root, &best_path->path); @@ -1539,6 +1603,14 @@ create_gather_merge_plan(PlannerInfo *root, GatherMergePath *best_path) /* use parallel mode for parallel plans. */ root->glob->parallelModeNeeded = true; + /* + * Since we called build_path_tlist() before create_plan_recurse some + * Append nodes may have been removed and new translation Vars added, + * we'll need to perform a final translation, just in case. + */ + gm_plan->plan.targetlist = finalize_plan_tlist(root, + gm_plan->plan.targetlist); + return gm_plan; } @@ -1684,6 +1756,8 @@ create_group_plan(PlannerInfo *root, GroupPath *best_path) quals = order_qual_clauses(root, best_path->qual); + quals = (List *) replace_translatable_exprs(root, (Node *) quals); + plan = make_group(tlist, quals, list_length(best_path->groupClause), @@ -1749,6 +1823,8 @@ create_agg_plan(PlannerInfo *root, AggPath *best_path) quals = order_qual_clauses(root, best_path->qual); + quals = (List *) replace_translatable_exprs(root, (Node *) quals); + plan = make_agg(tlist, quals, best_path->aggstrategy, best_path->aggsplit, @@ -1988,6 +2064,13 @@ create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path) Plan *plan; /* + * This minmaxagg may have been below a single path Append node which + * has been removed. Apply expr translations, if required. + */ + mminfo->target = (Expr *) replace_translatable_exprs(root, + (Node *) mminfo->target); + + /* * Generate the plan for the subquery. We already have a Path, but we * have to convert it to a Plan and attach a LIMIT node above it. * Since we are entering a different planner context (subroot), @@ -3042,6 +3125,11 @@ create_tidscan_plan(PlannerInfo *root, TidPath *best_path, ortidquals = list_make1(make_orclause(ortidquals)); scan_clauses = list_difference(scan_clauses, ortidquals); + /* + * tidquals won't need to be passed through replace_translatable_exprs() + * as ctid is the same varattno in every relation. + */ + scan_plan = make_tidscan(tlist, scan_clauses, scan_relid, @@ -3718,6 +3806,8 @@ create_nestloop_plan(PlannerInfo *root, { root->curOuterParams = list_delete_cell(root->curOuterParams, cell, prev); + nlp->paramval = (Var *) replace_translatable_exprs(root, + (Node *) nlp->paramval); nestParams = lappend(nestParams, nlp); } else if (IsA(nlp->paramval, PlaceHolderVar) && @@ -3730,12 +3820,20 @@ create_nestloop_plan(PlannerInfo *root, { root->curOuterParams = list_delete_cell(root->curOuterParams, cell, prev); + nlp->paramval = (Var *) replace_translatable_exprs(root, + (Node *) nlp->paramval); nestParams = lappend(nestParams, nlp); } else prev = cell; } + /* perform translation of join clauses, if required */ + joinclauses = (List *) replace_translatable_exprs(root, + (Node *) joinclauses); + otherclauses = (List *) replace_translatable_exprs(root, + (Node *) otherclauses); + join_plan = make_nestloop(tlist, joinclauses, otherclauses, @@ -3745,6 +3843,9 @@ create_nestloop_plan(PlannerInfo *root, best_path->jointype, best_path->inner_unique); + join_plan->join.plan.targetlist = finalize_plan_tlist(root, + join_plan->join.plan.targetlist); + copy_generic_path_info(&join_plan->join.plan, &best_path->path); return join_plan; @@ -4038,6 +4139,14 @@ create_mergejoin_plan(PlannerInfo *root, * than we need for the current mergejoin. */ + /* perform translation of join clauses, if required */ + mergeclauses = (List *) replace_translatable_exprs(root, + (Node *) mergeclauses); + joinclauses = (List *) replace_translatable_exprs(root, + (Node *) joinclauses); + otherclauses = (List *) replace_translatable_exprs(root, + (Node *) otherclauses); + /* * Now we can build the mergejoin node. */ @@ -4055,6 +4164,9 @@ create_mergejoin_plan(PlannerInfo *root, best_path->jpath.inner_unique, best_path->skip_mark_restore); + join_plan->join.plan.targetlist = finalize_plan_tlist(root, + join_plan->join.plan.targetlist); + /* Costs of sort and material steps are included in path cost already */ copy_generic_path_info(&join_plan->join.plan, &best_path->jpath.path); @@ -4181,6 +4293,14 @@ create_hashjoin_plan(PlannerInfo *root, copy_plan_costsize(&hash_plan->plan, inner_plan); hash_plan->plan.startup_cost = hash_plan->plan.total_cost; + /* perform translation of join clauses, if required */ + hashclauses = (List *) replace_translatable_exprs(root, + (Node *) hashclauses); + joinclauses = (List *) replace_translatable_exprs(root, + (Node *) joinclauses); + otherclauses = (List *) replace_translatable_exprs(root, + (Node *) otherclauses); + join_plan = make_hashjoin(tlist, joinclauses, otherclauses, @@ -4190,6 +4310,9 @@ create_hashjoin_plan(PlannerInfo *root, best_path->jpath.jointype, best_path->jpath.inner_unique); + join_plan->join.plan.targetlist = finalize_plan_tlist(root, + join_plan->join.plan.targetlist); + copy_generic_path_info(&join_plan->join.plan, &best_path->jpath.path); return join_plan; @@ -6573,14 +6696,24 @@ is_projection_capable_path(Path *path) case T_RecursiveUnion: return false; case T_Append: + { + AppendPath *apath = (AppendPath *) path; + /* + * For Appends acting as a proxy to a single subpath, just + * return the projection capability of that subpath. + */ + if (IS_PROXY_PATH(apath)) + return is_projection_capable_path( + (Path *) linitial(apath->subpaths)); - /* - * Append can't project, but if it's being used to represent a - * dummy path, claim that it can project. This prevents us from - * converting a rel from dummy to non-dummy status by applying a - * projection to its dummy path. - */ - return IS_DUMMY_PATH(path); + /* + * Otherwise, Append can't project, but if it's being used to + * represent a dummy path, claim that it can project. This + * prevents us from converting a rel from dummy to non-dummy + * status by applying a projection to its dummy path. + */ + return IS_DUMMY_PATH(path); + } case T_ProjectSet: /* diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index d58635c..17f1f41 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -3679,7 +3679,10 @@ create_grouping_paths(PlannerInfo *root, paths, NULL, 0, - NIL); + NIL, + NIL, + NIL, + false); path->pathtarget = target; } else diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index f3bb73a..0c95dea 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -918,6 +918,9 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, subroot->hasRecursion = false; subroot->wt_param_id = -1; subroot->non_recursive_path = NULL; + subroot->translate_from_exprs = NIL; + subroot->translate_to_exprs = NIL; + subroot->translated_childrelids = NULL; /* No CTEs to worry about */ Assert(subquery->cteList == NIL); diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 1c84a2c..d9f42d3 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -118,6 +118,9 @@ static void make_inh_translation_list(Relation oldrelation, List **translated_vars); static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, List *translated_vars); +static List *find_all_appinfos_for_child(PlannerInfo *root, + RelOptInfo *parent, + RelOptInfo *child); static Node *adjust_appendrel_attrs_mutator(Node *node, adjust_appendrel_attrs_context *context); static Relids adjust_child_relids(Relids relids, int nappinfos, @@ -590,7 +593,8 @@ generate_union_path(SetOperationStmt *op, PlannerInfo *root, /* * Append the child results together. */ - path = (Path *) create_append_path(result_rel, pathlist, NULL, 0, NIL); + path = (Path *) create_append_path(result_rel, pathlist, NULL, 0, NIL, + NIL, NIL, false); /* We have to manually jam the right tlist into the path; ick */ path->pathtarget = create_pathtarget(root, tlist); @@ -702,7 +706,8 @@ generate_nonunion_path(SetOperationStmt *op, PlannerInfo *root, /* * Append the child results together. */ - path = (Path *) create_append_path(result_rel, pathlist, NULL, 0, NIL); + path = (Path *) create_append_path(result_rel, pathlist, NULL, 0, NIL, + NIL, NIL, false); /* We have to manually jam the right tlist into the path; ick */ path->pathtarget = create_pathtarget(root, tlist); @@ -1927,6 +1932,106 @@ translate_col_privs(const Bitmapset *parent_privs, } /* + * find_all_appinfos_for_child + * Find the all necessary AppendRelInfos required to translate Vars from + * parent into child. + * + * Note that it can take multiple AppendRelInfos to perform this translation + * since partition tables may themselves be partition parents to more + * partitions. + */ +static List * +find_all_appinfos_for_child(PlannerInfo *root, RelOptInfo *parent, + RelOptInfo *child) +{ + List *appinfolist = NIL; + + switch (parent->reloptkind) + { + case RELOPT_BASEREL: + case RELOPT_OTHER_MEMBER_REL: + while (child != parent) + { + AppendRelInfo *appinfo = find_childrel_appendrelinfo(root, child); + /* prepend it to the list. The order is critical */ + appinfolist = lcons(appinfo, appinfolist); + child = find_base_rel(root, appinfo->parent_relid); + } + break; + + case RELOPT_JOINREL: + { + int relid = -1; + while ((relid = bms_next_member(child->relids, relid)) >= 0) + { + RelOptInfo *childbaserel = find_base_rel(root, relid); + + while (childbaserel->reloptkind == RELOPT_OTHER_MEMBER_REL) + { + AppendRelInfo *appinfo = find_childrel_appendrelinfo(root, childbaserel); + appinfolist = lcons(appinfo, appinfolist); + childbaserel = find_base_rel(root, appinfo->parent_relid); + } + } + break; + } + default: + elog(ERROR, "unexpected reloptkind: %d", + (int) parent->reloptkind); + } + + return appinfolist; +} + +/* + * promote_child_relation + * Make the required changes to allow a child relation to be used instead + * of an Append node. + */ +void +promote_child_relation(PlannerInfo *root, RelOptInfo *parent, + RelOptInfo *child, + List *translate_from_exprs, + List *translate_to_exprs) +{ + List *appinfos; + ListCell *lc; + + root->translate_from_exprs = list_concat(root->translate_from_exprs, + list_copy(translate_from_exprs)); + + root->translate_to_exprs = list_concat(root->translate_to_exprs, + list_copy(translate_to_exprs)); + + root->translated_childrelids = + bms_add_members(root->translated_childrelids, child->relids); + + appinfos = find_all_appinfos_for_child(root, parent, child); + + foreach(lc, appinfos) + { + AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); + RelOptInfo *parent = find_base_rel(root, appinfo->parent_relid); + RelOptInfo *child = find_base_rel(root, appinfo->child_relid); + + /* + * Some childrel equivalences may not exist due to some eclasses + * having been added since add_child_rel_equivalences was called + * originally. Calling this again will add child members for any + * newly added eclasses. + */ + add_child_rel_equivalences(root, appinfo, parent, child); + } + + /* + * Finally remove em_is_child markers for child eclass members belonging + * to this child rel. This is required so we can find eclass members for + * sort PathKeys. + */ + promote_child_rel_equivalences(root, child->relids); +} + +/* * adjust_appendrel_attrs * Copy the specified query or expression and translate Vars referring to a * parent rel to refer to the corresponding child rel instead. We also diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 652843a..03df318 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -156,6 +156,7 @@ static Query *substitute_actual_srf_parameters(Query *expr, static Node *substitute_actual_srf_parameters_mutator(Node *node, substitute_actual_srf_parameters_context *context); static bool tlist_matches_coltypelist(List *tlist, List *coltypelist); +static Node *replace_translatable_exprs_mutator(Node *node, PlannerInfo *root); /***************************************************************************** @@ -5197,3 +5198,42 @@ tlist_matches_coltypelist(List *tlist, List *coltypelist) return true; } + +/* + * replace_translatable_exprs + * Append paths with a single subpath are special because its possible + * to disregard the Append and just build the plan using that single + * subpath instead. Expr translation is required to translate the + * targetlists of nodes above the Append, this function performs that + * translation. + */ +Node * +replace_translatable_exprs(PlannerInfo *root, Node *expr) +{ + if (root->translate_from_exprs != NIL) + return replace_translatable_exprs_mutator(expr, root); + return expr; +} + +static Node * +replace_translatable_exprs_mutator(Node *node, PlannerInfo *root) +{ + if (node == NULL) + return NULL; + + if (IsA(node, Var)) + { + ListCell *fromlc; + ListCell *tolc; + + forboth(fromlc, root->translate_from_exprs, tolc, root->translate_to_exprs) + { + if (equal(node, lfirst(fromlc))) + return (Node *) copyObject(lfirst(tolc)); + } + return node; + } + return expression_tree_mutator(node, + replace_translatable_exprs_mutator, + (void *) root); +} diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index 2d491eb..b59aa5e 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -1206,14 +1206,22 @@ create_tidscan_path(PlannerInfo *root, RelOptInfo *rel, List *tidquals, * pathnode. * * Note that we must handle subpaths = NIL, representing a dummy access path. + * If isproxy is true, then we expect only a single subpath. translate_from + * and translate_to can be set to allow translation of Exprs between the + * subpath and the Append Rel. */ AppendPath * create_append_path(RelOptInfo *rel, List *subpaths, Relids required_outer, - int parallel_workers, List *partitioned_rels) + int parallel_workers, List *partitioned_rels, + List *translate_from, List *translate_to, + bool isproxy) { AppendPath *pathnode = makeNode(AppendPath); ListCell *l; + /* Might both be NIL, but must contain the same number of elements. */ + Assert(list_length(translate_from) == list_length(translate_to)); + pathnode->path.pathtype = T_Append; pathnode->path.parent = rel; pathnode->path.pathtarget = rel->reltarget; @@ -1222,9 +1230,26 @@ create_append_path(RelOptInfo *rel, List *subpaths, Relids required_outer, pathnode->path.parallel_aware = false; pathnode->path.parallel_safe = rel->consider_parallel; pathnode->path.parallel_workers = parallel_workers; - pathnode->path.pathkeys = NIL; /* result is always considered unsorted */ pathnode->partitioned_rels = list_copy(partitioned_rels); + pathnode->translate_from = translate_from; + pathnode->translate_to = translate_to; pathnode->subpaths = subpaths; + pathnode->isproxy = isproxy; + + /* + * If this is a proxy Append, there can one subpath, so we're able to use + * its PathKeys. + */ + if (isproxy) + { + Assert(list_length(subpaths) == 1); + pathnode->path.pathkeys = ((Path *) linitial(subpaths))->pathkeys; + } + else + { + Assert(translate_from == NIL); + pathnode->path.pathkeys = NIL; + } /* * We don't bother with inventing a cost_append(), but just do it here. diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index e085cef..2cc654f 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -317,6 +317,16 @@ typedef struct PlannerInfo /* optional private data for join_search_hook, e.g., GEQO */ void *join_search_private; + + /* + * Used for expr translation for some singleton Append nodes during + * createplan. + */ + List *translate_from_exprs; + List *translate_to_exprs; + + Relids translated_childrelids; /* All child rels that have been + * promoted to parents */ } PlannerInfo; @@ -1259,13 +1269,19 @@ typedef struct CustomPath * elements. These cases are optimized during create_append_plan. * In particular, an AppendPath with no subpaths is a "dummy" path that * is created to represent the case that a relation is provably empty. + * AppendPaths with a single subpath can become proxy paths. translate_from + * and translate_to can be set to have expressions altered when the final plan + * is created. */ typedef struct AppendPath { Path path; /* RT indexes of non-leaf tables in a partition tree */ List *partitioned_rels; + List *translate_from; + List *translate_to; List *subpaths; /* list of component Paths */ + bool isproxy; } AppendPath; #define IS_DUMMY_PATH(p) \ @@ -1276,6 +1292,9 @@ typedef struct AppendPath ((r)->cheapest_total_path != NULL && \ IS_DUMMY_PATH((r)->cheapest_total_path)) +#define IS_PROXY_PATH(p) \ + (IsA((p), AppendPath) && ((AppendPath *) (p))->isproxy) + /* * MergeAppendPath represents a MergeAppend plan, ie, the merging of sorted * results from several member plans to produce similarly-sorted output. diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h index e367221..232a0a4 100644 --- a/src/include/optimizer/clauses.h +++ b/src/include/optimizer/clauses.h @@ -85,4 +85,6 @@ extern Node *estimate_expression_value(PlannerInfo *root, Node *node); extern Query *inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte); +extern Node *replace_translatable_exprs(PlannerInfo *root, Node *expr); + #endif /* CLAUSES_H */ diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index e9ed16a..ad5787c 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -65,7 +65,9 @@ extern TidPath *create_tidscan_path(PlannerInfo *root, RelOptInfo *rel, List *tidquals, Relids required_outer); extern AppendPath *create_append_path(RelOptInfo *rel, List *subpaths, Relids required_outer, int parallel_workers, - List *partitioned_rels); + List *partitioned_rels, + List *translate_from, List *translate_to, + bool isproxy); extern MergeAppendPath *create_merge_append_path(PlannerInfo *root, RelOptInfo *rel, List *subpaths, diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index ea886b6..c8bbea0 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -156,6 +156,8 @@ extern bool exprs_known_equal(PlannerInfo *root, Node *item1, Node *item2); extern EquivalenceClass *match_eclasses_to_foreign_key_col(PlannerInfo *root, ForeignKeyOptInfo *fkinfo, int colno); +extern void promote_child_rel_equivalences(PlannerInfo *root, + Relids childrelids); extern void add_child_rel_equivalences(PlannerInfo *root, AppendRelInfo *appinfo, RelOptInfo *parent_rel, diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index 80fbfd6..a083c75 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -52,6 +52,11 @@ extern RelOptInfo *plan_set_operations(PlannerInfo *root); extern void expand_inherited_tables(PlannerInfo *root); +extern void promote_child_relation(PlannerInfo *root, RelOptInfo *parent, + RelOptInfo *child, + List *translate_from_exprs, + List *translate_to_exprs); + extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, AppendRelInfo **appinfos); diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out index c698faf..6d27dd0 100644 --- a/src/test/regress/expected/inherit.out +++ b/src/test/regress/expected/inherit.out @@ -1678,12 +1678,11 @@ explain (costs off) select * from list_parted; (4 rows) explain (costs off) select * from list_parted where a is null; - QUERY PLAN --------------------------------- - Append - -> Seq Scan on part_null_xy - Filter: (a IS NULL) -(3 rows) + QUERY PLAN +-------------------------- + Seq Scan on part_null_xy + Filter: (a IS NULL) +(2 rows) explain (costs off) select * from list_parted where a is not null; QUERY PLAN @@ -1720,12 +1719,11 @@ explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd' (7 rows) explain (costs off) select * from list_parted where a = 'ab'; - QUERY PLAN ------------------------------------------- - Append - -> Seq Scan on part_ab_cd - Filter: ((a)::text = 'ab'::text) -(3 rows) + QUERY PLAN +------------------------------------ + Seq Scan on part_ab_cd + Filter: ((a)::text = 'ab'::text) +(2 rows) create table range_list_parted ( a int, @@ -1805,12 +1803,11 @@ explain (costs off) select * from range_list_parted where a is null; /* Should only select rows from the null-accepting partition */ explain (costs off) select * from range_list_parted where b is null; - QUERY PLAN ------------------------------------- - Append - -> Seq Scan on part_40_inf_null - Filter: (b IS NULL) -(3 rows) + QUERY PLAN +------------------------------ + Seq Scan on part_40_inf_null + Filter: (b IS NULL) +(2 rows) explain (costs off) select * from range_list_parted where a is not null and a < 67; QUERY PLAN @@ -1860,20 +1857,18 @@ create table mcrparted3 partition of mcrparted for values from (11, 1, 1) to (20 create table mcrparted4 partition of mcrparted for values from (20, 10, 10) to (20, 20, 20); create table mcrparted5 partition of mcrparted for values from (20, 20, 20) to (maxvalue, maxvalue, maxvalue); explain (costs off) select * from mcrparted where a = 0; -- scans mcrparted0 - QUERY PLAN ------------------------------- - Append - -> Seq Scan on mcrparted0 - Filter: (a = 0) -(3 rows) + QUERY PLAN +------------------------ + Seq Scan on mcrparted0 + Filter: (a = 0) +(2 rows) explain (costs off) select * from mcrparted where a = 10 and abs(b) < 5; -- scans mcrparted1 - QUERY PLAN ---------------------------------------------- - Append - -> Seq Scan on mcrparted1 - Filter: ((a = 10) AND (abs(b) < 5)) -(3 rows) + QUERY PLAN +--------------------------------------- + Seq Scan on mcrparted1 + Filter: ((a = 10) AND (abs(b) < 5)) +(2 rows) explain (costs off) select * from mcrparted where a = 10 and abs(b) = 5; -- scans mcrparted1, mcrparted2 QUERY PLAN @@ -1920,12 +1915,11 @@ explain (costs off) select * from mcrparted where a > -1; -- scans all partition (13 rows) explain (costs off) select * from mcrparted where a = 20 and abs(b) = 10 and c > 10; -- scans mcrparted4 - QUERY PLAN ------------------------------------------------------------ - Append - -> Seq Scan on mcrparted4 - Filter: ((c > 10) AND (a = 20) AND (abs(b) = 10)) -(3 rows) + QUERY PLAN +----------------------------------------------------- + Seq Scan on mcrparted4 + Filter: ((c > 10) AND (a = 20) AND (abs(b) = 10)) +(2 rows) explain (costs off) select * from mcrparted where a = 20 and c > 20; -- scans mcrparted3, mcrparte4, mcrparte5 QUERY PLAN @@ -1947,22 +1941,18 @@ create table parted_minmax1 partition of parted_minmax for values from (1) to (1 create index parted_minmax1i on parted_minmax1 (a, b); insert into parted_minmax values (1,'12345'); explain (costs off) select min(a), max(a) from parted_minmax where b = '12345'; - QUERY PLAN -------------------------------------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------------------- Result InitPlan 1 (returns $0) -> Limit - -> Merge Append - Sort Key: parted_minmax1.a - -> Index Only Scan using parted_minmax1i on parted_minmax1 - Index Cond: ((a IS NOT NULL) AND (b = '12345'::text)) + -> Index Only Scan using parted_minmax1i on parted_minmax1 + Index Cond: ((a IS NOT NULL) AND (b = '12345'::text)) InitPlan 2 (returns $1) -> Limit - -> Merge Append - Sort Key: parted_minmax1_1.a DESC - -> Index Only Scan Backward using parted_minmax1i on parted_minmax1 parted_minmax1_1 - Index Cond: ((a IS NOT NULL) AND (b = '12345'::text)) -(13 rows) + -> Index Only Scan Backward using parted_minmax1i on parted_minmax1 parted_minmax1_1 + Index Cond: ((a IS NOT NULL) AND (b = '12345'::text)) +(9 rows) select min(a), max(a) from parted_minmax where b = '12345'; min | max diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out index adf6aed..cd31dc1 100644 --- a/src/test/regress/expected/partition_join.out +++ b/src/test/regress/expected/partition_join.out @@ -193,19 +193,18 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT 50 phv, * FROM prt1 WHERE prt1.b = 0) -- Join with pruned partitions from joining relations EXPLAIN (COSTS OFF) SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a < 450 AND t2.b > 250 AND t1.b = 0 ORDER BY t1.a, t2.b; - QUERY PLAN ------------------------------------------------------------ + QUERY PLAN +----------------------------------------------------- Sort Sort Key: t1.a - -> Append - -> Hash Join - Hash Cond: (t2.b = t1.a) - -> Seq Scan on prt2_p2 t2 - Filter: (b > 250) - -> Hash - -> Seq Scan on prt1_p2 t1 - Filter: ((a < 450) AND (b = 0)) -(10 rows) + -> Hash Join + Hash Cond: (t2.b = t1.a) + -> Seq Scan on prt2_p2 t2 + Filter: (b > 250) + -> Hash + -> Seq Scan on prt1_p2 t1 + Filter: ((a < 450) AND (b = 0)) +(9 rows) SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a < 450 AND t2.b > 250 AND t1.b = 0 ORDER BY t1.a, t2.b; a | c | b | c @@ -1310,10 +1309,9 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1, prt2_l t2 WHERE t1.a = t2.b AND t1 -> Seq Scan on prt2_l_p3_p1 t2_3 -> Seq Scan on prt2_l_p3_p2 t2_4 -> Hash - -> Append - -> Seq Scan on prt1_l_p3_p1 t1_3 - Filter: (b = 0) -(29 rows) + -> Seq Scan on prt1_l_p3_p1 t1_3 + Filter: (b = 0) +(28 rows) SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1, prt2_l t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b; a | c | b | c @@ -1356,10 +1354,9 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1 LEFT JOIN prt2_l t2 ON t1.a = t2.b -> Seq Scan on prt2_l_p3_p1 t2_3 -> Seq Scan on prt2_l_p3_p2 t2_4 -> Hash - -> Append - -> Seq Scan on prt1_l_p3_p1 t1_3 - Filter: (b = 0) -(30 rows) + -> Seq Scan on prt1_l_p3_p1 t1_3 + Filter: (b = 0) +(29 rows) SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1 LEFT JOIN prt2_l t2 ON t1.a = t2.b AND t1.c = t2.c WHERE t1.b = 0 ORDER BY t1.a, t2.b; a | c | b | c @@ -1411,10 +1408,9 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1 RIGHT JOIN prt2_l t2 ON t1.a = t2.b -> Seq Scan on prt1_l_p3_p1 t1_3 -> Seq Scan on prt1_l_p3_p2 t1_4 -> Hash - -> Append - -> Seq Scan on prt2_l_p3_p1 t2_3 - Filter: (a = 0) -(31 rows) + -> Seq Scan on prt2_l_p3_p1 t2_3 + Filter: (a = 0) +(30 rows) SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1 RIGHT JOIN prt2_l t2 ON t1.a = t2.b AND t1.c = t2.c WHERE t2.a = 0 ORDER BY t1.a, t2.b; a | c | b | c @@ -1460,14 +1456,12 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1_l WHERE prt1_l.b = 0) t1 Filter: (a = 0) -> Hash Full Join Hash Cond: ((prt1_l_p3_p1.a = prt2_l_p3_p1.b) AND ((prt1_l_p3_p1.c)::text = (prt2_l_p3_p1.c)::text)) - -> Append - -> Seq Scan on prt1_l_p3_p1 - Filter: (b = 0) + -> Seq Scan on prt1_l_p3_p1 + Filter: (b = 0) -> Hash - -> Append - -> Seq Scan on prt2_l_p3_p1 - Filter: (a = 0) -(33 rows) + -> Seq Scan on prt2_l_p3_p1 + Filter: (a = 0) +(31 rows) SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1_l WHERE prt1_l.b = 0) t1 FULL JOIN (SELECT * FROM prt2_l WHERE prt2_l.a = 0) t2 ON (t1.a = t2.b AND t1.c = t2.c) ORDER BY t1.a, t2.b; a | c | b | c @@ -1529,9 +1523,8 @@ SELECT * FROM prt1_l t1 LEFT JOIN LATERAL -> Seq Scan on prt1_l_p2_p2 t2_2 Filter: ((t1_2.a = a) AND ((t1_2.c)::text = (c)::text)) -> Nested Loop Left Join - -> Append - -> Seq Scan on prt1_l_p3_p1 t1_3 - Filter: (b = 0) + -> Seq Scan on prt1_l_p3_p1 t1_3 + Filter: (b = 0) -> Hash Join Hash Cond: ((t3_3.b = t2_3.a) AND ((t3_3.c)::text = (t2_3.c)::text)) -> Append @@ -1543,7 +1536,7 @@ SELECT * FROM prt1_l t1 LEFT JOIN LATERAL Filter: ((t1_3.a = a) AND ((t1_3.c)::text = (c)::text)) -> Seq Scan on prt1_l_p3_p2 t2_4 Filter: ((t1_3.a = a) AND ((t1_3.c)::text = (c)::text)) -(46 rows) +(45 rows) SELECT * FROM prt1_l t1 LEFT JOIN LATERAL (SELECT t2.a AS t2a, t2.c AS t2c, t2.b AS t2b, t3.b AS t3b, least(t1.a,t2.a,t3.b) FROM prt1_l t2 JOIN prt2_l t3 ON (t2.a = t3.b AND t2.c = t3.c)) ss diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out index de2ee4d..c20342e 100644 --- a/src/test/regress/expected/rowsecurity.out +++ b/src/test/regress/expected/rowsecurity.out @@ -1057,15 +1057,14 @@ NOTICE: f_leak => awesome science fiction (4 rows) EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle); - QUERY PLAN --------------------------------------------------------------------- - Append + QUERY PLAN +-------------------------------------------------------------- + Seq Scan on part_document_fiction + Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle)) InitPlan 1 (returns $0) -> Index Scan using uaccount_pkey on uaccount Index Cond: (pguser = CURRENT_USER) - -> Seq Scan on part_document_fiction - Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle)) -(6 rows) +(5 rows) -- pp1 ERROR INSERT INTO part_document VALUES (100, 11, 5, 'regress_rls_dave', 'testing pp1'); -- fail @@ -1136,15 +1135,14 @@ NOTICE: f_leak => awesome science fiction (4 rows) EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle); - QUERY PLAN --------------------------------------------------------------------- - Append + QUERY PLAN +-------------------------------------------------------------- + Seq Scan on part_document_fiction + Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle)) InitPlan 1 (returns $0) -> Index Scan using uaccount_pkey on uaccount Index Cond: (pguser = CURRENT_USER) - -> Seq Scan on part_document_fiction - Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle)) -(6 rows) +(5 rows) -- viewpoint from regress_rls_carol SET SESSION AUTHORIZATION regress_rls_carol; diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out index ee26b16..11d30a3 100644 --- a/src/test/regress/expected/union.out +++ b/src/test/regress/expected/union.out @@ -697,11 +697,10 @@ explain (costs off) UNION ALL SELECT 2 AS t, * FROM tenk1 b) c WHERE t = 2; - QUERY PLAN ---------------------------- - Append - -> Seq Scan on tenk1 b -(2 rows) + QUERY PLAN +--------------------- + Seq Scan on tenk1 b +(1 row) -- Test that we push quals into UNION sub-selects only when it's safe explain (costs off)