aboutsummaryrefslogtreecommitdiff
path: root/src/backend/optimizer
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/optimizer')
-rw-r--r--src/backend/optimizer/path/allpaths.c21
-rw-r--r--src/backend/optimizer/path/joinrels.c33
-rw-r--r--src/backend/optimizer/plan/createplan.c11
-rw-r--r--src/backend/optimizer/plan/planner.c148
4 files changed, 121 insertions, 92 deletions
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index f8f4e0b68f3..2091f715d32 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -1607,7 +1607,7 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
* Consider an append of unordered, unparameterized partial paths. Make
* it parallel-aware if possible.
*/
- if (partial_subpaths_valid)
+ if (partial_subpaths_valid && partial_subpaths != NIL)
{
AppendPath *appendpath;
ListCell *lc;
@@ -2004,9 +2004,11 @@ accumulate_append_subpath(Path *path, List **subpaths, List **special_subpaths)
* Build a dummy path for a relation that's been excluded by constraints
*
* Rather than inventing a special "dummy" path type, we represent this as an
- * AppendPath with no members (see also IS_DUMMY_PATH/IS_DUMMY_REL macros).
+ * AppendPath with no members (see also IS_DUMMY_APPEND/IS_DUMMY_REL macros).
*
- * This is exported because inheritance_planner() has need for it.
+ * (See also mark_dummy_rel, which does basically the same thing, but is
+ * typically used to change a rel into dummy state after we already made
+ * paths for it.)
*/
void
set_dummy_rel_pathlist(RelOptInfo *rel)
@@ -2019,14 +2021,15 @@ set_dummy_rel_pathlist(RelOptInfo *rel)
rel->pathlist = NIL;
rel->partial_pathlist = NIL;
+ /* Set up the dummy path */
add_path(rel, (Path *) create_append_path(NULL, rel, NIL, NIL, NULL,
0, false, NIL, -1));
/*
- * We set the cheapest path immediately, to ensure that IS_DUMMY_REL()
- * will recognize the relation as dummy if anyone asks. This is redundant
- * when we're called from set_rel_size(), but not when called from
- * elsewhere, and doing it twice is harmless anyway.
+ * We set the cheapest-path fields immediately, just in case they were
+ * pointing at some discarded path. This is redundant when we're called
+ * from set_rel_size(), but not when called from elsewhere, and doing it
+ * twice is harmless anyway.
*/
set_cheapest(rel);
}
@@ -3552,12 +3555,12 @@ generate_partitionwise_join_paths(PlannerInfo *root, RelOptInfo *rel)
/* Add partitionwise join paths for partitioned child-joins. */
generate_partitionwise_join_paths(root, child_rel);
+ set_cheapest(child_rel);
+
/* Dummy children will not be scanned, so ignore those. */
if (IS_DUMMY_REL(child_rel))
continue;
- set_cheapest(child_rel);
-
#ifdef OPTIMIZER_DEBUG
debug_print_rel(root, child_rel);
#endif
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 6ddf8461257..50fc0c500d0 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -33,7 +33,6 @@ static void make_rels_by_clauseless_joins(PlannerInfo *root,
ListCell *other_rels);
static bool has_join_restriction(PlannerInfo *root, RelOptInfo *rel);
static bool has_legal_joinclause(PlannerInfo *root, RelOptInfo *rel);
-static bool is_dummy_rel(RelOptInfo *rel);
static bool restriction_is_constant_false(List *restrictlist,
RelOptInfo *joinrel,
bool only_pushed_down);
@@ -1192,10 +1191,38 @@ have_dangerous_phv(PlannerInfo *root,
/*
* is_dummy_rel --- has relation been proven empty?
*/
-static bool
+bool
is_dummy_rel(RelOptInfo *rel)
{
- return IS_DUMMY_REL(rel);
+ Path *path;
+
+ /*
+ * A rel that is known dummy will have just one path that is a childless
+ * Append. (Even if somehow it has more paths, a childless Append will
+ * have cost zero and hence should be at the front of the pathlist.)
+ */
+ if (rel->pathlist == NIL)
+ return false;
+ path = (Path *) linitial(rel->pathlist);
+
+ /*
+ * Initially, a dummy path will just be a childless Append. But in later
+ * planning stages we might stick a ProjectSetPath and/or ProjectionPath
+ * on top, since Append can't project. Rather than make assumptions about
+ * which combinations can occur, just descend through whatever we find.
+ */
+ for (;;)
+ {
+ if (IsA(path, ProjectionPath))
+ path = ((ProjectionPath *) path)->subpath;
+ else if (IsA(path, ProjectSetPath))
+ path = ((ProjectSetPath *) path)->subpath;
+ else
+ break;
+ }
+ if (IS_DUMMY_APPEND(path))
+ return true;
+ return false;
}
/*
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index c225e61be96..ae51c0e0b0c 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -1038,7 +1038,7 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path)
*
* Note that an AppendPath with no members is also generated in certain
* cases where there was no appending construct at all, but we know the
- * relation is empty (see set_dummy_rel_pathlist).
+ * relation is empty (see set_dummy_rel_pathlist and mark_dummy_rel).
*/
if (best_path->subpaths == NIL)
{
@@ -6506,12 +6506,11 @@ is_projection_capable_path(Path *path)
case T_Append:
/*
- * 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.
+ * Append can't project, but if an AppendPath is being used to
+ * represent a dummy path, what will actually be generated is a
+ * Result which can project.
*/
- return IS_DUMMY_PATH(path);
+ return IS_DUMMY_APPEND(path);
case T_ProjectSet:
/*
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);
}
/*