From fe564024bb4815e0f7a30238fe36d08ed9179a80 Mon Sep 17 00:00:00 2001 From: "dgrowley@gmail.com" Date: Sun, 13 Jan 2019 11:06:10 +1300 Subject: [PATCH v10] Forgo generating single-subpath Append and MergeAppend paths Previously, when an Append or MergeAppend contained a single subpath we still included the node in the final plan. There was little reason for this since these node types are meant to append the results of multiple nodes. Here we change things so that we no longer generate any single sub-path Append/MergeAppend paths. This can result in slightly faster query execution since we no longer need to needlessly pull tuples through an additional node. Additionally, when such a path would have only contained a single subpath, we now gain additional flexability in the shape of plans that we produce. For example, both Append and MergeAppend don't support mark and restore, but Index scans do, so this may save having a Materialize node which technically would not have been required. --- contrib/postgres_fdw/expected/postgres_fdw.out | 13 +- src/backend/executor/execAmi.c | 15 + src/backend/optimizer/path/allpaths.c | 106 ++++- src/backend/optimizer/path/equivclass.c | 48 +++ src/backend/optimizer/path/joinrels.c | 3 +- src/backend/optimizer/plan/createplan.c | 196 +++++++-- src/backend/optimizer/plan/planner.c | 7 +- src/backend/optimizer/prep/prepjointree.c | 3 + src/backend/optimizer/prep/prepunion.c | 9 +- src/backend/optimizer/util/appendinfo.c | 86 ++++ src/backend/optimizer/util/clauses.c | 46 +++ src/backend/optimizer/util/paramassign.c | 5 + src/backend/optimizer/util/pathnode.c | 31 +- src/include/nodes/relation.h | 47 ++- src/include/optimizer/appendinfo.h | 4 + src/include/optimizer/clauses.h | 2 + src/include/optimizer/pathnode.h | 4 +- src/include/optimizer/paths.h | 2 + src/test/regress/expected/inherit.out | 73 ++-- src/test/regress/expected/partition_join.out | 61 ++- src/test/regress/expected/partition_prune.out | 526 +++++++++++-------------- src/test/regress/expected/rowsecurity.out | 22 +- src/test/regress/expected/union.out | 9 +- 23 files changed, 883 insertions(+), 435 deletions(-) diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index bb92d9d37a..070557c912 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -8374,14 +8374,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 12cb18c18f..1ba13f7a63 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 bc389b52e4..39924e586d 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -136,6 +136,8 @@ static void subquery_push_qual(Query *subquery, static void recurse_push_qual(Node *setOp, Query *topquery, RangeTblEntry *rte, Index rti, Node *qual); static void remove_unused_subquery_outputs(Query *subquery, RelOptInfo *rel); +static void generate_proxy_paths(PlannerInfo *root, RelOptInfo *rel, + RelOptInfo *childrel); /* @@ -1632,15 +1634,35 @@ 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 (subpaths_valid) - add_path(rel, (Path *) create_append_path(root, 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. This allows full flexibility for generating + * plans as if the only-child relation had been queried directly. + */ + if (list_length(subpaths) == 1) + { + generate_proxy_paths(root, rel, + ((Path *) linitial(subpaths))->parent); + + /* no need to generate any other paths */ + return; + } + else + { + /* + * Otherwise, build an unordered, unparameterized Append path for + * the rel. (Note: this is correct even if we have no live + * subpaths due to constraint exclusion.) + */ + add_path(rel, (Path *) create_append_path(root, rel, subpaths, NIL, + NULL, 0, false, + partitioned_rels, -1, + NIL, NIL, false)); + } + } /* * Consider an append of unordered, unparameterized partial paths. Make @@ -1683,7 +1705,8 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, appendpath = create_append_path(root, 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 @@ -1732,7 +1755,8 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, appendpath = create_append_path(root, 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); } @@ -1791,10 +1815,61 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, } if (subpaths_valid) - add_path(rel, (Path *) - create_append_path(root, 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(root, 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(root, 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(root, rel, + list_make1(path), NIL, PATH_REQ_OUTER(path), + path->parallel_workers, false, NULL, -1, + oldtlist, newtlist, true)); } } @@ -2059,7 +2134,8 @@ set_dummy_rel_pathlist(RelOptInfo *rel) rel->partial_pathlist = NIL; add_path(rel, (Path *) create_append_path(NULL, 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() diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index 6e134ae1d2..e563421eca 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -2094,6 +2094,54 @@ 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 *ec = lfirst_node(EquivalenceClass, lc1); + ListCell *lc2; + + /* + * No need to search in eclasses with volatile expressions, there will + * be no child exprs in here. + */ + if (ec->ec_has_volatile) + continue; + + foreach(lc2, ec->ec_members) + { + EquivalenceMember *em = lfirst_node(EquivalenceMember, lc2); + + if (!em->em_is_child) + continue; /* ignore non-child members */ + + if (em->em_is_const) + continue; /* ignore consts here */ + + /* skip if it doesn't reference these childrelids */ + if (!bms_overlap(em->em_relids, childrelids)) + continue; + + 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. + */ + ec->ec_relids = bms_add_members(ec->ec_relids, em->em_relids); + } + } +} + /* * add_child_rel_equivalences * Search for EC members that reference the parent_rel, and diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 00611a5e40..2d859768df 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -1235,7 +1235,8 @@ mark_dummy_rel(RelOptInfo *rel) /* Set up the dummy path */ add_path(rel, (Path *) create_append_path(NULL, 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 97d0c28132..14733a6fb5 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -26,6 +26,7 @@ #include "nodes/extensible.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/paramassign.h" @@ -34,6 +35,7 @@ #include "optimizer/plancat.h" #include "optimizer/planmain.h" #include "optimizer/predtest.h" +#include "optimizer/prep.h" #include "optimizer/restrictinfo.h" #include "optimizer/subselect.h" #include "optimizer/tlist.h" @@ -77,12 +79,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 +388,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, @@ -758,6 +761,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, @@ -771,6 +780,18 @@ build_path_tlist(PlannerInfo *root, Path *path) return tlist; } +/* + * 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, @@ -804,9 +825,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; /* @@ -1020,15 +1045,51 @@ 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; RelOptInfo *rel = best_path->path.parent; PartitionPruneInfo *partpruneinfo = NULL; + /* + * 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, CP_EXACT_TLIST); + + 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 @@ -1097,13 +1158,6 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path) prunequal); } - /* - * 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, partpruneinfo); @@ -1250,6 +1304,14 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path) node->mergeplans = subplans; node->part_prune_info = partpruneinfo; + /* + * 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; } @@ -1273,6 +1335,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); @@ -1379,7 +1443,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); @@ -1618,6 +1683,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; } @@ -1842,6 +1916,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), @@ -1907,6 +1983,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, @@ -2100,13 +2178,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, @@ -2145,6 +2227,13 @@ create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path) Query *subparse = subroot->parse; 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. @@ -3155,6 +3244,11 @@ create_tidscan_plan(PlannerInfo *root, TidPath *best_path, replace_nestloop_params(root, (Node *) scan_clauses); } + /* + * 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, @@ -3819,6 +3913,12 @@ create_nestloop_plan(PlannerInfo *root, outerrelids = best_path->outerjoinpath->parent->relids; nestParams = identify_current_nestloop_params(root, outerrelids); + /* 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, @@ -3828,6 +3928,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; @@ -4117,6 +4226,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. */ @@ -4134,6 +4251,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); @@ -4261,6 +4387,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 @@ -4281,6 +4414,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; @@ -6549,14 +6691,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 5ba612922f..a354b00091 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -3908,7 +3908,10 @@ create_degenerate_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel, 0, false, NIL, - -1); + -1, + NIL, + NIL, + false); } else { @@ -6942,7 +6945,7 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, */ rel->pathlist = list_make1(create_append_path(root, rel, NIL, NIL, NULL, 0, false, NIL, - -1)); + -1, NIL, NIL, false)); rel->partial_pathlist = NIL; set_cheapest(rel); Assert(IS_DUMMY_REL(rel)); diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index 77dbf4eba3..80b4014780 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -921,6 +921,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 55eeb5127c..dab4f1c30f 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -617,7 +617,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root, * Append the child results together. */ path = (Path *) create_append_path(root, result_rel, pathlist, NIL, - NULL, 0, false, NIL, -1); + NULL, 0, false, NIL, -1, + NIL, NIL, false); /* * For UNION ALL, we just need the Append path. For UNION, need to add @@ -673,7 +674,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root, ppath = (Path *) create_append_path(root, result_rel, NIL, partial_pathlist, NULL, parallel_workers, enable_parallel_append, - NIL, -1); + NIL, -1, NIL, NIL, false); + ppath = (Path *) create_gather_path(root, result_rel, ppath, result_rel->reltarget, NULL, NULL); @@ -783,7 +785,8 @@ generate_nonunion_paths(SetOperationStmt *op, PlannerInfo *root, * Append the child results together. */ path = (Path *) create_append_path(root, result_rel, pathlist, NIL, - NULL, 0, false, NIL, -1); + NULL, 0, false, NIL, -1, NIL, NIL, + false); /* Identify the grouping semantics */ groupList = generate_setop_grouplist(op, tlist); diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c index ca6622ece9..5b5ea08ff2 100644 --- a/src/backend/optimizer/util/appendinfo.c +++ b/src/backend/optimizer/util/appendinfo.c @@ -18,6 +18,8 @@ #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/appendinfo.h" +#include "optimizer/pathnode.h" +#include "optimizer/paths.h" #include "parser/parsetree.h" #include "utils/lsyscache.h" #include "utils/rel.h" @@ -727,3 +729,87 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos) } return appinfos; } + +/* + * 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); +} diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index f0ef1029d1..4645d60088 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -157,6 +157,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); /***************************************************************************** @@ -5524,3 +5525,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/paramassign.c b/src/backend/optimizer/util/paramassign.c index 838587c2b8..10aadc6fec 100644 --- a/src/backend/optimizer/util/paramassign.c +++ b/src/backend/optimizer/util/paramassign.c @@ -52,6 +52,7 @@ #include "nodes/nodeFuncs.h" #include "nodes/plannodes.h" +#include "optimizer/clauses.h" #include "optimizer/paramassign.h" #include "optimizer/placeholder.h" #include "rewrite/rewriteManip.h" @@ -531,6 +532,8 @@ identify_current_nestloop_params(PlannerInfo *root, Relids leftrelids) { root->curOuterParams = list_delete_cell(root->curOuterParams, cell, prev); + nlp->paramval = (Var *) replace_translatable_exprs(root, + (Node *) nlp->paramval); result = lappend(result, nlp); } else if (IsA(nlp->paramval, PlaceHolderVar) && @@ -543,6 +546,8 @@ identify_current_nestloop_params(PlannerInfo *root, Relids leftrelids) { root->curOuterParams = list_delete_cell(root->curOuterParams, cell, prev); + nlp->paramval = (Var *) replace_translatable_exprs(root, + (Node *) nlp->paramval); result = lappend(result, nlp); } else diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index b2637d0e89..e911e20369 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -1222,11 +1222,15 @@ create_append_path(PlannerInfo *root, 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 is_proxy) { 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; @@ -1254,8 +1258,26 @@ create_append_path(PlannerInfo *root, 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->is_proxy = is_proxy; + + /* + * If this is a proxy Append, there can be only one subpath, so we're able + * to use its PathKeys. + */ + if (is_proxy) + { + 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 @@ -3592,7 +3614,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->is_proxy); } default: break; diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 3430061361..624b5bc94a 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -343,6 +343,16 @@ typedef struct PlannerInfo /* Does this query modify any partition key columns? */ bool partColsUpdated; + + /* + * 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; @@ -1311,6 +1321,34 @@ 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. + * + * Normally, a query has a fixed set of base relations. These base relations + * are the ones which the join search is performed upon. Proxy paths are + * useful as there are some cases where it is possible to scan another + * relation instead of scanning a base relation. In order to make this + * possible the path to the other relation must be added to the base + * relation's pathlist as a sort of "foreign" path, however, the "forign" + * word is already used by foreign data wrappers, so we call these + * "proxy paths" instead. + * + * These proxy paths never actually make it into the final plan, they're + * simply skipped over when the final plan is being created, instead a node is + * created for the real path which the proxy path is storing. This path is + * stored as a single element of the 'subpaths' List. + * + * 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 is_proxy is true. + * + * This is intended as generic infrastructure to allow paths to be added to + * relations which they don't belong to, however, its primary use case is to + * allow Appends with only a single subpath to be removed from the final plan. */ typedef struct AppendPath { @@ -1318,9 +1356,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 is_proxy; } AppendPath; #define IS_DUMMY_PATH(p) \ @@ -1331,6 +1372,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))->is_proxy) + /* * 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/appendinfo.h b/src/include/optimizer/appendinfo.h index 604e36d73c..ec9beba00d 100644 --- a/src/include/optimizer/appendinfo.h +++ b/src/include/optimizer/appendinfo.h @@ -31,5 +31,9 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, Relids child_relids, Relids top_parent_relids); extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos); +extern void promote_child_relation(PlannerInfo *root, RelOptInfo *parent, + RelOptInfo *child, + List *translate_from_exprs, + List *translate_to_exprs); #endif /* APPENDINFO_H */ diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h index 5c8580e478..301f4fc84e 100644 --- a/src/include/optimizer/clauses.h +++ b/src/include/optimizer/clauses.h @@ -88,4 +88,6 @@ extern Query *inline_set_returning_function(PlannerInfo *root, extern List *expand_function_arguments(List *args, Oid result_type, HeapTuple func_tuple); +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 bd905d3328..6555a18b12 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -68,7 +68,9 @@ extern AppendPath *create_append_path(PlannerInfo *root, 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 is_proxy); 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 666217c189..a896064773 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -158,6 +158,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/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out index f259d07535..b1f5cf85f6 100644 --- a/src/test/regress/expected/inherit.out +++ b/src/test/regress/expected/inherit.out @@ -1729,12 +1729,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 @@ -1759,20 +1758,18 @@ explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef'); (5 rows) explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd'); - QUERY PLAN ---------------------------------------------------------------------------------------- - Append - -> Seq Scan on part_ab_cd - Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[]))) -(3 rows) + QUERY PLAN +--------------------------------------------------------------------------------- + Seq Scan on part_ab_cd + Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[]))) +(2 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, @@ -1852,12 +1849,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 @@ -1980,12 +1976,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 @@ -2009,22 +2004,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 c55de5d476..db87054453 100644 --- a/src/test/regress/expected/partition_join.out +++ b/src/test/regress/expected/partition_join.out @@ -186,19 +186,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 @@ -1480,10 +1479,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 @@ -1526,10 +1524,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 @@ -1580,10 +1577,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) -(30 rows) + -> Seq Scan on prt2_l_p3_p1 t2_3 + Filter: (a = 0) +(29 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 @@ -1629,14 +1625,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 @@ -1697,9 +1691,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 @@ -1711,7 +1704,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)) -(45 rows) +(44 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 120b651bf5..8bb25a60f4 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 @@ -218,20 +212,18 @@ explain (costs off) select * from rlp where a <= 1; (5 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 @@ -384,20 +376,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 @@ -410,12 +400,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 @@ -432,12 +421,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 @@ -486,12 +474,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 @@ -528,12 +515,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; (9 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 @@ -797,20 +781,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,53 +967,47 @@ 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) -- all partitions but the default one should be pruned explain (costs off) select * from mc2p where a = 1 and b is null; - QUERY PLAN -------------------------------------------- - Append - -> Seq Scan on mc2p_default - Filter: ((b IS NULL) AND (a = 1)) -(3 rows) + QUERY PLAN +------------------------------------- + Seq Scan on mc2p_default + Filter: ((b IS NULL) AND (a = 1)) +(2 rows) explain (costs off) select * from mc2p where a is null and b is null; - QUERY PLAN ------------------------------------------------ - Append - -> Seq Scan on mc2p_default - Filter: ((a IS NULL) AND (b IS NULL)) -(3 rows) + QUERY PLAN +----------------------------------------- + Seq Scan on mc2p_default + Filter: ((a IS NULL) AND (b IS NULL)) +(2 rows) explain (costs off) select * from mc2p where a is null and b = 1; - QUERY PLAN -------------------------------------------- - Append - -> Seq Scan on mc2p_default - Filter: ((a IS NULL) AND (b = 1)) -(3 rows) + QUERY PLAN +------------------------------------- + Seq Scan on mc2p_default + Filter: ((a IS NULL) AND (b = 1)) +(2 rows) explain (costs off) select * from mc2p where a is null; - QUERY PLAN --------------------------------- - Append - -> Seq Scan on mc2p_default - Filter: (a IS NULL) -(3 rows) + QUERY PLAN +-------------------------- + Seq Scan on mc2p_default + Filter: (a IS NULL) +(2 rows) explain (costs off) select * from mc2p where b is null; - QUERY PLAN --------------------------------- - Append - -> Seq Scan on mc2p_default - Filter: (b IS NULL) -(3 rows) + QUERY PLAN +-------------------------- + Seq Scan on mc2p_default + Filter: (b IS NULL) +(2 rows) -- boolean partitioning create table boolpart (a bool) partition by list (a); @@ -1050,20 +1025,18 @@ explain (costs off) select * from boolpart where a in (true, false); (5 rows) explain (costs off) select * from boolpart where a = false; - QUERY PLAN ------------------------------- - Append - -> Seq Scan on boolpart_f - Filter: (NOT a) -(3 rows) + QUERY PLAN +------------------------ + Seq Scan on boolpart_f + Filter: (NOT a) +(2 rows) explain (costs off) select * from boolpart where not a = false; - QUERY PLAN ------------------------------- - Append - -> Seq Scan on boolpart_t - Filter: a -(3 rows) + QUERY PLAN +------------------------ + Seq Scan on boolpart_t + Filter: a +(2 rows) explain (costs off) select * from boolpart where a is true or a is not true; QUERY PLAN @@ -1076,12 +1049,11 @@ explain (costs off) select * from boolpart where a is true or a is not true; (5 rows) explain (costs off) select * from boolpart where a is not true; - QUERY PLAN ---------------------------------- - Append - -> Seq Scan on boolpart_f - Filter: (a IS NOT TRUE) -(3 rows) + QUERY PLAN +--------------------------- + Seq Scan on boolpart_f + Filter: (a IS NOT TRUE) +(2 rows) explain (costs off) select * from boolpart where a is not true and a is not false; QUERY PLAN @@ -1190,10 +1162,9 @@ EXPLAIN (COSTS OFF) SELECT tableoid::regclass as part, a, b FROM part WHERE a IS --------------------------------------------------------------------------- Sort Sort Key: ((part_p2_p1.tableoid)::regclass), part_p2_p1.a, part_p2_p1.b - -> Append - -> Seq Scan on part_p2_p1 - Filter: (a IS NULL) -(5 rows) + -> Seq Scan on part_p2_p1 + Filter: (a IS NULL) +(4 rows) -- -- some more cases @@ -1260,13 +1231,12 @@ explain (costs off) select * from mc2p t1, lateral (select count(*) from mc3p t2 -- also here, because values for all keys are provided explain (costs off) select * from mc2p t1, lateral (select count(*) from mc3p t2 where t2.a = 1 and abs(t2.b) = 1 and t2.c = 1) s where t1.a = 1; - QUERY PLAN --------------------------------------------------------------------- + QUERY PLAN +-------------------------------------------------------------- Nested Loop -> Aggregate - -> Append - -> Seq Scan on mc3p1 t2 - Filter: ((a = 1) AND (c = 1) AND (abs(b) = 1)) + -> Seq Scan on mc3p1 t2 + Filter: ((a = 1) AND (c = 1) AND (abs(b) = 1)) -> Append -> Seq Scan on mc2p1 t1 Filter: (a = 1) @@ -1274,7 +1244,7 @@ explain (costs off) select * from mc2p t1, lateral (select count(*) from mc3p t2 Filter: (a = 1) -> Seq Scan on mc2p_default t1_2 Filter: (a = 1) -(12 rows) +(11 rows) -- -- pruning with clauses containing <> operator @@ -1395,12 +1365,11 @@ explain (costs off) select * from coll_pruning_multi where substr(a, 1) = 'a' co -- pruning, with values provided for both keys explain (costs off) select * from coll_pruning_multi where substr(a, 1) = 'e' collate "C" and substr(a, 1) = 'a' collate "POSIX"; - QUERY PLAN ---------------------------------------------------------------------------------------------------------- - Append - -> Seq Scan on coll_pruning_multi2 - Filter: ((substr(a, 1) = 'e'::text COLLATE "C") AND (substr(a, 1) = 'a'::text COLLATE "POSIX")) -(3 rows) + QUERY PLAN +--------------------------------------------------------------------------------------------------- + Seq Scan on coll_pruning_multi2 + Filter: ((substr(a, 1) = 'e'::text COLLATE "C") AND (substr(a, 1) = 'a'::text COLLATE "POSIX")) +(2 rows) -- -- LIKE operators don't prune @@ -1445,12 +1414,11 @@ explain (costs off) select * from rparted_by_int2 where a > 100000000000000; create table rparted_by_int2_maxvalue partition of rparted_by_int2 for values from (16384) to (maxvalue); -- all partitions but rparted_by_int2_maxvalue pruned explain (costs off) select * from rparted_by_int2 where a > 100000000000000; - QUERY PLAN -------------------------------------------------- - Append - -> Seq Scan on rparted_by_int2_maxvalue - Filter: (a > '100000000000000'::bigint) -(3 rows) + QUERY PLAN +------------------------------------------- + Seq Scan on rparted_by_int2_maxvalue + Filter: (a > '100000000000000'::bigint) +(2 rows) drop table lp, coll_pruning, rlp, mc3p, mc2p, boolpart, rp, coll_pruning_multi, like_op_noprune, lparted_by_int2, rparted_by_int2; -- @@ -1584,52 +1552,46 @@ explain (costs off) select * from hp where a <> 1 and b <> 'xxx'; -- pruning should work if either a value or a IS NULL clause is provided for -- each of the keys explain (costs off) select * from hp where a is null and b is null; - QUERY PLAN ------------------------------------------------ - Append - -> Seq Scan on hp0 - Filter: ((a IS NULL) AND (b IS NULL)) -(3 rows) + QUERY PLAN +----------------------------------------- + Seq Scan on hp0 + Filter: ((a IS NULL) AND (b IS NULL)) +(2 rows) explain (costs off) select * from hp where a = 1 and b is null; - QUERY PLAN -------------------------------------------- - Append - -> Seq Scan on hp1 - Filter: ((b IS NULL) AND (a = 1)) -(3 rows) + QUERY PLAN +------------------------------------- + Seq Scan on hp1 + Filter: ((b IS NULL) AND (a = 1)) +(2 rows) explain (costs off) select * from hp where a = 1 and b = 'xxx'; - QUERY PLAN -------------------------------------------------- - Append - -> Seq Scan on hp0 - Filter: ((a = 1) AND (b = 'xxx'::text)) -(3 rows) + QUERY PLAN +------------------------------------------- + Seq Scan on hp0 + Filter: ((a = 1) AND (b = 'xxx'::text)) +(2 rows) explain (costs off) select * from hp where a is null and b = 'xxx'; - QUERY PLAN ------------------------------------------------------ - Append - -> Seq Scan on hp2 - Filter: ((a IS NULL) AND (b = 'xxx'::text)) -(3 rows) + QUERY PLAN +----------------------------------------------- + Seq Scan on hp2 + Filter: ((a IS NULL) AND (b = 'xxx'::text)) +(2 rows) explain (costs off) select * from hp where a = 2 and b = 'xxx'; - QUERY PLAN -------------------------------------------------- - Append - -> Seq Scan on hp3 - Filter: ((a = 2) AND (b = 'xxx'::text)) -(3 rows) + QUERY PLAN +------------------------------------------- + Seq Scan on hp3 + Filter: ((a = 2) AND (b = 'xxx'::text)) +(2 rows) explain (costs off) select * from hp where a = 1 and b = 'abcde'; - QUERY PLAN ---------------------------------------------------- - Append - -> Seq Scan on hp2 - Filter: ((a = 1) AND (b = 'abcde'::text)) -(3 rows) + QUERY PLAN +--------------------------------------------- + Seq Scan on hp2 + Filter: ((a = 1) AND (b = 'abcde'::text)) +(2 rows) explain (costs off) select * from hp where (a = 1 and b = 'abcde') or (a = 2 and b = 'xxx') or (a is null and b is null); QUERY PLAN @@ -2878,12 +2840,11 @@ execute part_abc_q1 (1, 2, 3); -- Single partition should be scanned. explain (analyze, costs off, summary off, timing off) execute part_abc_q1 (1, 2, 3); - QUERY PLAN -------------------------------------------------------- - Append (actual rows=0 loops=1) - -> Seq Scan on part_abc_p1 (actual rows=0 loops=1) - Filter: ((a = $1) AND (b = $2) AND (c = $3)) -(3 rows) + QUERY PLAN +------------------------------------------------- + Seq Scan on part_abc_p1 (actual rows=0 loops=1) + Filter: ((a = $1) AND (b = $2) AND (c = $3)) +(2 rows) deallocate part_abc_q1; drop table part_abc; @@ -3205,12 +3166,11 @@ create table pp_arrpart (a int[]) partition by list (a); create table pp_arrpart1 partition of pp_arrpart for values in ('{1}'); create table pp_arrpart2 partition of pp_arrpart for values in ('{2, 3}', '{4, 5}'); explain (costs off) select * from pp_arrpart where a = '{1}'; - QUERY PLAN ----------------------------------------- - Append - -> Seq Scan on pp_arrpart1 - Filter: (a = '{1}'::integer[]) -(3 rows) + QUERY PLAN +---------------------------------- + Seq Scan on pp_arrpart1 + Filter: (a = '{1}'::integer[]) +(2 rows) explain (costs off) select * from pp_arrpart where a = '{1, 2}'; QUERY PLAN @@ -3262,20 +3222,18 @@ select tableoid::regclass, * from pph_arrpart order by 1; (3 rows) explain (costs off) select * from pph_arrpart where a = '{1}'; - QUERY PLAN ----------------------------------------- - Append - -> Seq Scan on pph_arrpart2 - Filter: (a = '{1}'::integer[]) -(3 rows) + QUERY PLAN +---------------------------------- + Seq Scan on pph_arrpart2 + Filter: (a = '{1}'::integer[]) +(2 rows) explain (costs off) select * from pph_arrpart where a = '{1, 2}'; - QUERY PLAN ------------------------------------------- - Append - -> Seq Scan on pph_arrpart1 - Filter: (a = '{1,2}'::integer[]) -(3 rows) + QUERY PLAN +------------------------------------ + Seq Scan on pph_arrpart1 + Filter: (a = '{1,2}'::integer[]) +(2 rows) explain (costs off) select * from pph_arrpart where a in ('{4, 5}', '{1}'); QUERY PLAN @@ -3294,12 +3252,11 @@ create table pp_enumpart (a pp_colors) partition by list (a); create table pp_enumpart_green partition of pp_enumpart for values in ('green'); create table pp_enumpart_blue partition of pp_enumpart for values in ('blue'); explain (costs off) select * from pp_enumpart where a = 'blue'; - QUERY PLAN ------------------------------------------ - Append - -> Seq Scan on pp_enumpart_blue - Filter: (a = 'blue'::pp_colors) -(3 rows) + QUERY PLAN +----------------------------------- + Seq Scan on pp_enumpart_blue + Filter: (a = 'blue'::pp_colors) +(2 rows) explain (costs off) select * from pp_enumpart where a = 'black'; QUERY PLAN @@ -3316,12 +3273,11 @@ create table pp_recpart (a pp_rectype) partition by list (a); create table pp_recpart_11 partition of pp_recpart for values in ('(1,1)'); create table pp_recpart_23 partition of pp_recpart for values in ('(2,3)'); explain (costs off) select * from pp_recpart where a = '(1,1)'::pp_rectype; - QUERY PLAN -------------------------------------------- - Append - -> Seq Scan on pp_recpart_11 - Filter: (a = '(1,1)'::pp_rectype) -(3 rows) + QUERY PLAN +------------------------------------- + Seq Scan on pp_recpart_11 + Filter: (a = '(1,1)'::pp_rectype) +(2 rows) explain (costs off) select * from pp_recpart where a = '(1,2)'::pp_rectype; QUERY PLAN @@ -3337,12 +3293,11 @@ create table pp_intrangepart (a int4range) partition by list (a); create table pp_intrangepart12 partition of pp_intrangepart for values in ('[1,2]'); create table pp_intrangepart2inf partition of pp_intrangepart for values in ('[2,)'); explain (costs off) select * from pp_intrangepart where a = '[1,2]'::int4range; - QUERY PLAN ------------------------------------------- - Append - -> Seq Scan on pp_intrangepart12 - Filter: (a = '[1,3)'::int4range) -(3 rows) + QUERY PLAN +------------------------------------ + Seq Scan on pp_intrangepart12 + Filter: (a = '[1,3)'::int4range) +(2 rows) explain (costs off) select * from pp_intrangepart where a = '(1,2)'::int4range; QUERY PLAN @@ -3359,12 +3314,11 @@ create table pp_lp (a int, value int) partition by list (a); create table pp_lp1 partition of pp_lp for values in(1); create table pp_lp2 partition of pp_lp for values in(2); explain (costs off) select * from pp_lp where a = 1; - QUERY PLAN --------------------------- - Append - -> Seq Scan on pp_lp1 - Filter: (a = 1) -(3 rows) + QUERY PLAN +-------------------- + Seq Scan on pp_lp1 + Filter: (a = 1) +(2 rows) explain (costs off) update pp_lp set value = 10 where a = 1; QUERY PLAN @@ -3529,12 +3483,11 @@ explain (costs off) select * from pp_temp_parent where true; (3 rows) explain (costs off) select * from pp_temp_parent where a = 2; - QUERY PLAN ------------------------------------- - Append - -> Seq Scan on pp_temp_part_def - Filter: (a = 2) -(3 rows) + QUERY PLAN +------------------------------ + Seq Scan on pp_temp_part_def + Filter: (a = 2) +(2 rows) drop table pp_temp_parent; -- Stress run-time partition pruning a bit more, per bug reports @@ -3628,13 +3581,12 @@ create table listp2 partition of listp for values in(2) partition by list(b); create table listp2_10 partition of listp2 for values in (10); explain (analyze, costs off, summary off, timing off) select * from listp where a = (select 2) and b <> 10; - QUERY PLAN -------------------------------------------- - Append (actual rows=0 loops=1) + QUERY PLAN +-------------------------------------------- + Seq Scan on listp1 (actual rows=0 loops=1) + Filter: ((b <> 10) AND (a = $0)) InitPlan 1 (returns $0) - -> Result (actual rows=1 loops=1) - -> Seq Scan on listp1 (never executed) - Filter: ((b <> 10) AND (a = $0)) -(5 rows) + -> Result (never executed) +(4 rows) drop table listp; diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out index 1d12b01068..7200b1f3a8 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 92d427a690..da70438951 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) -- 2.16.2.windows.1