Commit 8edd0e79 authored by Tom Lane's avatar Tom Lane

Suppress Append and MergeAppend plan nodes that have a single child.

If there's only one child relation, the Append or MergeAppend isn't
doing anything useful, and can be elided.  It does have a purpose
during planning though, which is to serve as a buffer between parent
and child Var numbering.  Therefore we keep it all the way through
to setrefs.c, and get rid of it only after fixing references in the
plan level(s) above it.  This works largely the same as setrefs.c's
ancient hack to get rid of no-op SubqueryScan nodes, and can even
share some code with that.

Note the change to make setrefs.c use apply_tlist_labeling rather than
ad-hoc code.  This has the effect of propagating the child's resjunk
and ressortgroupref labels, which formerly weren't propagated when
removing a SubqueryScan.  Doing that is demonstrably necessary for
the [Merge]Append cases, and seems harmless for SubqueryScan, if only
because trivial_subqueryscan is afraid to collapse cases where the
resjunk marking differs.  (I suspect that restriction could now be
removed, though it's unclear that it'd make any new matches possible,
since the outer query can't have references to a child resjunk column.)

David Rowley, reviewed by Alvaro Herrera and Tomas Vondra

Discussion: https://postgr.es/m/CAKJS1f_7u8ATyJ1JGTMHFoKDvZdeF-iEBhs+sM_SXowOr9cArg@mail.gmail.com
parent f21668f3
...@@ -8427,17 +8427,16 @@ SELECT t1.a,t2.b,t3.c FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) INNER J ...@@ -8427,17 +8427,16 @@ SELECT t1.a,t2.b,t3.c FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) INNER J
400 | 400 | 0008 400 | 400 | 0008
(4 rows) (4 rows)
-- left outer join + nullable clasue -- left outer join + nullable clause
EXPLAIN (COSTS OFF) EXPLAIN (VERBOSE, 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; 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 QUERY PLAN
----------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Sort Foreign Scan
Sort Key: t1.a, ftprt2_p1.b, ftprt2_p1.c Output: t1.a, ftprt2_p1.b, ftprt2_p1.c
-> Append Relations: (public.ftprt1_p1 t1) LEFT JOIN (public.ftprt2_p1 fprt2)
-> Foreign Scan Remote SQL: SELECT r6.a, r9.b, r9.c FROM (public.fprt1_p1 r6 LEFT JOIN public.fprt2_p1 r9 ON (((r6.a = r9.b)) AND ((r6.b = r9.a)) AND ((r9.a < 10)))) WHERE ((r6.a < 10)) ORDER BY r6.a ASC NULLS LAST, r9.b ASC NULLS LAST, r9.c ASC NULLS LAST
Relations: (public.ftprt1_p1 t1) LEFT JOIN (public.ftprt2_p1 fprt2) (4 rows)
(5 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; 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 a | b | c
......
...@@ -2309,8 +2309,8 @@ EXPLAIN (COSTS OFF) ...@@ -2309,8 +2309,8 @@ EXPLAIN (COSTS OFF)
SELECT t1.a,t2.b,t3.c FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) INNER JOIN fprt1 t3 ON (t2.b = t3.a) WHERE t1.a % 25 =0 ORDER BY 1,2,3; SELECT t1.a,t2.b,t3.c FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) INNER JOIN fprt1 t3 ON (t2.b = t3.a) WHERE t1.a % 25 =0 ORDER BY 1,2,3;
SELECT t1.a,t2.b,t3.c FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) INNER JOIN fprt1 t3 ON (t2.b = t3.a) WHERE t1.a % 25 =0 ORDER BY 1,2,3; SELECT t1.a,t2.b,t3.c FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) INNER JOIN fprt1 t3 ON (t2.b = t3.a) WHERE t1.a % 25 =0 ORDER BY 1,2,3;
-- left outer join + nullable clasue -- left outer join + nullable clause
EXPLAIN (COSTS OFF) EXPLAIN (VERBOSE, 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; 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;
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; 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;
......
...@@ -447,6 +447,36 @@ ExecSupportsMarkRestore(Path *pathnode) ...@@ -447,6 +447,36 @@ ExecSupportsMarkRestore(Path *pathnode)
return false; /* childless Result */ return false; /* childless Result */
} }
case T_Append:
{
AppendPath *appendPath = castNode(AppendPath, pathnode);
/*
* If there's exactly one child, then there will be no Append
* in the final plan, so we can handle mark/restore if the
* child plan node can.
*/
if (list_length(appendPath->subpaths) == 1)
return ExecSupportsMarkRestore((Path *) linitial(appendPath->subpaths));
/* Otherwise, Append can't handle it */
return false;
}
case T_MergeAppend:
{
MergeAppendPath *mapath = castNode(MergeAppendPath, pathnode);
/*
* Like the Append case above, single-subpath MergeAppends
* won't be in the final plan, so just return the child's
* mark/restore ability.
*/
if (list_length(mapath->subpaths) == 1)
return ExecSupportsMarkRestore((Path *) linitial(mapath->subpaths));
/* Otherwise, MergeAppend can't handle it */
return false;
}
default: default:
break; break;
} }
......
...@@ -1044,8 +1044,8 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, ...@@ -1044,8 +1044,8 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
/* /*
* We have to copy the parent's targetlist and quals to the child, * We have to copy the parent's targetlist and quals to the child,
* with appropriate substitution of variables. If any constant false * with appropriate substitution of variables. If any constant false
* or NULL clauses turn up, we can disregard the child right away. * or NULL clauses turn up, we can disregard the child right away. If
* If not, we can apply constraint exclusion with just the * not, we can apply constraint exclusion with just the
* baserestrictinfo quals. * baserestrictinfo quals.
*/ */
if (!apply_child_basequals(root, rel, childrel, childRTE, appinfo)) if (!apply_child_basequals(root, rel, childrel, childRTE, appinfo))
...@@ -1708,6 +1708,38 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, ...@@ -1708,6 +1708,38 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
required_outer, 0, false, required_outer, 0, false,
partitioned_rels, -1)); partitioned_rels, -1));
} }
/*
* When there is only a single child relation, the Append path can inherit
* any ordering available for the child rel's path, so that it's useful to
* consider ordered partial paths. Above we only considered the cheapest
* partial path for each child, but let's also make paths using any
* partial paths that have pathkeys.
*/
if (list_length(live_childrels) == 1)
{
RelOptInfo *childrel = (RelOptInfo *) linitial(live_childrels);
foreach(l, childrel->partial_pathlist)
{
Path *path = (Path *) lfirst(l);
AppendPath *appendpath;
/*
* Skip paths with no pathkeys. Also skip the cheapest partial
* path, since we already used that above.
*/
if (path->pathkeys == NIL ||
path == linitial(childrel->partial_pathlist))
continue;
appendpath = create_append_path(root, rel, NIL, list_make1(path),
NULL, path->parallel_workers,
true,
partitioned_rels, partial_rows);
add_partial_path(rel, (Path *) appendpath);
}
}
} }
/* /*
......
...@@ -1134,10 +1134,10 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path) ...@@ -1134,10 +1134,10 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path)
} }
/* /*
* XXX ideally, if there's just one child, we'd not bother to generate an * And build the Append plan. Note that if there's just one child, the
* Append node but just return the single child. At the moment this does * Append is pretty useless; but we wait till setrefs.c to get rid of it.
* not work because the varno of the child scan plan won't match the * Doing so here doesn't work because the varno of the child scan plan
* parent-rel Vars it'll be asked to emit. * won't match the parent-rel Vars it'll be asked to emit.
*/ */
plan = make_append(subplans, best_path->first_partial_path, plan = make_append(subplans, best_path->first_partial_path,
......
...@@ -94,12 +94,19 @@ static Plan *set_subqueryscan_references(PlannerInfo *root, ...@@ -94,12 +94,19 @@ static Plan *set_subqueryscan_references(PlannerInfo *root,
SubqueryScan *plan, SubqueryScan *plan,
int rtoffset); int rtoffset);
static bool trivial_subqueryscan(SubqueryScan *plan); static bool trivial_subqueryscan(SubqueryScan *plan);
static Plan *clean_up_removed_plan_level(Plan *parent, Plan *child);
static void set_foreignscan_references(PlannerInfo *root, static void set_foreignscan_references(PlannerInfo *root,
ForeignScan *fscan, ForeignScan *fscan,
int rtoffset); int rtoffset);
static void set_customscan_references(PlannerInfo *root, static void set_customscan_references(PlannerInfo *root,
CustomScan *cscan, CustomScan *cscan,
int rtoffset); int rtoffset);
static Plan *set_append_references(PlannerInfo *root,
Append *aplan,
int rtoffset);
static Plan *set_mergeappend_references(PlannerInfo *root,
MergeAppend *mplan,
int rtoffset);
static Node *fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset); static Node *fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset);
static Node *fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context); static Node *fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context);
static bool fix_scan_expr_walker(Node *node, fix_scan_expr_context *context); static bool fix_scan_expr_walker(Node *node, fix_scan_expr_context *context);
...@@ -181,19 +188,22 @@ static List *set_returning_clause_references(PlannerInfo *root, ...@@ -181,19 +188,22 @@ static List *set_returning_clause_references(PlannerInfo *root,
* 8. We assign every plan node in the tree a unique ID. * 8. We assign every plan node in the tree a unique ID.
* *
* We also perform one final optimization step, which is to delete * We also perform one final optimization step, which is to delete
* SubqueryScan plan nodes that aren't doing anything useful (ie, have * SubqueryScan, Append, and MergeAppend plan nodes that aren't doing
* no qual and a no-op targetlist). The reason for doing this last is that * anything useful. The reason for doing this last is that
* it can't readily be done before set_plan_references, because it would * it can't readily be done before set_plan_references, because it would
* break set_upper_references: the Vars in the subquery's top tlist * break set_upper_references: the Vars in the child plan's top tlist
* wouldn't match up with the Vars in the outer plan tree. The SubqueryScan * wouldn't match up with the Vars in the outer plan tree. A SubqueryScan
* serves a necessary function as a buffer between outer query and subquery * serves a necessary function as a buffer between outer query and subquery
* variable numbering ... but after we've flattened the rangetable this is * variable numbering ... but after we've flattened the rangetable this is
* no longer a problem, since then there's only one rtindex namespace. * no longer a problem, since then there's only one rtindex namespace.
* Likewise, Append and MergeAppend buffer between the parent and child vars
* of an appendrel, but we don't need to worry about that once we've done
* set_plan_references.
* *
* set_plan_references recursively traverses the whole plan tree. * set_plan_references recursively traverses the whole plan tree.
* *
* The return value is normally the same Plan node passed in, but can be * The return value is normally the same Plan node passed in, but can be
* different when the passed-in Plan is a SubqueryScan we decide isn't needed. * different when the passed-in Plan is a node we decide isn't needed.
* *
* The flattened rangetable entries are appended to root->glob->finalrtable. * The flattened rangetable entries are appended to root->glob->finalrtable.
* Also, rowmarks entries are appended to root->glob->finalrowmarks, and the * Also, rowmarks entries are appended to root->glob->finalrowmarks, and the
...@@ -897,71 +907,15 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) ...@@ -897,71 +907,15 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
} }
break; break;
case T_Append: case T_Append:
{ /* Needs special treatment, see comments below */
Append *splan = (Append *) plan; return set_append_references(root,
(Append *) plan,
/* rtoffset);
* Append, like Sort et al, doesn't actually evaluate its
* targetlist or check quals.
*/
set_dummy_tlist_references(plan, rtoffset);
Assert(splan->plan.qual == NIL);
foreach(l, splan->appendplans)
{
lfirst(l) = set_plan_refs(root,
(Plan *) lfirst(l),
rtoffset);
}
if (splan->part_prune_info)
{
foreach(l, splan->part_prune_info->prune_infos)
{
List *prune_infos = lfirst(l);
ListCell *l2;
foreach(l2, prune_infos)
{
PartitionedRelPruneInfo *pinfo = lfirst(l2);
pinfo->rtindex += rtoffset;
}
}
}
}
break;
case T_MergeAppend: case T_MergeAppend:
{ /* Needs special treatment, see comments below */
MergeAppend *splan = (MergeAppend *) plan; return set_mergeappend_references(root,
(MergeAppend *) plan,
/*
* MergeAppend, like Sort et al, doesn't actually evaluate its
* targetlist or check quals.
*/
set_dummy_tlist_references(plan, rtoffset);
Assert(splan->plan.qual == NIL);
foreach(l, splan->mergeplans)
{
lfirst(l) = set_plan_refs(root,
(Plan *) lfirst(l),
rtoffset); rtoffset);
}
if (splan->part_prune_info)
{
foreach(l, splan->part_prune_info->prune_infos)
{
List *prune_infos = lfirst(l);
ListCell *l2;
foreach(l2, prune_infos)
{
PartitionedRelPruneInfo *pinfo = lfirst(l2);
pinfo->rtindex += rtoffset;
}
}
}
}
break;
case T_RecursiveUnion: case T_RecursiveUnion:
/* This doesn't evaluate targetlist or check quals either */ /* This doesn't evaluate targetlist or check quals either */
set_dummy_tlist_references(plan, rtoffset); set_dummy_tlist_references(plan, rtoffset);
...@@ -1086,30 +1040,7 @@ set_subqueryscan_references(PlannerInfo *root, ...@@ -1086,30 +1040,7 @@ set_subqueryscan_references(PlannerInfo *root,
/* /*
* We can omit the SubqueryScan node and just pull up the subplan. * We can omit the SubqueryScan node and just pull up the subplan.
*/ */
ListCell *lp, result = clean_up_removed_plan_level((Plan *) plan, plan->subplan);
*lc;
result = plan->subplan;
/* We have to be sure we don't lose any initplans */
result->initPlan = list_concat(plan->scan.plan.initPlan,
result->initPlan);
/*
* We also have to transfer the SubqueryScan's result-column names
* into the subplan, else columns sent to client will be improperly
* labeled if this is the topmost plan level. Copy the "source
* column" information too.
*/
forboth(lp, plan->scan.plan.targetlist, lc, result->targetlist)
{
TargetEntry *ptle = (TargetEntry *) lfirst(lp);
TargetEntry *ctle = (TargetEntry *) lfirst(lc);
ctle->resname = ptle->resname;
ctle->resorigtbl = ptle->resorigtbl;
ctle->resorigcol = ptle->resorigcol;
}
} }
else else
{ {
...@@ -1190,6 +1121,30 @@ trivial_subqueryscan(SubqueryScan *plan) ...@@ -1190,6 +1121,30 @@ trivial_subqueryscan(SubqueryScan *plan)
return true; return true;
} }
/*
* clean_up_removed_plan_level
* Do necessary cleanup when we strip out a SubqueryScan, Append, etc
*
* We are dropping the "parent" plan in favor of returning just its "child".
* A few small tweaks are needed.
*/
static Plan *
clean_up_removed_plan_level(Plan *parent, Plan *child)
{
/* We have to be sure we don't lose any initplans */
child->initPlan = list_concat(parent->initPlan,
child->initPlan);
/*
* We also have to transfer the parent's column labeling info into the
* child, else columns sent to client will be improperly labeled if this
* is the topmost plan level. resjunk and so on may be important too.
*/
apply_tlist_labeling(child->targetlist, parent->targetlist);
return child;
}
/* /*
* set_foreignscan_references * set_foreignscan_references
* Do set_plan_references processing on a ForeignScan * Do set_plan_references processing on a ForeignScan
...@@ -1340,6 +1295,131 @@ set_customscan_references(PlannerInfo *root, ...@@ -1340,6 +1295,131 @@ set_customscan_references(PlannerInfo *root,
} }
} }
/*
* set_append_references
* Do set_plan_references processing on an Append
*
* We try to strip out the Append entirely; if we can't, we have
* to do the normal processing on it.
*/
static Plan *
set_append_references(PlannerInfo *root,
Append *aplan,
int rtoffset)
{
ListCell *l;
/*
* Append, like Sort et al, doesn't actually evaluate its targetlist or
* check quals. If it's got exactly one child plan, then it's not doing
* anything useful at all, and we can strip it out.
*/
Assert(aplan->plan.qual == NIL);
/* First, we gotta recurse on the children */
foreach(l, aplan->appendplans)
{
lfirst(l) = set_plan_refs(root, (Plan *) lfirst(l), rtoffset);
}
/* Now, if there's just one, forget the Append and return that child */
if (list_length(aplan->appendplans) == 1)
return clean_up_removed_plan_level((Plan *) aplan,
(Plan *) linitial(aplan->appendplans));
/*
* Otherwise, clean up the Append as needed. It's okay to do this after
* recursing to the children, because set_dummy_tlist_references doesn't
* look at those.
*/
set_dummy_tlist_references((Plan *) aplan, rtoffset);
if (aplan->part_prune_info)
{
foreach(l, aplan->part_prune_info->prune_infos)
{
List *prune_infos = lfirst(l);
ListCell *l2;
foreach(l2, prune_infos)
{
PartitionedRelPruneInfo *pinfo = lfirst(l2);
pinfo->rtindex += rtoffset;
}
}
}
/* We don't need to recurse to lefttree or righttree ... */
Assert(aplan->plan.lefttree == NULL);
Assert(aplan->plan.righttree == NULL);
return (Plan *) aplan;
}
/*
* set_mergeappend_references
* Do set_plan_references processing on a MergeAppend
*
* We try to strip out the MergeAppend entirely; if we can't, we have
* to do the normal processing on it.
*/
static Plan *
set_mergeappend_references(PlannerInfo *root,
MergeAppend *mplan,
int rtoffset)
{
ListCell *l;
/*
* MergeAppend, like Sort et al, doesn't actually evaluate its targetlist
* or check quals. If it's got exactly one child plan, then it's not
* doing anything useful at all, and we can strip it out.
*/
Assert(mplan->plan.qual == NIL);
/* First, we gotta recurse on the children */
foreach(l, mplan->mergeplans)
{
lfirst(l) = set_plan_refs(root, (Plan *) lfirst(l), rtoffset);
}
/* Now, if there's just one, forget the MergeAppend and return that child */
if (list_length(mplan->mergeplans) == 1)
return clean_up_removed_plan_level((Plan *) mplan,
(Plan *) linitial(mplan->mergeplans));
/*
* Otherwise, clean up the MergeAppend as needed. It's okay to do this
* after recursing to the children, because set_dummy_tlist_references
* doesn't look at those.
*/
set_dummy_tlist_references((Plan *) mplan, rtoffset);
if (mplan->part_prune_info)
{
foreach(l, mplan->part_prune_info->prune_infos)
{
List *prune_infos = lfirst(l);
ListCell *l2;
foreach(l2, prune_infos)
{
PartitionedRelPruneInfo *pinfo = lfirst(l2);
pinfo->rtindex += rtoffset;
}
}
}
/* We don't need to recurse to lefttree or righttree ... */
Assert(mplan->plan.lefttree == NULL);
Assert(mplan->plan.righttree == NULL);
return (Plan *) mplan;
}
/* /*
* copyVar * copyVar
* Copy a Var node. * Copy a Var node.
......
...@@ -1242,7 +1242,6 @@ create_append_path(PlannerInfo *root, ...@@ -1242,7 +1242,6 @@ create_append_path(PlannerInfo *root,
pathnode->path.parallel_aware = parallel_aware; pathnode->path.parallel_aware = parallel_aware;
pathnode->path.parallel_safe = rel->consider_parallel; pathnode->path.parallel_safe = rel->consider_parallel;
pathnode->path.parallel_workers = parallel_workers; pathnode->path.parallel_workers = parallel_workers;
pathnode->path.pathkeys = NIL; /* result is always considered unsorted */
pathnode->partitioned_rels = list_copy(partitioned_rels); pathnode->partitioned_rels = list_copy(partitioned_rels);
/* /*
...@@ -1276,7 +1275,26 @@ create_append_path(PlannerInfo *root, ...@@ -1276,7 +1275,26 @@ create_append_path(PlannerInfo *root,
Assert(!parallel_aware || pathnode->path.parallel_safe); Assert(!parallel_aware || pathnode->path.parallel_safe);
cost_append(pathnode); /*
* If there's exactly one child path, the Append is a no-op and will be
* discarded later (in setrefs.c); therefore, we can inherit the child's
* size, cost, and pathkeys if any. Otherwise, it's unsorted, and we must
* do the normal costsize calculation.
*/
if (list_length(pathnode->subpaths) == 1)
{
Path *child = (Path *) linitial(pathnode->subpaths);
pathnode->path.rows = child->rows;
pathnode->path.startup_cost = child->startup_cost;
pathnode->path.total_cost = child->total_cost;
pathnode->path.pathkeys = child->pathkeys;
}
else
{
pathnode->path.pathkeys = NIL; /* unsorted if more than 1 subpath */
cost_append(pathnode);
}
/* If the caller provided a row estimate, override the computed value. */ /* If the caller provided a row estimate, override the computed value. */
if (rows >= 0) if (rows >= 0)
...@@ -1408,11 +1426,21 @@ create_merge_append_path(PlannerInfo *root, ...@@ -1408,11 +1426,21 @@ create_merge_append_path(PlannerInfo *root,
Assert(bms_equal(PATH_REQ_OUTER(subpath), required_outer)); Assert(bms_equal(PATH_REQ_OUTER(subpath), required_outer));
} }
/* Now we can compute total costs of the MergeAppend */ /*
cost_merge_append(&pathnode->path, root, * Now we can compute total costs of the MergeAppend. If there's exactly
pathkeys, list_length(subpaths), * one child path, the MergeAppend is a no-op and will be discarded later
input_startup_cost, input_total_cost, * (in setrefs.c); otherwise we do the normal cost calculation.
pathnode->path.rows); */
if (list_length(subpaths) == 1)
{
pathnode->path.startup_cost = input_startup_cost;
pathnode->path.total_cost = input_total_cost;
}
else
cost_merge_append(&pathnode->path, root,
pathkeys, list_length(subpaths),
input_startup_cost, input_total_cost,
pathnode->path.rows);
return pathnode; return pathnode;
} }
......
...@@ -1770,12 +1770,11 @@ explain (costs off) select * from list_parted; ...@@ -1770,12 +1770,11 @@ explain (costs off) select * from list_parted;
(4 rows) (4 rows)
explain (costs off) select * from list_parted where a is null; explain (costs off) select * from list_parted where a is null;
QUERY PLAN QUERY PLAN
-------------------------------- --------------------------
Append Seq Scan on part_null_xy
-> Seq Scan on part_null_xy Filter: (a IS NULL)
Filter: (a IS NULL) (2 rows)
(3 rows)
explain (costs off) select * from list_parted where a is not null; explain (costs off) select * from list_parted where a is not null;
QUERY PLAN QUERY PLAN
...@@ -1800,20 +1799,18 @@ explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef'); ...@@ -1800,20 +1799,18 @@ explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
(5 rows) (5 rows)
explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd'); explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
QUERY PLAN QUERY PLAN
--------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------
Append Seq Scan on part_ab_cd
-> Seq Scan on part_ab_cd Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[]))) (2 rows)
(3 rows)
explain (costs off) select * from list_parted where a = 'ab'; explain (costs off) select * from list_parted where a = 'ab';
QUERY PLAN QUERY PLAN
------------------------------------------ ------------------------------------
Append Seq Scan on part_ab_cd
-> Seq Scan on part_ab_cd Filter: ((a)::text = 'ab'::text)
Filter: ((a)::text = 'ab'::text) (2 rows)
(3 rows)
create table range_list_parted ( create table range_list_parted (
a int, a int,
...@@ -1893,12 +1890,11 @@ explain (costs off) select * from range_list_parted where a is null; ...@@ -1893,12 +1890,11 @@ explain (costs off) select * from range_list_parted where a is null;
/* Should only select rows from the null-accepting partition */ /* Should only select rows from the null-accepting partition */
explain (costs off) select * from range_list_parted where b is null; explain (costs off) select * from range_list_parted where b is null;
QUERY PLAN QUERY PLAN
------------------------------------ ------------------------------
Append Seq Scan on part_40_inf_null
-> Seq Scan on part_40_inf_null Filter: (b IS NULL)
Filter: (b IS NULL) (2 rows)
(3 rows)
explain (costs off) select * from range_list_parted where a is not null and a < 67; explain (costs off) select * from range_list_parted where a is not null and a < 67;
QUERY PLAN QUERY PLAN
...@@ -2021,12 +2017,11 @@ explain (costs off) select * from mcrparted where a > -1; -- scans all partition ...@@ -2021,12 +2017,11 @@ explain (costs off) select * from mcrparted where a > -1; -- scans all partition
(15 rows) (15 rows)
explain (costs off) select * from mcrparted where a = 20 and abs(b) = 10 and c > 10; -- scans mcrparted4 explain (costs off) select * from mcrparted where a = 20 and abs(b) = 10 and c > 10; -- scans mcrparted4
QUERY PLAN QUERY PLAN
----------------------------------------------------------- -----------------------------------------------------
Append Seq Scan on mcrparted4
-> Seq Scan on mcrparted4 Filter: ((c > 10) AND (a = 20) AND (abs(b) = 10))
Filter: ((c > 10) AND (a = 20) AND (abs(b) = 10)) (2 rows)
(3 rows)
explain (costs off) select * from mcrparted where a = 20 and c > 20; -- scans mcrparted3, mcrparte4, mcrparte5, mcrparted_def explain (costs off) select * from mcrparted where a = 20 and c > 20; -- scans mcrparted3, mcrparte4, mcrparte5, mcrparted_def
QUERY PLAN QUERY PLAN
...@@ -2050,22 +2045,18 @@ create table parted_minmax1 partition of parted_minmax for values from (1) to (1 ...@@ -2050,22 +2045,18 @@ create table parted_minmax1 partition of parted_minmax for values from (1) to (1
create index parted_minmax1i on parted_minmax1 (a, b); create index parted_minmax1i on parted_minmax1 (a, b);
insert into parted_minmax values (1,'12345'); insert into parted_minmax values (1,'12345');
explain (costs off) select min(a), max(a) from parted_minmax where b = '12345'; explain (costs off) select min(a), max(a) from parted_minmax where b = '12345';
QUERY PLAN QUERY PLAN
------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------
Result Result
InitPlan 1 (returns $0) InitPlan 1 (returns $0)
-> Limit -> Limit
-> Merge Append -> Index Only Scan using parted_minmax1i on parted_minmax1
Sort Key: parted_minmax1.a 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) InitPlan 2 (returns $1)
-> Limit -> Limit
-> Merge Append -> Index Only Scan Backward using parted_minmax1i on parted_minmax1 parted_minmax1_1
Sort Key: parted_minmax1_1.a DESC Index Cond: ((a IS NOT NULL) AND (b = '12345'::text))
-> Index Only Scan Backward using parted_minmax1i on parted_minmax1 parted_minmax1_1 (9 rows)
Index Cond: ((a IS NOT NULL) AND (b = '12345'::text))
(13 rows)
select min(a), max(a) from parted_minmax where b = '12345'; select min(a), max(a) from parted_minmax where b = '12345';
min | max min | max
......
...@@ -186,19 +186,18 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT 50 phv, * FROM prt1 WHERE prt1.b = 0) ...@@ -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 -- Join with pruned partitions from joining relations
EXPLAIN (COSTS OFF) 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; 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
Sort Key: t1.a Sort Key: t1.a
-> Append -> Hash Join
-> Hash Join Hash Cond: (t2.b = t1.a)
Hash Cond: (t2.b = t1.a) -> Seq Scan on prt2_p2 t2
-> Seq Scan on prt2_p2 t2 Filter: (b > 250)
Filter: (b > 250) -> Hash
-> Hash -> Seq Scan on prt1_p2 t1
-> Seq Scan on prt1_p2 t1 Filter: ((a < 450) AND (b = 0))
Filter: ((a < 450) AND (b = 0)) (9 rows)
(10 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; 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 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 ...@@ -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_p1 t2_3
-> Seq Scan on prt2_l_p3_p2 t2_4 -> Seq Scan on prt2_l_p3_p2 t2_4
-> Hash -> Hash
-> Append -> Seq Scan on prt1_l_p3_p1 t1_3
-> Seq Scan on prt1_l_p3_p1 t1_3 Filter: (b = 0)
Filter: (b = 0) (28 rows)
(29 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; 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 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 ...@@ -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_p1 t2_3
-> Seq Scan on prt2_l_p3_p2 t2_4 -> Seq Scan on prt2_l_p3_p2 t2_4
-> Hash -> Hash
-> Append -> Seq Scan on prt1_l_p3_p1 t1_3
-> Seq Scan on prt1_l_p3_p1 t1_3 Filter: (b = 0)
Filter: (b = 0) (29 rows)
(30 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; 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 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 ...@@ -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_p1 t1_3
-> Seq Scan on prt1_l_p3_p2 t1_4 -> Seq Scan on prt1_l_p3_p2 t1_4
-> Hash -> Hash
-> Append -> Seq Scan on prt2_l_p3_p1 t2_3
-> Seq Scan on prt2_l_p3_p1 t2_3 Filter: (a = 0)
Filter: (a = 0) (29 rows)
(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; 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 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 ...@@ -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) Filter: (a = 0)
-> Hash Full Join -> 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)) 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
-> Seq Scan on prt1_l_p3_p1 Filter: (b = 0)
Filter: (b = 0)
-> Hash -> Hash
-> Append -> Seq Scan on prt2_l_p3_p1
-> Seq Scan on prt2_l_p3_p1 Filter: (a = 0)
Filter: (a = 0) (31 rows)
(33 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; 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 a | c | b | c
...@@ -1697,9 +1691,8 @@ SELECT * FROM prt1_l t1 LEFT JOIN LATERAL ...@@ -1697,9 +1691,8 @@ SELECT * FROM prt1_l t1 LEFT JOIN LATERAL
-> Seq Scan on prt1_l_p2_p2 t2_2 -> Seq Scan on prt1_l_p2_p2 t2_2
Filter: ((t1_2.a = a) AND ((t1_2.c)::text = (c)::text)) Filter: ((t1_2.a = a) AND ((t1_2.c)::text = (c)::text))
-> Nested Loop Left Join -> Nested Loop Left Join
-> Append -> Seq Scan on prt1_l_p3_p1 t1_3
-> Seq Scan on prt1_l_p3_p1 t1_3 Filter: (b = 0)
Filter: (b = 0)
-> Hash Join -> Hash Join
Hash Cond: ((t3_3.b = t2_3.a) AND ((t3_3.c)::text = (t2_3.c)::text)) Hash Cond: ((t3_3.b = t2_3.a) AND ((t3_3.c)::text = (t2_3.c)::text))
-> Append -> Append
...@@ -1711,7 +1704,7 @@ SELECT * FROM prt1_l t1 LEFT JOIN LATERAL ...@@ -1711,7 +1704,7 @@ SELECT * FROM prt1_l t1 LEFT JOIN LATERAL
Filter: ((t1_3.a = a) AND ((t1_3.c)::text = (c)::text)) Filter: ((t1_3.a = a) AND ((t1_3.c)::text = (c)::text))
-> Seq Scan on prt1_l_p3_p2 t2_4 -> Seq Scan on prt1_l_p3_p2 t2_4
Filter: ((t1_3.a = a) AND ((t1_3.c)::text = (c)::text)) 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 * 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 (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
......
...@@ -1057,15 +1057,14 @@ NOTICE: f_leak => awesome science fiction ...@@ -1057,15 +1057,14 @@ NOTICE: f_leak => awesome science fiction
(4 rows) (4 rows)
EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle); EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle);
QUERY PLAN QUERY PLAN
-------------------------------------------------------------------- --------------------------------------------------------------
Append Seq Scan on part_document_fiction
Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))
InitPlan 1 (returns $0) InitPlan 1 (returns $0)
-> Index Scan using uaccount_pkey on uaccount -> Index Scan using uaccount_pkey on uaccount
Index Cond: (pguser = CURRENT_USER) Index Cond: (pguser = CURRENT_USER)
-> Seq Scan on part_document_fiction (5 rows)
Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))
(6 rows)
-- pp1 ERROR -- pp1 ERROR
INSERT INTO part_document VALUES (100, 11, 5, 'regress_rls_dave', 'testing pp1'); -- fail INSERT INTO part_document VALUES (100, 11, 5, 'regress_rls_dave', 'testing pp1'); -- fail
...@@ -1136,15 +1135,14 @@ NOTICE: f_leak => awesome science fiction ...@@ -1136,15 +1135,14 @@ NOTICE: f_leak => awesome science fiction
(4 rows) (4 rows)
EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle); EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle);
QUERY PLAN QUERY PLAN
-------------------------------------------------------------------- --------------------------------------------------------------
Append Seq Scan on part_document_fiction
Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))
InitPlan 1 (returns $0) InitPlan 1 (returns $0)
-> Index Scan using uaccount_pkey on uaccount -> Index Scan using uaccount_pkey on uaccount
Index Cond: (pguser = CURRENT_USER) Index Cond: (pguser = CURRENT_USER)
-> Seq Scan on part_document_fiction (5 rows)
Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))
(6 rows)
-- viewpoint from regress_rls_carol -- viewpoint from regress_rls_carol
SET SESSION AUTHORIZATION regress_rls_carol; SET SESSION AUTHORIZATION regress_rls_carol;
......
...@@ -812,11 +812,10 @@ explain (costs off) ...@@ -812,11 +812,10 @@ explain (costs off)
UNION ALL UNION ALL
SELECT 2 AS t, * FROM tenk1 b) c SELECT 2 AS t, * FROM tenk1 b) c
WHERE t = 2; WHERE t = 2;
QUERY PLAN QUERY PLAN
--------------------------- ---------------------
Append Seq Scan on tenk1 b
-> Seq Scan on tenk1 b (1 row)
(2 rows)
-- Test that we push quals into UNION sub-selects only when it's safe -- Test that we push quals into UNION sub-selects only when it's safe
explain (costs off) explain (costs off)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment