diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index f88e0a2..c0022da 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -7566,14 +7566,11 @@ SELECT t1.a,t2.b,t3.c FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) INNER J -- left outer join + nullable clasue EXPLAIN (COSTS OFF) SELECT t1.a,t2.b,t2.c FROM fprt1 t1 LEFT JOIN (SELECT * FROM fprt2 WHERE a < 10) t2 ON (t1.a = t2.b and t1.b = t2.a) WHERE t1.a < 10 ORDER BY 1,2,3; - QUERY PLAN ------------------------------------------------------------------------------------ - Sort - Sort Key: t1.a, ftprt2_p1.b, ftprt2_p1.c - -> Append - -> Foreign Scan - Relations: (public.ftprt1_p1 t1) LEFT JOIN (public.ftprt2_p1 fprt2) -(5 rows) + QUERY PLAN +----------------------------------------------------------------------- + Foreign Scan + Relations: (public.ftprt1_p1 t1) LEFT JOIN (public.ftprt2_p1 fprt2) +(2 rows) SELECT t1.a,t2.b,t2.c FROM fprt1 t1 LEFT JOIN (SELECT * FROM fprt2 WHERE a < 10) t2 ON (t1.a = t2.b and t1.b = t2.a) WHERE t1.a < 10 ORDER BY 1,2,3; a | b | c diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c index 9e78421..11950ec 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 fd1a583..128f2d8 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -136,6 +136,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); /* @@ -1545,12 +1547,31 @@ 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, NIL, - NULL, 0, false, - partitioned_rels, -1)); + { + /* + * 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, NIL, + NULL, 0, false, + partitioned_rels, -1, + NIL, NIL, false)); + } + } /* * Consider an append of unordered, unparameterized partial paths. Make @@ -1593,7 +1614,8 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, appendpath = create_append_path(rel, NIL, partial_subpaths, NULL, parallel_workers, enable_parallel_append, - partitioned_rels, -1); + partitioned_rels, -1, + NIL, NIL, false); /* * Make sure any subsequent partial paths use the same row count @@ -1642,7 +1664,8 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, appendpath = create_append_path(rel, pa_nonpartial_subpaths, pa_partial_subpaths, NULL, parallel_workers, true, - partitioned_rels, partial_rows); + partitioned_rels, partial_rows, + NIL, NIL, false); add_partial_path(rel, (Path *) appendpath); } @@ -1694,10 +1717,61 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, } if (subpaths_valid) - add_path(rel, (Path *) - create_append_path(rel, subpaths, NIL, - required_outer, 0, false, - partitioned_rels, -1)); + { + if (list_length(subpaths) == 1) + generate_proxy_paths(root, rel, + ((Path *) linitial(subpaths))->parent); + else + add_path(rel, (Path *) + create_append_path(rel, subpaths, NIL, + required_outer, 0, false, + partitioned_rels, -1, + 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), NIL, PATH_REQ_OUTER(path), + path->parallel_workers, false, NULL, -1, + 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), NIL, PATH_REQ_OUTER(path), + path->parallel_workers, false, NULL, -1, + oldtlist, newtlist, true)); } } @@ -1962,8 +2036,8 @@ set_dummy_rel_pathlist(RelOptInfo *rel) rel->partial_pathlist = NIL; add_path(rel, (Path *) create_append_path(rel, NIL, NIL, NULL, - 0, false, NIL, -1)); - + 0, false, NIL, -1, + NIL, NIL, false)); /* * We set the cheapest path immediately, to ensure that IS_DUMMY_REL() * will recognize the relation as dummy if anyone asks. This is redundant diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index 70a925c..97a77fe 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 + * contains members for. 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 a35d068..3ca4e8b 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -1231,7 +1231,8 @@ mark_dummy_rel(RelOptInfo *rel) /* Set up the dummy path */ add_path(rel, (Path *) create_append_path(rel, NIL, NIL, NULL, - 0, false, NIL, -1)); + 0, false, NIL, -1, 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 86e7e74..8df673e 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); @@ -385,7 +387,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, @@ -749,6 +751,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, @@ -763,6 +771,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. @@ -795,9 +815,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; /* @@ -1011,14 +1035,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 create 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. @@ -1054,13 +1120,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, best_path->first_partial_path, tlist, best_path->partitioned_rels); @@ -1173,6 +1232,14 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path) node->partitioned_rels = best_path->partitioned_rels; node->mergeplans = subplans; + /* + * 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 to perform any further + * translations which were added since build_path_tlist was called. + */ + plan->targetlist = finalize_plan_tlist(root, plan->targetlist); + return (Plan *) node; } @@ -1196,6 +1263,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); @@ -1302,7 +1371,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); @@ -1551,6 +1621,15 @@ 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 to perform any further + * translations which were added since build_path_tlist was called. + */ + gm_plan->plan.targetlist = finalize_plan_tlist(root, + gm_plan->plan.targetlist); + return gm_plan; } @@ -1696,6 +1775,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), @@ -1761,6 +1842,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, @@ -1954,13 +2037,17 @@ create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path) RollupData *rollup = linitial(rollups); AttrNumber *top_grpColIdx; int numGroupCols; + List *quals; top_grpColIdx = remap_groupColIdx(root, rollup->groupClause); numGroupCols = list_length((List *) linitial(rollup->gsets)); + quals = (List *) replace_translatable_exprs(root, + (Node *) best_path->qual); + plan = make_agg(build_path_tlist(root, &best_path->path), - best_path->qual, + quals, best_path->aggstrategy, AGGSPLIT_SIMPLE, numGroupCols, @@ -2000,6 +2087,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), @@ -3055,6 +3149,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, @@ -3731,6 +3830,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) && @@ -3743,12 +3844,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, @@ -3758,6 +3867,15 @@ create_nestloop_plan(PlannerInfo *root, best_path->jointype, best_path->inner_unique); + /* + * 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 to perform any further + * translations which were added since build_path_tlist was called. + */ + 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; @@ -4051,6 +4169,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. */ @@ -4068,6 +4194,15 @@ create_mergejoin_plan(PlannerInfo *root, best_path->jpath.inner_unique, best_path->skip_mark_restore); + /* + * 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 to perform any further + * translations which were added since build_path_tlist was called. + */ + 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); @@ -4194,6 +4329,13 @@ 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); /* * If parallel-aware, the executor will also need an estimate of the total * number of rows expected from all participants so that it can size the @@ -4214,6 +4356,15 @@ create_hashjoin_plan(PlannerInfo *root, best_path->jpath.jointype, best_path->jpath.inner_unique); + /* + * 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 to perform any further + * translations which were added since build_path_tlist was called. + */ + 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; @@ -6601,14 +6752,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 5387043..18cd1bc 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -3689,7 +3689,10 @@ create_grouping_paths(PlannerInfo *root, 0, false, NIL, - -1); + -1, + NIL, + NIL, + false); path->pathtarget = target; } else diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index 45d82da..b0c3894 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 e6b1534..4c239eb 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -592,7 +592,8 @@ generate_union_path(SetOperationStmt *op, PlannerInfo *root, * Append the child results together. */ path = (Path *) create_append_path(result_rel, pathlist, NIL, - NULL, 0, false, NIL, -1); + NULL, 0, false, NIL, -1, + NIL, NIL, false); /* We have to manually jam the right tlist into the path; ick */ path->pathtarget = create_pathtarget(root, tlist); @@ -704,7 +705,8 @@ generate_nonunion_path(SetOperationStmt *op, PlannerInfo *root, * Append the child results together. */ path = (Path *) create_append_path(result_rel, pathlist, NIL, - NULL, 0, false, NIL, -1); + NULL, 0, false, NIL, -1, + NIL, NIL, false); /* We have to manually jam the right tlist into the path; ick */ path->pathtarget = create_pathtarget(root, tlist); @@ -1939,6 +1941,90 @@ translate_col_privs(const Bitmapset *parent_privs, } /* + * 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) +{ + Bitmapset *cur_relids; + + /* + * First we must record the translation expressions in the PlannerInfo. + * These need to be found when the expression translation is being done + * when the final plan is being assembled. + */ + 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)); + + /* + * Record this child as having been promoted. Some places treat child + * relations in a special way, and this will give them a VIP ticket to + * adulthood, where required. + */ + root->translated_childrelids = + bms_add_members(root->translated_childrelids, child->relids); + + cur_relids = child->relids; + + do + { + AppendRelInfo **appinfos; + int nappinfos; + int i; + + appinfos = find_appinfos_by_relids(root, cur_relids, &nappinfos); + + /* free any bitmapset we used in the last iteration */ + if (cur_relids != child->relids) + bms_free(cur_relids); + + cur_relids = NULL; + + for (i = 0; i < nappinfos; i++) + { + AppendRelInfo *appinfo = appinfos[i]; + + 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); + + cur_relids = bms_add_member(cur_relids, appinfo->parent_relid); + } + + pfree(appinfos); + + /* + * There may be multiple levels between the parent and child, so keep + * going until we reach the top-level parent. + */ + } while (!bms_equal(cur_relids, parent->relids)); + + bms_free(cur_relids); + + /* + * Finally, we 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 89f27ce..2c0d3fa 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -159,6 +159,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); /***************************************************************************** @@ -5308,3 +5309,48 @@ 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 AppendPath 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. + * + * For now, we really only ever expect to translate from a Var. However, the + * Var may get translated into something other than a Var. + */ +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 *l1; + ListCell *l2; + + forboth(l1, root->translate_from_exprs, l2, root->translate_to_exprs) + { + if (equal(node, lfirst(l1))) + { + /* XXX do we need to do copyObject? */ + return (Node *) copyObject(lfirst(l2)); + } + } + 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 fe3b458..b8456f0 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -1208,17 +1208,24 @@ 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, List *partial_subpaths, Relids required_outer, int parallel_workers, bool parallel_aware, - List *partitioned_rels, double rows) + List *partitioned_rels, double rows, + 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)); Assert(!parallel_aware || parallel_workers > 0); pathnode->path.pathtype = T_Append; @@ -1229,8 +1236,26 @@ create_append_path(RelOptInfo *rel, pathnode->path.parallel_aware = parallel_aware; 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 be only 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; + } /* * For parallel append, non-partial paths are sorted by descending total @@ -3572,7 +3597,10 @@ reparameterize_path(PlannerInfo *root, Path *path, apath->path.parallel_workers, apath->path.parallel_aware, apath->partitioned_rels, - -1); + -1, + apath->translate_from, + apath->translate_to, + apath->isproxy); } default: break; diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 6bf68f3..3683ddb 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 proxy path 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; @@ -1262,6 +1272,18 @@ 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. + * + * An AppendPath with a single subpath can be set up to become a "proxy" path. + * This allows a Path which belongs to one relation to be added to the pathlist + * of some other relation. This is intended as generic infrastructure, but its + * primary use case is to allow Appends with only a single subpath to be + * removed from the final plan. + * + * A path's targetlist naturally will contain Vars belonging to its parent + * rel, so we must also provide a mechanism to allow the translation of any + * Vars which reference the original Append relation's Vars to allow them to + * be translated into the proxied path Vars. translate_from and translate_to + * serve this purpose. They must only be set when isproxy is true. */ typedef struct AppendPath { @@ -1269,9 +1291,12 @@ typedef struct AppendPath /* RT indexes of non-leaf tables in a partition tree */ List *partitioned_rels; List *subpaths; /* list of component Paths */ - /* Index of first partial path in subpaths */ int first_partial_path; + + List *translate_from; + List *translate_to; + bool isproxy; } AppendPath; #define IS_DUMMY_PATH(p) \ @@ -1282,6 +1307,10 @@ typedef struct AppendPath ((r)->cheapest_total_path != NULL && \ IS_DUMMY_PATH((r)->cheapest_total_path)) +/* Append path is acting as a proxy for its single subpath */ +#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 ba4fa4b..10e0d50 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 ef7173f..a2dad71 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -68,7 +68,9 @@ extern AppendPath *create_append_path(RelOptInfo *rel, List *subpaths, List *partial_subpaths, Relids required_outer, int parallel_workers, bool parallel_aware, - List *partitioned_rels, double rows); + List *partitioned_rels, double rows, + 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 0072b7a..2b7515f 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 89b7ef3..bc2552f 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -49,6 +49,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 a79f891..c294d65 100644 --- a/src/test/regress/expected/inherit.out +++ b/src/test/regress/expected/inherit.out @@ -1680,12 +1680,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 @@ -1722,12 +1721,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, @@ -1807,12 +1805,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 @@ -1933,12 +1930,11 @@ explain (costs off) select * from mcrparted where a > -1; -- scans all partition (15 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, mcrparted_def QUERY PLAN @@ -1962,22 +1958,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 27ab852..78664fa 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 @@ -1391,10 +1390,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 @@ -1437,10 +1435,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 @@ -1492,10 +1489,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 @@ -1541,14 +1537,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 @@ -1610,9 +1604,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 @@ -1624,7 +1617,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/partition_prune.out b/src/test/regress/expected/partition_prune.out index aabb024..c02f82c 100644 --- a/src/test/regress/expected/partition_prune.out +++ b/src/test/regress/expected/partition_prune.out @@ -43,20 +43,18 @@ explain (costs off) select * from lp where a > 'a' and a <= 'd'; (7 rows) explain (costs off) select * from lp where a = 'a'; - QUERY PLAN ------------------------------------ - Append - -> Seq Scan on lp_ad - Filter: (a = 'a'::bpchar) -(3 rows) + QUERY PLAN +----------------------------- + Seq Scan on lp_ad + Filter: (a = 'a'::bpchar) +(2 rows) explain (costs off) select * from lp where 'a' = a; /* commuted */ - QUERY PLAN ------------------------------------ - Append - -> Seq Scan on lp_ad - Filter: ('a'::bpchar = a) -(3 rows) + QUERY PLAN +----------------------------- + Seq Scan on lp_ad + Filter: ('a'::bpchar = a) +(2 rows) explain (costs off) select * from lp where a is not null; QUERY PLAN @@ -75,12 +73,11 @@ explain (costs off) select * from lp where a is not null; (11 rows) explain (costs off) select * from lp where a is null; - QUERY PLAN ------------------------------ - Append - -> Seq Scan on lp_null - Filter: (a IS NULL) -(3 rows) + QUERY PLAN +----------------------- + Seq Scan on lp_null + Filter: (a IS NULL) +(2 rows) explain (costs off) select * from lp where a = 'a' or a = 'c'; QUERY PLAN @@ -150,12 +147,11 @@ create table coll_pruning_a partition of coll_pruning for values in ('a'); create table coll_pruning_b partition of coll_pruning for values in ('b'); create table coll_pruning_def partition of coll_pruning default; explain (costs off) select * from coll_pruning where a collate "C" = 'a' collate "C"; - QUERY PLAN ---------------------------------------------- - Append - -> Seq Scan on coll_pruning_a - Filter: (a = 'a'::text COLLATE "C") -(3 rows) + QUERY PLAN +--------------------------------------- + Seq Scan on coll_pruning_a + Filter: (a = 'a'::text COLLATE "C") +(2 rows) -- collation doesn't match the partitioning collation, no pruning occurs explain (costs off) select * from coll_pruning where a collate "POSIX" = 'a' collate "POSIX"; @@ -192,20 +188,18 @@ create table rlp5 partition of rlp for values from (31) to (maxvalue) partition create table rlp5_default partition of rlp5 default; create table rlp5_1 partition of rlp5 for values from (31) to (40); explain (costs off) select * from rlp where a < 1; - QUERY PLAN -------------------------- - Append - -> Seq Scan on rlp1 - Filter: (a < 1) -(3 rows) + QUERY PLAN +------------------- + Seq Scan on rlp1 + Filter: (a < 1) +(2 rows) explain (costs off) select * from rlp where 1 > a; /* commuted */ - QUERY PLAN -------------------------- - Append - -> Seq Scan on rlp1 - Filter: (1 > a) -(3 rows) + QUERY PLAN +------------------- + Seq Scan on rlp1 + Filter: (1 > a) +(2 rows) explain (costs off) select * from rlp where a <= 1; QUERY PLAN @@ -220,20 +214,18 @@ explain (costs off) select * from rlp where a <= 1; (7 rows) explain (costs off) select * from rlp where a = 1; - QUERY PLAN -------------------------- - Append - -> Seq Scan on rlp2 - Filter: (a = 1) -(3 rows) + QUERY PLAN +------------------- + Seq Scan on rlp2 + Filter: (a = 1) +(2 rows) explain (costs off) select * from rlp where a = 1::bigint; /* same as above */ - QUERY PLAN ------------------------------------ - Append - -> Seq Scan on rlp2 - Filter: (a = '1'::bigint) -(3 rows) + QUERY PLAN +----------------------------- + Seq Scan on rlp2 + Filter: (a = '1'::bigint) +(2 rows) explain (costs off) select * from rlp where a = 1::numeric; /* no pruning */ QUERY PLAN @@ -386,20 +378,18 @@ explain (costs off) select * from rlp where a = 16; (9 rows) explain (costs off) select * from rlp where a = 16 and b in ('not', 'in', 'here'); - QUERY PLAN ----------------------------------------------------------------------------- - Append - -> Seq Scan on rlp3_default - Filter: ((a = 16) AND ((b)::text = ANY ('{not,in,here}'::text[]))) -(3 rows) + QUERY PLAN +---------------------------------------------------------------------- + Seq Scan on rlp3_default + Filter: ((a = 16) AND ((b)::text = ANY ('{not,in,here}'::text[]))) +(2 rows) explain (costs off) select * from rlp where a = 16 and b < 'ab'; - QUERY PLAN ---------------------------------------------------------- - Append - -> Seq Scan on rlp3_default - Filter: (((b)::text < 'ab'::text) AND (a = 16)) -(3 rows) + QUERY PLAN +--------------------------------------------------- + Seq Scan on rlp3_default + Filter: (((b)::text < 'ab'::text) AND (a = 16)) +(2 rows) explain (costs off) select * from rlp where a = 16 and b <= 'ab'; QUERY PLAN @@ -412,12 +402,11 @@ explain (costs off) select * from rlp where a = 16 and b <= 'ab'; (5 rows) explain (costs off) select * from rlp where a = 16 and b is null; - QUERY PLAN --------------------------------------------- - Append - -> Seq Scan on rlp3nullxy - Filter: ((b IS NULL) AND (a = 16)) -(3 rows) + QUERY PLAN +-------------------------------------- + Seq Scan on rlp3nullxy + Filter: ((b IS NULL) AND (a = 16)) +(2 rows) explain (costs off) select * from rlp where a = 16 and b is not null; QUERY PLAN @@ -434,12 +423,11 @@ explain (costs off) select * from rlp where a = 16 and b is not null; (9 rows) explain (costs off) select * from rlp where a is null; - QUERY PLAN ------------------------------------- - Append - -> Seq Scan on rlp_default_null - Filter: (a IS NULL) -(3 rows) + QUERY PLAN +------------------------------ + Seq Scan on rlp_default_null + Filter: (a IS NULL) +(2 rows) explain (costs off) select * from rlp where a is not null; QUERY PLAN @@ -488,12 +476,11 @@ explain (costs off) select * from rlp where a > 30; (7 rows) explain (costs off) select * from rlp where a = 30; /* only default is scanned */ - QUERY PLAN ----------------------------------- - Append - -> Seq Scan on rlp_default_30 - Filter: (a = 30) -(3 rows) + QUERY PLAN +---------------------------- + Seq Scan on rlp_default_30 + Filter: (a = 30) +(2 rows) explain (costs off) select * from rlp where a <= 31; QUERY PLAN @@ -530,12 +517,11 @@ explain (costs off) select * from rlp where a <= 31; (29 rows) explain (costs off) select * from rlp where a = 1 or a = 7; - QUERY PLAN --------------------------------------- - Append - -> Seq Scan on rlp2 - Filter: ((a = 1) OR (a = 7)) -(3 rows) + QUERY PLAN +-------------------------------- + Seq Scan on rlp2 + Filter: ((a = 1) OR (a = 7)) +(2 rows) explain (costs off) select * from rlp where a = 1 or b = 'ab'; QUERY PLAN @@ -580,12 +566,11 @@ explain (costs off) select * from rlp where a > 20 and a < 27; (7 rows) explain (costs off) select * from rlp where a = 29; - QUERY PLAN --------------------------------- - Append - -> Seq Scan on rlp4_default - Filter: (a = 29) -(3 rows) + QUERY PLAN +-------------------------- + Seq Scan on rlp4_default + Filter: (a = 29) +(2 rows) explain (costs off) select * from rlp where a >= 29; QUERY PLAN @@ -605,12 +590,11 @@ explain (costs off) select * from rlp where a >= 29; -- redundant clauses are eliminated explain (costs off) select * from rlp where a > 1 and a = 10; /* only default */ - QUERY PLAN ----------------------------------------- - Append - -> Seq Scan on rlp_default_10 - Filter: ((a > 1) AND (a = 10)) -(3 rows) + QUERY PLAN +---------------------------------- + Seq Scan on rlp_default_10 + Filter: ((a > 1) AND (a = 10)) +(2 rows) explain (costs off) select * from rlp where a > 1 and a >=15; /* rlp3 onwards, including default */ QUERY PLAN @@ -799,20 +783,18 @@ explain (costs off) select * from mc3p where a <= 10 and abs(b) < 10; (9 rows) explain (costs off) select * from mc3p where a = 11 and abs(b) = 0; - QUERY PLAN ---------------------------------------------- - Append - -> Seq Scan on mc3p_default - Filter: ((a = 11) AND (abs(b) = 0)) -(3 rows) + QUERY PLAN +--------------------------------------- + Seq Scan on mc3p_default + Filter: ((a = 11) AND (abs(b) = 0)) +(2 rows) explain (costs off) select * from mc3p where a = 20 and abs(b) = 10 and c = 100; - QUERY PLAN ------------------------------------------------------------- - Append - -> Seq Scan on mc3p6 - Filter: ((a = 20) AND (c = 100) AND (abs(b) = 10)) -(3 rows) + QUERY PLAN +------------------------------------------------------ + Seq Scan on mc3p6 + Filter: ((a = 20) AND (c = 100) AND (abs(b) = 10)) +(2 rows) explain (costs off) select * from mc3p where a > 20; QUERY PLAN @@ -962,12 +944,11 @@ explain (costs off) select * from mc2p where a < 2; (9 rows) explain (costs off) select * from mc2p where a = 2 and b < 1; - QUERY PLAN ---------------------------------------- - Append - -> Seq Scan on mc2p3 - Filter: ((b < 1) AND (a = 2)) -(3 rows) + QUERY PLAN +--------------------------------- + Seq Scan on mc2p3 + Filter: ((b < 1) AND (a = 2)) +(2 rows) explain (costs off) select * from mc2p where a > 1; QUERY PLAN @@ -986,12 +967,11 @@ explain (costs off) select * from mc2p where a > 1; (11 rows) explain (costs off) select * from mc2p where a = 1 and b > 1; - QUERY PLAN ---------------------------------------- - Append - -> Seq Scan on mc2p2 - Filter: ((b > 1) AND (a = 1)) -(3 rows) + QUERY PLAN +--------------------------------- + Seq Scan on mc2p2 + Filter: ((b > 1) AND (a = 1)) +(2 rows) -- boolean partitioning create table boolpart (a bool) partition by list (a); diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out index f1ae40d..5aad8b4 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 92d427a..da70438 100644 --- a/src/test/regress/expected/union.out +++ b/src/test/regress/expected/union.out @@ -812,11 +812,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)