put-srfs-in-separate-result-nodes-1.patch
text/x-diff
Filename: put-srfs-in-separate-result-nodes-1.patch
Type: text/x-diff
Part: 0
Patch
Format: unified
| File | + | − |
|---|---|---|
| src/backend/nodes/outfuncs.c | 0 | 0 |
| src/backend/optimizer/plan/createplan.c | 0 | 0 |
| src/backend/optimizer/plan/planner.c | 0 | 0 |
| src/backend/optimizer/util/clauses.c | 0 | 0 |
| src/backend/optimizer/util/pathnode.c | 0 | 0 |
| src/backend/optimizer/util/tlist.c | 0 | 0 |
| src/include/nodes/relation.h | 0 | 0 |
| src/include/optimizer/clauses.h | 0 | 0 |
| src/include/optimizer/pathnode.h | 0 | 0 |
| src/include/optimizer/tlist.h | 0 | 0 |
| src/test/regress/expected/aggregates.out | 0 | 0 |
| src/test/regress/expected/limit.out | 0 | 0 |
| src/test/regress/expected/rangefuncs.out | 0 | 0 |
| src/test/regress/expected/subselect.out | 0 | 0 |
| src/test/regress/expected/tsrf.out | 0 | 0 |
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 7e092d7..9052273 100644
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
*************** _outProjectionPath(StringInfo str, const
*** 1817,1822 ****
--- 1817,1823 ----
WRITE_NODE_FIELD(subpath);
WRITE_BOOL_FIELD(dummypp);
+ WRITE_BOOL_FIELD(srfpp);
}
static void
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 47158f6..7c59c3d 100644
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
*************** create_projection_plan(PlannerInfo *root
*** 1421,1428 ****
Plan *subplan;
List *tlist;
! /* Since we intend to project, we don't need to constrain child tlist */
! subplan = create_plan_recurse(root, best_path->subpath, 0);
tlist = build_path_tlist(root, &best_path->path);
--- 1421,1441 ----
Plan *subplan;
List *tlist;
! /*
! * XXX Possibly-temporary hack: if the subpath is a dummy ResultPath,
! * don't bother with it, just make a Result with no input. This avoids an
! * extra Result plan node when doing "SELECT srf()". Depending on what we
! * decide about the desired plan structure for SRF-expanding nodes, this
! * optimization might have to go away, and in any case it'll probably look
! * a good bit different.
! */
! if (IsA(best_path->subpath, ResultPath) &&
! ((ResultPath *) best_path->subpath)->path.pathtarget->exprs == NIL &&
! ((ResultPath *) best_path->subpath)->quals == NIL)
! subplan = NULL;
! else
! /* Since we intend to project, we don't need to constrain child tlist */
! subplan = create_plan_recurse(root, best_path->subpath, 0);
tlist = build_path_tlist(root, &best_path->path);
*************** create_projection_plan(PlannerInfo *root
*** 1441,1448 ****
* creation, but that would add expense to creating Paths we might end up
* not using.)
*/
! if (is_projection_capable_path(best_path->subpath) ||
! tlist_same_exprs(tlist, subplan->targetlist))
{
/* Don't need a separate Result, just assign tlist to subplan */
plan = subplan;
--- 1454,1462 ----
* creation, but that would add expense to creating Paths we might end up
* not using.)
*/
! if (!best_path->srfpp &&
! (is_projection_capable_path(best_path->subpath) ||
! tlist_same_exprs(tlist, subplan->targetlist)))
{
/* Don't need a separate Result, just assign tlist to subplan */
plan = subplan;
*************** is_projection_capable_path(Path *path)
*** 6185,6190 ****
--- 6199,6215 ----
* projection to its dummy path.
*/
return IS_DUMMY_PATH(path);
+ case T_Result:
+
+ /*
+ * If the path is doing SRF evaluation, claim it can't project, so
+ * we don't jam a new tlist into it and thereby break the property
+ * that the SRFs appear at top level.
+ */
+ if (IsA(path, ProjectionPath) &&
+ ((ProjectionPath *) path)->srfpp)
+ return false;
+ break;
default:
break;
}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index f657ffc..8fff294 100644
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
*************** static List *make_pathkeys_for_window(Pl
*** 153,158 ****
--- 153,160 ----
static PathTarget *make_sort_input_target(PlannerInfo *root,
PathTarget *final_target,
bool *have_postponed_srfs);
+ static void adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
+ List *targets, List *targets_contain_srfs);
/*****************************************************************************
*************** grouping_planner(PlannerInfo *root, bool
*** 1440,1447 ****
int64 count_est = 0;
double limit_tuples = -1.0;
bool have_postponed_srfs = false;
- double tlist_rows;
PathTarget *final_target;
RelOptInfo *current_rel;
RelOptInfo *final_rel;
ListCell *lc;
--- 1442,1450 ----
int64 count_est = 0;
double limit_tuples = -1.0;
bool have_postponed_srfs = false;
PathTarget *final_target;
+ List *final_targets;
+ List *final_targets_contain_srfs;
RelOptInfo *current_rel;
RelOptInfo *final_rel;
ListCell *lc;
*************** grouping_planner(PlannerInfo *root, bool
*** 1504,1509 ****
--- 1507,1516 ----
/* Also extract the PathTarget form of the setop result tlist */
final_target = current_rel->cheapest_total_path->pathtarget;
+ /* The setop result tlist couldn't contain any SRFs */
+ Assert(!parse->hasTargetSRFs);
+ final_targets = final_targets_contain_srfs = NIL;
+
/*
* Can't handle FOR [KEY] UPDATE/SHARE here (parser should have
* checked already, but let's make sure).
*************** grouping_planner(PlannerInfo *root, bool
*** 1529,1536 ****
--- 1536,1549 ----
{
/* No set operations, do regular planning */
PathTarget *sort_input_target;
+ List *sort_input_targets;
+ List *sort_input_targets_contain_srfs;
PathTarget *grouping_target;
+ List *grouping_targets;
+ List *grouping_targets_contain_srfs;
PathTarget *scanjoin_target;
+ List *scanjoin_targets;
+ List *scanjoin_targets_contain_srfs;
bool have_grouping;
AggClauseCosts agg_costs;
WindowFuncLists *wflists = NULL;
*************** grouping_planner(PlannerInfo *root, bool
*** 1781,1788 ****
scanjoin_target = grouping_target;
/*
! * Forcibly apply scan/join target to all the Paths for the scan/join
! * rel.
*
* In principle we should re-run set_cheapest() here to identify the
* cheapest path, but it seems unlikely that adding the same tlist
--- 1794,1843 ----
scanjoin_target = grouping_target;
/*
! * If there are any SRFs in the targetlist, we must separate each of
! * these PathTargets into SRF-computing and SRF-free targets. Replace
! * each of the named targets with a SRF-free version, and remember the
! * list of additional projection steps we need to add afterwards.
! */
! if (parse->hasTargetSRFs)
! {
! /* final_target doesn't recompute any SRFs in sort_input_target */
! split_pathtarget_at_srfs(root, final_target, sort_input_target,
! &final_targets,
! &final_targets_contain_srfs);
! final_target = (PathTarget *) linitial(final_targets);
! Assert(!linitial_int(final_targets_contain_srfs));
! /* likewise for sort_input_target vs. grouping_target */
! split_pathtarget_at_srfs(root, sort_input_target, grouping_target,
! &sort_input_targets,
! &sort_input_targets_contain_srfs);
! sort_input_target = (PathTarget *) linitial(sort_input_targets);
! Assert(!linitial_int(sort_input_targets_contain_srfs));
! /* likewise for grouping_target vs. scanjoin_target */
! split_pathtarget_at_srfs(root, grouping_target, scanjoin_target,
! &grouping_targets,
! &grouping_targets_contain_srfs);
! grouping_target = (PathTarget *) linitial(grouping_targets);
! Assert(!linitial_int(grouping_targets_contain_srfs));
! /* scanjoin_target will not have any SRFs precomputed for it */
! split_pathtarget_at_srfs(root, scanjoin_target, NULL,
! &scanjoin_targets,
! &scanjoin_targets_contain_srfs);
! scanjoin_target = (PathTarget *) linitial(scanjoin_targets);
! Assert(!linitial_int(scanjoin_targets_contain_srfs));
! }
! else
! {
! /* initialize lists, just to keep compiler quiet */
! final_targets = final_targets_contain_srfs = NIL;
! sort_input_targets = sort_input_targets_contain_srfs = NIL;
! grouping_targets = grouping_targets_contain_srfs = NIL;
! scanjoin_targets = scanjoin_targets_contain_srfs = NIL;
! }
!
! /*
! * Forcibly apply SRF-free scan/join target to all the Paths for the
! * scan/join rel.
*
* In principle we should re-run set_cheapest() here to identify the
* cheapest path, but it seems unlikely that adding the same tlist
*************** grouping_planner(PlannerInfo *root, bool
*** 1853,1858 ****
--- 1908,1919 ----
current_rel->partial_pathlist = NIL;
}
+ /* Now fix things up if scan/join target contains SRFs */
+ if (parse->hasTargetSRFs)
+ adjust_paths_for_srfs(root, current_rel,
+ scanjoin_targets,
+ scanjoin_targets_contain_srfs);
+
/*
* Save the various upper-rel PathTargets we just computed into
* root->upper_targets[]. The core code doesn't use this, but it
*************** grouping_planner(PlannerInfo *root, bool
*** 1877,1882 ****
--- 1938,1948 ----
&agg_costs,
rollup_lists,
rollup_groupclauses);
+ /* Fix things up if grouping_target contains SRFs */
+ if (parse->hasTargetSRFs)
+ adjust_paths_for_srfs(root, current_rel,
+ grouping_targets,
+ grouping_targets_contain_srfs);
}
/*
*************** grouping_planner(PlannerInfo *root, bool
*** 1892,1897 ****
--- 1958,1968 ----
tlist,
wflists,
activeWindows);
+ /* Fix things up if sort_input_target contains SRFs */
+ if (parse->hasTargetSRFs)
+ adjust_paths_for_srfs(root, current_rel,
+ sort_input_targets,
+ sort_input_targets_contain_srfs);
}
/*
*************** grouping_planner(PlannerInfo *root, bool
*** 1920,1959 ****
final_target,
have_postponed_srfs ? -1.0 :
limit_tuples);
! }
!
! /*
! * If there are set-returning functions in the tlist, scale up the output
! * rowcounts of all surviving Paths to account for that. Note that if any
! * SRFs appear in sorting or grouping columns, we'll have underestimated
! * the numbers of rows passing through earlier steps; but that's such a
! * weird usage that it doesn't seem worth greatly complicating matters to
! * account for it.
! */
! if (parse->hasTargetSRFs)
! tlist_rows = tlist_returns_set_rows(tlist);
! else
! tlist_rows = 1;
!
! if (tlist_rows > 1)
! {
! foreach(lc, current_rel->pathlist)
! {
! Path *path = (Path *) lfirst(lc);
!
! /*
! * We assume that execution costs of the tlist as such were
! * already accounted for. However, it still seems appropriate to
! * charge something more for the executor's general costs of
! * processing the added tuples. The cost is probably less than
! * cpu_tuple_cost, though, so we arbitrarily use half of that.
! */
! path->total_cost += path->rows * (tlist_rows - 1) *
! cpu_tuple_cost / 2;
!
! path->rows *= tlist_rows;
! }
! /* No need to run set_cheapest; we're keeping all paths anyway. */
}
/*
--- 1991,2001 ----
final_target,
have_postponed_srfs ? -1.0 :
limit_tuples);
! /* Fix things up if final_target contains SRFs */
! if (parse->hasTargetSRFs)
! adjust_paths_for_srfs(root, current_rel,
! final_targets,
! final_targets_contain_srfs);
}
/*
*************** get_cheapest_fractional_path(RelOptInfo
*** 5151,5156 ****
--- 5193,5301 ----
}
/*
+ * adjust_paths_for_srfs
+ * Fix up the Paths of the given upperrel to handle tSRFs properly.
+ *
+ * The executor can only handle set-returning functions that appear at the
+ * top level of the targetlist of a Result plan node. If we have any SRFs
+ * that are not at top level, we need to split up the evaluation into multiple
+ * plan levels in which each level satisfies this constraint. This function
+ * modifies each Path of an upperrel that (might) compute any SRFs in its
+ * output tlist to insert appropriate projection steps.
+ *
+ * The given targets and targets_contain_srfs lists are from
+ * split_pathtarget_at_srfs(). We assume the existing Paths emit the first
+ * target in targets.
+ */
+ static void
+ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
+ List *targets, List *targets_contain_srfs)
+ {
+ ListCell *lc;
+
+ Assert(list_length(targets) == list_length(targets_contain_srfs));
+ Assert(!linitial_int(targets_contain_srfs));
+
+ /* If no SRFs appear at this plan level, nothing to do */
+ if (list_length(targets) == 1)
+ return;
+
+ /*
+ * Stack SRF-evaluation nodes atop each path for the rel.
+ *
+ * In principle we should re-run set_cheapest() here to identify the
+ * cheapest path, but it seems unlikely that adding the same tlist eval
+ * costs to all the paths would change that, so we don't bother. Instead,
+ * just assume that the cheapest-startup and cheapest-total paths remain
+ * so. (There should be no parameterized paths anymore, so we needn't
+ * worry about updating cheapest_parameterized_paths.)
+ */
+ foreach(lc, rel->pathlist)
+ {
+ Path *subpath = (Path *) lfirst(lc);
+ Path *newpath = subpath;
+ ListCell *lc1,
+ *lc2;
+
+ Assert(subpath->param_info == NULL);
+ forboth(lc1, targets, lc2, targets_contain_srfs)
+ {
+ PathTarget *thistarget = (PathTarget *) lfirst(lc1);
+ bool contains_srfs = (bool) lfirst_int(lc2);
+
+ /* If this level doesn't contain SRFs, do regular projection */
+ if (contains_srfs)
+ newpath = (Path *) create_srf_projection_path(root,
+ rel,
+ newpath,
+ thistarget);
+ else
+ newpath = (Path *) apply_projection_to_path(root,
+ rel,
+ newpath,
+ thistarget);
+ }
+ lfirst(lc) = newpath;
+ if (subpath == rel->cheapest_startup_path)
+ rel->cheapest_startup_path = newpath;
+ if (subpath == rel->cheapest_total_path)
+ rel->cheapest_total_path = newpath;
+ }
+
+ /* Likewise for partial paths, if any */
+ foreach(lc, rel->partial_pathlist)
+ {
+ Path *subpath = (Path *) lfirst(lc);
+ Path *newpath = subpath;
+ ListCell *lc1,
+ *lc2;
+
+ Assert(subpath->param_info == NULL);
+ forboth(lc1, targets, lc2, targets_contain_srfs)
+ {
+ PathTarget *thistarget = (PathTarget *) lfirst(lc1);
+ bool contains_srfs = (bool) lfirst_int(lc2);
+
+ /* If this level doesn't contain SRFs, do regular projection */
+ if (contains_srfs)
+ newpath = (Path *) create_srf_projection_path(root,
+ rel,
+ newpath,
+ thistarget);
+ else
+ {
+ /* avoid apply_projection_to_path, in case of multiple refs */
+ newpath = (Path *) create_projection_path(root,
+ rel,
+ newpath,
+ thistarget);
+ }
+ }
+ lfirst(lc) = newpath;
+ }
+ }
+
+ /*
* expression_planner
* Perform planner's transformations on a standalone expression.
*
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 663ffe0..0aa4339 100644
*** a/src/backend/optimizer/util/clauses.c
--- b/src/backend/optimizer/util/clauses.c
*************** static bool contain_agg_clause_walker(No
*** 99,105 ****
static bool get_agg_clause_costs_walker(Node *node,
get_agg_clause_costs_context *context);
static bool find_window_functions_walker(Node *node, WindowFuncLists *lists);
- static bool expression_returns_set_rows_walker(Node *node, double *count);
static bool contain_subplans_walker(Node *node, void *context);
static bool contain_mutable_functions_walker(Node *node, void *context);
static bool contain_volatile_functions_walker(Node *node, void *context);
--- 99,104 ----
*************** find_window_functions_walker(Node *node,
*** 780,893 ****
/*
* expression_returns_set_rows
* Estimate the number of rows returned by a set-returning expression.
! * The result is 1 if there are no set-returning functions.
*
! * We use the product of the rowcount estimates of all the functions in
! * the given tree (this corresponds to the behavior of ExecMakeFunctionResult
! * for nested set-returning functions).
*
* Note: keep this in sync with expression_returns_set() in nodes/nodeFuncs.c.
*/
double
expression_returns_set_rows(Node *clause)
{
! double result = 1;
!
! (void) expression_returns_set_rows_walker(clause, &result);
! return clamp_row_est(result);
! }
!
! static bool
! expression_returns_set_rows_walker(Node *node, double *count)
! {
! if (node == NULL)
! return false;
! if (IsA(node, FuncExpr))
{
! FuncExpr *expr = (FuncExpr *) node;
if (expr->funcretset)
! *count *= get_func_rows(expr->funcid);
}
! if (IsA(node, OpExpr))
{
! OpExpr *expr = (OpExpr *) node;
if (expr->opretset)
{
set_opfuncid(expr);
! *count *= get_func_rows(expr->opfuncid);
}
}
!
! /* Avoid recursion for some cases that can't return a set */
! if (IsA(node, Aggref))
! return false;
! if (IsA(node, WindowFunc))
! return false;
! if (IsA(node, DistinctExpr))
! return false;
! if (IsA(node, NullIfExpr))
! return false;
! if (IsA(node, ScalarArrayOpExpr))
! return false;
! if (IsA(node, BoolExpr))
! return false;
! if (IsA(node, SubLink))
! return false;
! if (IsA(node, SubPlan))
! return false;
! if (IsA(node, AlternativeSubPlan))
! return false;
! if (IsA(node, ArrayExpr))
! return false;
! if (IsA(node, RowExpr))
! return false;
! if (IsA(node, RowCompareExpr))
! return false;
! if (IsA(node, CoalesceExpr))
! return false;
! if (IsA(node, MinMaxExpr))
! return false;
! if (IsA(node, XmlExpr))
! return false;
!
! return expression_tree_walker(node, expression_returns_set_rows_walker,
! (void *) count);
! }
!
! /*
! * tlist_returns_set_rows
! * Estimate the number of rows returned by a set-returning targetlist.
! * The result is 1 if there are no set-returning functions.
! *
! * Here, the result is the largest rowcount estimate of any of the tlist's
! * expressions, not the product as you would get from naively applying
! * expression_returns_set_rows() to the whole tlist. The behavior actually
! * implemented by ExecTargetList produces a number of rows equal to the least
! * common multiple of the expression rowcounts, so that the product would be
! * a worst-case estimate that is typically not realistic. Taking the max as
! * we do here is a best-case estimate that might not be realistic either,
! * but it's probably closer for typical usages. We don't try to compute the
! * actual LCM because we're working with very approximate estimates, so their
! * LCM would be unduly noisy.
! */
! double
! tlist_returns_set_rows(List *tlist)
! {
! double result = 1;
! ListCell *lc;
!
! foreach(lc, tlist)
! {
! TargetEntry *tle = (TargetEntry *) lfirst(lc);
! double colresult;
!
! colresult = expression_returns_set_rows((Node *) tle->expr);
! if (result < colresult)
! result = colresult;
! }
! return result;
}
--- 779,815 ----
/*
* expression_returns_set_rows
* Estimate the number of rows returned by a set-returning expression.
! * The result is 1 if it's not a set-returning expression.
*
! * We should only examine the top-level function or operator; it used to be
! * appropriate to recurse, but not anymore. (Even if there are more SRFs in
! * the function's inputs, their multipliers are accounted for separately.)
*
* Note: keep this in sync with expression_returns_set() in nodes/nodeFuncs.c.
*/
double
expression_returns_set_rows(Node *clause)
{
! if (clause == NULL)
! return 1.0;
! if (IsA(clause, FuncExpr))
{
! FuncExpr *expr = (FuncExpr *) clause;
if (expr->funcretset)
! return clamp_row_est(get_func_rows(expr->funcid));
}
! if (IsA(clause, OpExpr))
{
! OpExpr *expr = (OpExpr *) clause;
if (expr->opretset)
{
set_opfuncid(expr);
! return clamp_row_est(get_func_rows(expr->opfuncid));
}
}
! return 1.0;
}
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index abb7507..5a7891f 100644
*** a/src/backend/optimizer/util/pathnode.c
--- b/src/backend/optimizer/util/pathnode.c
*************** create_projection_path(PlannerInfo *root
*** 2227,2232 ****
--- 2227,2235 ----
(cpu_tuple_cost + target->cost.per_tuple) * subpath->rows;
}
+ /* Assume no SRFs around */
+ pathnode->srfpp = false;
+
return pathnode;
}
*************** apply_projection_to_path(PlannerInfo *ro
*** 2320,2325 ****
--- 2323,2400 ----
}
/*
+ * create_srf_projection_path
+ * Creates a pathnode that represents performing a SRF projection.
+ *
+ * For the moment, we just use ProjectionPath for this, and generate a
+ * Result plan node. That's likely to change.
+ *
+ * 'rel' is the parent relation associated with the result
+ * 'subpath' is the path representing the source of data
+ * 'target' is the PathTarget to be computed
+ */
+ ProjectionPath *
+ create_srf_projection_path(PlannerInfo *root,
+ RelOptInfo *rel,
+ Path *subpath,
+ PathTarget *target)
+ {
+ ProjectionPath *pathnode = makeNode(ProjectionPath);
+ double tlist_rows;
+ ListCell *lc;
+
+ pathnode->path.pathtype = T_Result;
+ pathnode->path.parent = rel;
+ pathnode->path.pathtarget = target;
+ /* For now, assume we are above any joins, so no parameterization */
+ pathnode->path.param_info = NULL;
+ pathnode->path.parallel_aware = false;
+ pathnode->path.parallel_safe = rel->consider_parallel &&
+ subpath->parallel_safe &&
+ is_parallel_safe(root, (Node *) target->exprs);
+ pathnode->path.parallel_workers = subpath->parallel_workers;
+ /* Projection does not change the sort order */
+ pathnode->path.pathkeys = subpath->pathkeys;
+
+ pathnode->subpath = subpath;
+
+ /* Always need the Result node */
+ pathnode->dummypp = false;
+ pathnode->srfpp = true;
+
+ /*
+ * Estimate number of rows produced by SRFs for each row of input; if
+ * there's more than one in this node, use the maximum.
+ */
+ tlist_rows = 1;
+ foreach(lc, target->exprs)
+ {
+ Node *node = (Node *) lfirst(lc);
+ double itemrows;
+
+ itemrows = expression_returns_set_rows(node);
+ if (tlist_rows < itemrows)
+ tlist_rows = itemrows;
+ }
+
+ /*
+ * In addition to the cost of evaluating the tlist, charge cpu_tuple_cost
+ * per input row, and half of cpu_tuple_cost for each added output row.
+ * This is slightly bizarre maybe, but it's what 9.6 did; we may revisit
+ * this estimate later.
+ */
+ pathnode->path.rows = subpath->rows * tlist_rows;
+ pathnode->path.startup_cost = subpath->startup_cost +
+ target->cost.startup;
+ pathnode->path.total_cost = subpath->total_cost +
+ target->cost.startup +
+ (cpu_tuple_cost + target->cost.per_tuple) * subpath->rows +
+ (pathnode->path.rows - subpath->rows) * cpu_tuple_cost / 2;
+
+ return pathnode;
+ }
+
+ /*
* create_sort_path
* Creates a pathnode that represents performing an explicit sort.
*
diff --git a/src/backend/optimizer/util/tlist.c b/src/backend/optimizer/util/tlist.c
index 68096b3..ede7bb9 100644
*** a/src/backend/optimizer/util/tlist.c
--- b/src/backend/optimizer/util/tlist.c
***************
*** 16,24 ****
--- 16,35 ----
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+ #include "optimizer/cost.h"
#include "optimizer/tlist.h"
+ typedef struct
+ {
+ List *nextlevel_tlist;
+ bool nextlevel_contains_srfs;
+ } split_pathtarget_context;
+
+ static bool split_pathtarget_walker(Node *node,
+ split_pathtarget_context *context);
+
+
/*****************************************************************************
* Target list creation and searching utilities
*****************************************************************************/
*************** apply_pathtarget_labeling_to_tlist(List
*** 759,761 ****
--- 770,960 ----
i++;
}
}
+
+ /*
+ * split_pathtarget_at_srfs
+ * Split given PathTarget into multiple levels to position SRFs safely
+ *
+ * The executor can only handle set-returning functions that appear at the
+ * top level of the targetlist of a Result plan node. If we have any SRFs
+ * that are not at top level, we need to split up the evaluation into multiple
+ * plan levels in which each level satisfies this constraint. This function
+ * creates appropriate PathTarget(s) for each level.
+ *
+ * As an example, consider the tlist expression
+ * x + srf1(srf2(y + z))
+ * This expression should appear as-is in the top PathTarget, but below that
+ * we must have a PathTarget containing
+ * x, srf1(srf2(y + z))
+ * and below that, another PathTarget containing
+ * x, srf2(y + z)
+ * and below that, another PathTarget containing
+ * x, y, z
+ * When these tlists are processed by setrefs.c, subexpressions that match
+ * output expressions of the next lower tlist will be replaced by Vars,
+ * so that what the executor gets are tlists looking like
+ * Var1 + Var2
+ * Var1, srf1(Var2)
+ * Var1, srf2(Var2 + Var3)
+ * x, y, z
+ * which satisfy the desired property.
+ *
+ * In some cases, a SRF has already been evaluated in some previous plan level
+ * and we shouldn't expand it again (that is, what we see in the target is
+ * already meant as a reference to a lower subexpression). So, don't expand
+ * any tlist expressions that appear in input_target, if that's not NULL.
+ * In principle we might need to consider matching subexpressions to
+ * input_target, but for now it's not necessary because only ORDER BY and
+ * GROUP BY expressions are at issue and those will look the same at both
+ * plan levels.
+ *
+ * The outputs of this function are two parallel lists, one a list of
+ * PathTargets and the other an integer list of bool flags indicating
+ * whether the corresponding PathTarget contains any top-level SRFs.
+ * The lists are given in the order they'd need to be evaluated in, with
+ * the "lowest" PathTarget first. So the last list entry is always the
+ * originally given PathTarget, and any entries before it indicate evaluation
+ * levels that must be inserted below it. The first list entry must not
+ * contain any SRFs, since it will typically be attached to a plan node
+ * that cannot evaluate SRFs.
+ *
+ * Note: using a list for the flags may seem like overkill, since there
+ * are only a few possible patterns for which levels contain SRFs.
+ * But this representation decouples callers from that knowledge.
+ */
+ void
+ split_pathtarget_at_srfs(PlannerInfo *root,
+ PathTarget *target, PathTarget *input_target,
+ List **targets, List **targets_contain_srfs)
+ {
+ /* Initialize output lists to empty; we prepend to them within loop */
+ *targets = *targets_contain_srfs = NIL;
+
+ /* Loop to consider each level of PathTarget we need */
+ for (;;)
+ {
+ bool target_contains_srfs = false;
+ split_pathtarget_context context;
+ ListCell *lc;
+
+ context.nextlevel_tlist = NIL;
+ context.nextlevel_contains_srfs = false;
+
+ /*
+ * Scan the PathTarget looking for SRFs. Top-level SRFs are handled
+ * in this loop, ones lower down are found by split_pathtarget_walker.
+ */
+ foreach(lc, target->exprs)
+ {
+ Node *node = (Node *) lfirst(lc);
+
+ /*
+ * A tlist item that is just a reference to an expression already
+ * computed in input_target need not be evaluated here, so just
+ * make sure it's included in the next PathTarget.
+ */
+ if (input_target && list_member(input_target->exprs, node))
+ {
+ context.nextlevel_tlist = lappend(context.nextlevel_tlist, node);
+ continue;
+ }
+
+ /* Else, we need to compute this expression. */
+ if (IsA(node, FuncExpr) &&
+ ((FuncExpr *) node)->funcretset)
+ {
+ /* Top-level SRF: it can be evaluated here */
+ target_contains_srfs = true;
+ /* Recursively examine SRF's inputs */
+ split_pathtarget_walker((Node *) ((FuncExpr *) node)->args,
+ &context);
+ }
+ else if (IsA(node, OpExpr) &&
+ ((OpExpr *) node)->opretset)
+ {
+ /* Same as above, but for set-returning operator */
+ target_contains_srfs = true;
+ split_pathtarget_walker((Node *) ((OpExpr *) node)->args,
+ &context);
+ }
+ else
+ {
+ /* Not a top-level SRF, so recursively examine expression */
+ split_pathtarget_walker(node, &context);
+ }
+ }
+
+ /*
+ * Prepend current target and associated flag to output lists.
+ */
+ *targets = lcons(target, *targets);
+ *targets_contain_srfs = lcons_int(target_contains_srfs,
+ *targets_contain_srfs);
+
+ /*
+ * Done if we found no SRFs anywhere in this target; the tentative
+ * tlist we built for the next level can be discarded.
+ */
+ if (!target_contains_srfs && !context.nextlevel_contains_srfs)
+ break;
+
+ /*
+ * Else build the next PathTarget down, and loop back to process it.
+ * Copy the subexpressions to make sure PathTargets don't share
+ * substructure (might be unnecessary, but be safe); and drop any
+ * duplicate entries in the sub-targetlist.
+ */
+ target = create_empty_pathtarget();
+ add_new_columns_to_pathtarget(target,
+ (List *) copyObject(context.nextlevel_tlist));
+ set_pathtarget_cost_width(root, target);
+ }
+ }
+
+ /* Recursively examine expressions for split_pathtarget_at_srfs */
+ static bool
+ split_pathtarget_walker(Node *node, split_pathtarget_context *context)
+ {
+ if (node == NULL)
+ return false;
+ if (IsA(node, Var) ||
+ IsA(node, PlaceHolderVar) ||
+ IsA(node, Aggref) ||
+ IsA(node, GroupingFunc) ||
+ IsA(node, WindowFunc))
+ {
+ /*
+ * Pass these items down to the child plan level for evaluation.
+ *
+ * We assume that these constructs cannot contain any SRFs (if one
+ * does, there will be an executor failure from a misplaced SRF).
+ */
+ context->nextlevel_tlist = lappend(context->nextlevel_tlist, node);
+
+ /* Having done that, we need not examine their sub-structure */
+ return false;
+ }
+ else if ((IsA(node, FuncExpr) &&
+ ((FuncExpr *) node)->funcretset) ||
+ (IsA(node, OpExpr) &&
+ ((OpExpr *) node)->opretset))
+ {
+ /*
+ * Pass SRFs down to the child plan level for evaluation, and mark
+ * that it contains SRFs. (We are not at top level of our own tlist,
+ * else this would have been picked up by split_pathtarget_at_srfs.)
+ */
+ context->nextlevel_tlist = lappend(context->nextlevel_tlist, node);
+ context->nextlevel_contains_srfs = true;
+
+ /* Inputs to the SRF need not be considered here, so we're done */
+ return false;
+ }
+
+ /*
+ * Otherwise, the node is evaluatable within the current PathTarget, so
+ * recurse to examine its inputs.
+ */
+ return expression_tree_walker(node, split_pathtarget_walker,
+ (void *) context);
+ }
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 2709cc7..0cb42b7 100644
*** a/src/include/nodes/relation.h
--- b/src/include/nodes/relation.h
*************** typedef struct ProjectionPath
*** 1293,1298 ****
--- 1293,1299 ----
Path path;
Path *subpath; /* path representing input source */
bool dummypp; /* true if no separate Result is needed */
+ bool srfpp; /* true if SRFs are being evaluated here */
} ProjectionPath;
/*
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index 9abef37..1d0fa30 100644
*** a/src/include/optimizer/clauses.h
--- b/src/include/optimizer/clauses.h
*************** extern bool contain_window_function(Node
*** 54,60 ****
extern WindowFuncLists *find_window_functions(Node *clause, Index maxWinRef);
extern double expression_returns_set_rows(Node *clause);
- extern double tlist_returns_set_rows(List *tlist);
extern bool contain_subplans(Node *clause);
--- 54,59 ----
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 71d9154..c452927 100644
*** a/src/include/optimizer/pathnode.h
--- b/src/include/optimizer/pathnode.h
*************** extern Path *apply_projection_to_path(Pl
*** 144,149 ****
--- 144,153 ----
RelOptInfo *rel,
Path *path,
PathTarget *target);
+ extern ProjectionPath *create_srf_projection_path(PlannerInfo *root,
+ RelOptInfo *rel,
+ Path *subpath,
+ PathTarget *target);
extern SortPath *create_sort_path(PlannerInfo *root,
RelOptInfo *rel,
Path *subpath,
diff --git a/src/include/optimizer/tlist.h b/src/include/optimizer/tlist.h
index 0d745a0..edd1e80 100644
*** a/src/include/optimizer/tlist.h
--- b/src/include/optimizer/tlist.h
*************** extern void add_column_to_pathtarget(Pat
*** 61,66 ****
--- 61,69 ----
extern void add_new_column_to_pathtarget(PathTarget *target, Expr *expr);
extern void add_new_columns_to_pathtarget(PathTarget *target, List *exprs);
extern void apply_pathtarget_labeling_to_tlist(List *tlist, PathTarget *target);
+ extern void split_pathtarget_at_srfs(PlannerInfo *root,
+ PathTarget *target, PathTarget *input_target,
+ List **targets, List **targets_contain_srfs);
/* Convenience macro to get a PathTarget with valid cost/width fields */
#define create_pathtarget(root, tlist) \
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index 45208a6..e3804e9 100644
*** a/src/test/regress/expected/aggregates.out
--- b/src/test/regress/expected/aggregates.out
*************** explain (costs off)
*** 823,829 ****
-> Index Only Scan Backward using tenk1_unique2 on tenk1
Index Cond: (unique2 IS NOT NULL)
-> Result
! (7 rows)
select max(unique2), generate_series(1,3) as g from tenk1 order by g desc;
max | g
--- 823,830 ----
-> Index Only Scan Backward using tenk1_unique2 on tenk1
Index Cond: (unique2 IS NOT NULL)
-> Result
! -> Result
! (8 rows)
select max(unique2), generate_series(1,3) as g from tenk1 order by g desc;
max | g
diff --git a/src/test/regress/expected/limit.out b/src/test/regress/expected/limit.out
index 9c3eecf..a7ded3a 100644
*** a/src/test/regress/expected/limit.out
--- b/src/test/regress/expected/limit.out
*************** select currval('testseq');
*** 208,220 ****
explain (verbose, costs off)
select unique1, unique2, generate_series(1,10)
from tenk1 order by unique2 limit 7;
! QUERY PLAN
! ----------------------------------------------------------
Limit
Output: unique1, unique2, (generate_series(1, 10))
! -> Index Scan using tenk1_unique2 on public.tenk1
Output: unique1, unique2, generate_series(1, 10)
! (4 rows)
select unique1, unique2, generate_series(1,10)
from tenk1 order by unique2 limit 7;
--- 208,222 ----
explain (verbose, costs off)
select unique1, unique2, generate_series(1,10)
from tenk1 order by unique2 limit 7;
! QUERY PLAN
! -------------------------------------------------------------------------------------------------------------------------------------------------------------
Limit
Output: unique1, unique2, (generate_series(1, 10))
! -> Result
Output: unique1, unique2, generate_series(1, 10)
! -> Index Scan using tenk1_unique2 on public.tenk1
! Output: unique1, unique2, two, four, ten, twenty, hundred, thousand, twothousand, fivethous, tenthous, odd, even, stringu1, stringu2, string4
! (6 rows)
select unique1, unique2, generate_series(1,10)
from tenk1 order by unique2 limit 7;
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index f06cfa4..9634fa1 100644
*** a/src/test/regress/expected/rangefuncs.out
--- b/src/test/regress/expected/rangefuncs.out
*************** SELECT *,
*** 1995,2006 ****
END)
FROM
(VALUES (1,''), (2,'0000000049404'), (3,'FROM 10000000876')) v(id, str);
! id | str | lower
! ----+------------------+------------------
! 1 | |
! 2 | 0000000049404 | 49404
! 3 | FROM 10000000876 | from 10000000876
! (3 rows)
-- check whole-row-Var handling in nested lateral functions (bug #11703)
create function extractq2(t int8_tbl) returns int8 as $$
--- 1995,2004 ----
END)
FROM
(VALUES (1,''), (2,'0000000049404'), (3,'FROM 10000000876')) v(id, str);
! id | str | lower
! ----+---------------+-------
! 2 | 0000000049404 | 49404
! (1 row)
-- check whole-row-Var handling in nested lateral functions (bug #11703)
create function extractq2(t int8_tbl) returns int8 as $$
diff --git a/src/test/regress/expected/subselect.out b/src/test/regress/expected/subselect.out
index 0fc93d9..e76cb6b 100644
*** a/src/test/regress/expected/subselect.out
--- b/src/test/regress/expected/subselect.out
*************** select * from int4_tbl where
*** 807,830 ****
explain (verbose, costs off)
select * from int4_tbl o where (f1, f1) in
(select f1, generate_series(1,2) / 10 g from int4_tbl i group by f1);
! QUERY PLAN
! ----------------------------------------------------------------
! Hash Semi Join
Output: o.f1
! Hash Cond: (o.f1 = "ANY_subquery".f1)
-> Seq Scan on public.int4_tbl o
Output: o.f1
! -> Hash
Output: "ANY_subquery".f1, "ANY_subquery".g
-> Subquery Scan on "ANY_subquery"
Output: "ANY_subquery".f1, "ANY_subquery".g
Filter: ("ANY_subquery".f1 = "ANY_subquery".g)
! -> HashAggregate
! Output: i.f1, (generate_series(1, 2) / 10)
! Group Key: i.f1
! -> Seq Scan on public.int4_tbl i
! Output: i.f1
! (15 rows)
select * from int4_tbl o where (f1, f1) in
(select f1, generate_series(1,2) / 10 g from int4_tbl i group by f1);
--- 807,834 ----
explain (verbose, costs off)
select * from int4_tbl o where (f1, f1) in
(select f1, generate_series(1,2) / 10 g from int4_tbl i group by f1);
! QUERY PLAN
! -------------------------------------------------------------------
! Nested Loop Semi Join
Output: o.f1
! Join Filter: (o.f1 = "ANY_subquery".f1)
-> Seq Scan on public.int4_tbl o
Output: o.f1
! -> Materialize
Output: "ANY_subquery".f1, "ANY_subquery".g
-> Subquery Scan on "ANY_subquery"
Output: "ANY_subquery".f1, "ANY_subquery".g
Filter: ("ANY_subquery".f1 = "ANY_subquery".g)
! -> Result
! Output: i.f1, ((generate_series(1, 2)) / 10)
! -> Result
! Output: i.f1, generate_series(1, 2)
! -> HashAggregate
! Output: i.f1
! Group Key: i.f1
! -> Seq Scan on public.int4_tbl i
! Output: i.f1
! (19 rows)
select * from int4_tbl o where (f1, f1) in
(select f1, generate_series(1,2) / 10 g from int4_tbl i group by f1);
diff --git a/src/test/regress/expected/tsrf.out b/src/test/regress/expected/tsrf.out
index e9bea41..4e87186 100644
*** a/src/test/regress/expected/tsrf.out
--- b/src/test/regress/expected/tsrf.out
*************** SELECT generate_series(1, generate_serie
*** 43,49 ****
-- srf, with two SRF arguments
SELECT generate_series(generate_series(1,3), generate_series(2, 4));
! ERROR: functions and operators can take at most one set argument
CREATE TABLE few(id int, dataa text, datab text);
INSERT INTO few VALUES(1, 'a', 'foo'),(2, 'a', 'bar'),(3, 'b', 'bar');
-- SRF output order of sorting is maintained, if SRF is not referenced
--- 43,58 ----
-- srf, with two SRF arguments
SELECT generate_series(generate_series(1,3), generate_series(2, 4));
! generate_series
! -----------------
! 1
! 2
! 2
! 3
! 3
! 4
! (6 rows)
!
CREATE TABLE few(id int, dataa text, datab text);
INSERT INTO few VALUES(1, 'a', 'foo'),(2, 'a', 'bar'),(3, 'b', 'bar');
-- SRF output order of sorting is maintained, if SRF is not referenced