aboutsummaryrefslogtreecommitdiff
path: root/src/backend/optimizer/plan/planner.c
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2019-03-07 14:21:52 -0500
committerTom Lane <tgl@sss.pgh.pa.us>2019-03-07 14:21:52 -0500
commit925f46ffb82f0b25c94e7997caff732eaf14367d (patch)
tree3842ea3cc5a366c2c5229d7a6c49b4fcaf45319d /src/backend/optimizer/plan/planner.c
parentdadf9814d0dddaa3e5a301e2b13479431108bc5b (diff)
downloadpostgresql-925f46ffb82f0b25c94e7997caff732eaf14367d.tar.gz
postgresql-925f46ffb82f0b25c94e7997caff732eaf14367d.zip
Fix handling of targetlist SRFs when scan/join relation is known empty.
When we introduced separate ProjectSetPath nodes for application of set-returning functions in v10, we inadvertently broke some cases where we're supposed to recognize that the result of a subquery is known to be empty (contain zero rows). That's because IS_DUMMY_REL was just looking for a childless AppendPath without allowing for a ProjectSetPath being possibly stuck on top. In itself, this didn't do anything much worse than produce slightly worse plans for some corner cases. Then in v11, commit 11cf92f6e rearranged things to allow the scan/join targetlist to be applied directly to partial paths before they get gathered. But it inserted a short-circuit path for dummy relations that was a little too short: it failed to insert a ProjectSetPath node at all for a targetlist containing set-returning functions, resulting in bogus "set-valued function called in context that cannot accept a set" errors, as reported in bug #15669 from Madelaine Thibaut. The best way to fix this mess seems to be to reimplement IS_DUMMY_REL so that it drills down through any ProjectSetPath nodes that might be there (and it seems like we'd better allow for ProjectionPath as well). While we're at it, make it look at rel->pathlist not cheapest_total_path, so that it gives the right answer independently of whether set_cheapest has been done lately. That dependency looks pretty shaky in the context of code like apply_scanjoin_target_to_paths, and even if it's not broken today it'd certainly bite us at some point. (Nastily, unsafe use of the old coding would almost always work; the hazard comes down to possibly looking through a dangling pointer, and only once in a blue moon would you find something there that resulted in the wrong answer.) It now looks like it was a mistake for IS_DUMMY_REL to be a macro: if there are any extensions using it, they'll continue to use the old inadequate logic until they're recompiled, after which they'll fail to load into server versions predating this fix. Hopefully there are few such extensions. Having fixed IS_DUMMY_REL, the special path for dummy rels in apply_scanjoin_target_to_paths is unnecessary as well as being wrong, so we can just drop it. Also change a few places that were testing for partitioned-ness of a planner relation but not using IS_PARTITIONED_REL for the purpose; that seems unsafe as well as inconsistent, plus it required an ugly hack in apply_scanjoin_target_to_paths. In passing, save a few cycles in apply_scanjoin_target_to_paths by skipping processing of pre-existing paths for partitioned rels, and do some cosmetic cleanup and comment adjustment in that function. I renamed IS_DUMMY_PATH to IS_DUMMY_APPEND with the intention of breaking any code that might be using it, since in almost every case that would be wrong; IS_DUMMY_REL is what to be using instead. In HEAD, also make set_dummy_rel_pathlist static (since it's no longer used from outside allpaths.c), and delete is_dummy_plan, since it's no longer used anywhere. Back-patch as appropriate into v11 and v10. Tom Lane and Julien Rouhaud Discussion: https://postgr.es/m/15669-02fb3296cca26203@postgresql.org
Diffstat (limited to 'src/backend/optimizer/plan/planner.c')
-rw-r--r--src/backend/optimizer/plan/planner.c148
1 files changed, 74 insertions, 74 deletions
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index bbf355dbf2c..680085096ae 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -1495,7 +1495,7 @@ inheritance_planner(PlannerInfo *root)
* If this child rel was excluded by constraint exclusion, exclude it
* from the result plan.
*/
- if (IS_DUMMY_PATH(subpath))
+ if (IS_DUMMY_REL(sub_final_rel))
continue;
/*
@@ -3987,12 +3987,10 @@ create_ordinary_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel,
* If this is the topmost grouping relation or if the parent relation is
* doing some form of partitionwise aggregation, then we may be able to do
* it at this level also. However, if the input relation is not
- * partitioned, partitionwise aggregate is impossible, and if it is dummy,
- * partitionwise aggregate is pointless.
+ * partitioned, partitionwise aggregate is impossible.
*/
if (extra->patype != PARTITIONWISE_AGGREGATE_NONE &&
- input_rel->part_scheme && input_rel->part_rels &&
- !IS_DUMMY_REL(input_rel))
+ IS_PARTITIONED_REL(input_rel))
{
/*
* If this is the topmost relation or if the parent relation is doing
@@ -6817,27 +6815,48 @@ apply_scanjoin_target_to_paths(PlannerInfo *root,
bool scanjoin_target_parallel_safe,
bool tlist_same_exprs)
{
- ListCell *lc;
+ bool rel_is_partitioned = IS_PARTITIONED_REL(rel);
PathTarget *scanjoin_target;
- bool is_dummy_rel = IS_DUMMY_REL(rel);
+ ListCell *lc;
+ /* This recurses, so be paranoid. */
check_stack_depth();
/*
+ * If the rel is partitioned, we want to drop its existing paths and
+ * generate new ones. This function would still be correct if we kept the
+ * existing paths: we'd modify them to generate the correct target above
+ * the partitioning Append, and then they'd compete on cost with paths
+ * generating the target below the Append. However, in our current cost
+ * model the latter way is always the same or cheaper cost, so modifying
+ * the existing paths would just be useless work. Moreover, when the cost
+ * is the same, varying roundoff errors might sometimes allow an existing
+ * path to be picked, resulting in undesirable cross-platform plan
+ * variations. So we drop old paths and thereby force the work to be done
+ * below the Append, except in the case of a non-parallel-safe target.
+ *
+ * Some care is needed, because we have to allow generate_gather_paths to
+ * see the old partial paths in the next stanza. Hence, zap the main
+ * pathlist here, then allow generate_gather_paths to add path(s) to the
+ * main list, and finally zap the partial pathlist.
+ */
+ if (rel_is_partitioned)
+ rel->pathlist = NIL;
+
+ /*
* If the scan/join target is not parallel-safe, partial paths cannot
* generate it.
*/
if (!scanjoin_target_parallel_safe)
{
/*
- * Since we can't generate the final scan/join target, this is our
- * last opportunity to use any partial paths that exist. We don't do
- * this if the case where the target is parallel-safe, since we will
- * be able to generate superior paths by doing it after the final
- * scan/join target has been applied.
- *
- * Note that this may invalidate rel->cheapest_total_path, so we must
- * not rely on it after this point without first calling set_cheapest.
+ * Since we can't generate the final scan/join target in parallel
+ * workers, this is our last opportunity to use any partial paths that
+ * exist; so build Gather path(s) that use them and emit whatever the
+ * current reltarget is. We don't do this in the case where the
+ * target is parallel-safe, since we will be able to generate superior
+ * paths by doing it after the final scan/join target has been
+ * applied.
*/
generate_gather_paths(root, rel, false);
@@ -6846,61 +6865,27 @@ apply_scanjoin_target_to_paths(PlannerInfo *root,
rel->consider_parallel = false;
}
- /*
- * Update the reltarget. This may not be strictly necessary in all cases,
- * but it is at least necessary when create_append_path() gets called
- * below directly or indirectly, since that function uses the reltarget as
- * the pathtarget for the resulting path. It seems like a good idea to do
- * it unconditionally.
- */
- rel->reltarget = llast_node(PathTarget, scanjoin_targets);
-
- /* Special case: handle dummy relations separately. */
- if (is_dummy_rel)
- {
- /*
- * Since this is a dummy rel, it's got a single Append path with no
- * child paths. Replace it with a new path having the final scan/join
- * target. (Note that since Append is not projection-capable, it
- * would be bad to handle this using the general purpose code below;
- * we'd end up putting a ProjectionPath on top of the existing Append
- * node, which would cause this relation to stop appearing to be a
- * dummy rel.)
- */
- rel->pathlist = list_make1(create_append_path(root, rel, NIL, NIL,
- NULL, 0, false, NIL,
- -1));
+ /* Finish dropping old paths for a partitioned rel, per comment above */
+ if (rel_is_partitioned)
rel->partial_pathlist = NIL;
- set_cheapest(rel);
- Assert(IS_DUMMY_REL(rel));
-
- /*
- * Forget about any child relations. There's no point in adjusting
- * them and no point in using them for later planning stages (in
- * particular, partitionwise aggregate).
- */
- rel->nparts = 0;
- rel->part_rels = NULL;
- rel->boundinfo = NULL;
-
- return;
- }
/* Extract SRF-free scan/join target. */
scanjoin_target = linitial_node(PathTarget, scanjoin_targets);
/*
- * Adjust each input path. If the tlist exprs are the same, we can just
- * inject the sortgroupref information into the existing pathtarget.
- * Otherwise, replace each path with a projection path that generates the
- * SRF-free scan/join target. This can't change the ordering of paths
- * within rel->pathlist, so we just modify the list in place.
+ * Apply the SRF-free scan/join target to each existing path.
+ *
+ * If the tlist exprs are the same, we can just inject the sortgroupref
+ * information into the existing pathtargets. Otherwise, replace each
+ * path with a projection path that generates the SRF-free scan/join
+ * target. This can't change the ordering of paths within rel->pathlist,
+ * so we just modify the list in place.
*/
foreach(lc, rel->pathlist)
{
Path *subpath = (Path *) lfirst(lc);
- Path *newpath;
+ /* Shouldn't have any parameterized paths anymore */
Assert(subpath->param_info == NULL);
if (tlist_same_exprs)
@@ -6908,17 +6893,18 @@ apply_scanjoin_target_to_paths(PlannerInfo *root,
scanjoin_target->sortgrouprefs;
else
{
+ Path *newpath;
+
newpath = (Path *) create_projection_path(root, rel, subpath,
scanjoin_target);
lfirst(lc) = newpath;
}
}
- /* Same for partial paths. */
+ /* Likewise adjust the targets for any partial paths. */
foreach(lc, rel->partial_pathlist)
{
Path *subpath = (Path *) lfirst(lc);
- Path *newpath;
/* Shouldn't have any parameterized paths anymore */
Assert(subpath->param_info == NULL);
@@ -6928,39 +6914,54 @@ apply_scanjoin_target_to_paths(PlannerInfo *root,
scanjoin_target->sortgrouprefs;
else
{
- newpath = (Path *) create_projection_path(root,
- rel,
- subpath,
+ Path *newpath;
+
+ newpath = (Path *) create_projection_path(root, rel, subpath,
scanjoin_target);
lfirst(lc) = newpath;
}
}
- /* Now fix things up if scan/join target contains SRFs */
+ /*
+ * Now, if final scan/join target contains SRFs, insert ProjectSetPath(s)
+ * atop each existing path. (Note that this function doesn't look at the
+ * cheapest-path fields, which is a good thing because they're bogus right
+ * now.)
+ */
if (root->parse->hasTargetSRFs)
adjust_paths_for_srfs(root, rel,
scanjoin_targets,
scanjoin_targets_contain_srfs);
/*
- * If the relation is partitioned, recursively apply the same changes to
- * all partitions and generate new Append paths. Since Append is not
- * projection-capable, that might save a separate Result node, and it also
- * is important for partitionwise aggregate.
+ * Update the rel's target to be the final (with SRFs) scan/join target.
+ * This now matches the actual output of all the paths, and we might get
+ * confused in createplan.c if they don't agree. We must do this now so
+ * that any append paths made in the next part will use the correct
+ * pathtarget (cf. create_append_path).
*/
- if (rel->part_scheme && rel->part_rels)
+ rel->reltarget = llast_node(PathTarget, scanjoin_targets);
+
+ /*
+ * If the relation is partitioned, recursively apply the scan/join target
+ * to all partitions, and generate brand-new Append paths in which the
+ * scan/join target is computed below the Append rather than above it.
+ * Since Append is not projection-capable, that might save a separate
+ * Result node, and it also is important for partitionwise aggregate.
+ */
+ if (rel_is_partitioned)
{
- int partition_idx;
List *live_children = NIL;
+ int partition_idx;
/* Adjust each partition. */
for (partition_idx = 0; partition_idx < rel->nparts; partition_idx++)
{
RelOptInfo *child_rel = rel->part_rels[partition_idx];
- ListCell *lc;
AppendRelInfo **appinfos;
int nappinfos;
List *child_scanjoin_targets = NIL;
+ ListCell *lc;
/* Translate scan/join targets for this child. */
appinfos = find_appinfos_by_relids(root, child_rel->relids,
@@ -6992,8 +6993,7 @@ apply_scanjoin_target_to_paths(PlannerInfo *root,
}
/* Build new paths for this relation by appending child paths. */
- if (live_children != NIL)
- add_paths_to_append_rel(root, rel, live_children);
+ add_paths_to_append_rel(root, rel, live_children);
}
/*