I wrote:
> Thanks for the report. I traced through this, and the problem seems to
> be that split_pathtarget_at_srfs() is neglecting to attach sortgroupref
> labeling to the extra PathTargets it constructs. I don't remember
> whether that code is my fault or Andres', but I'll take a look at
> fixing it.
Here's a proposed patch for that.
regards, tom lane
diff --git a/src/backend/optimizer/util/tlist.c b/src/backend/optimizer/util/tlist.c
index 32160d5..5500f33 100644
*** a/src/backend/optimizer/util/tlist.c
--- b/src/backend/optimizer/util/tlist.c
***************
*** 25,44 ****
((IsA(node, FuncExpr) && ((FuncExpr *) (node))->funcretset) || \
(IsA(node, OpExpr) && ((OpExpr *) (node))->opretset))
! /* Workspace for split_pathtarget_walker */
typedef struct
{
List *input_target_exprs; /* exprs available from input */
! List *level_srfs; /* list of lists of SRF exprs */
! List *level_input_vars; /* vars needed by SRFs of each level */
! List *level_input_srfs; /* SRFs needed by SRFs of each level */
List *current_input_vars; /* vars needed in current subexpr */
List *current_input_srfs; /* SRFs needed in current subexpr */
int current_depth; /* max SRF depth in current subexpr */
} split_pathtarget_context;
static bool split_pathtarget_walker(Node *node,
split_pathtarget_context *context);
/*****************************************************************************
--- 25,62 ----
((IsA(node, FuncExpr) && ((FuncExpr *) (node))->funcretset) || \
(IsA(node, OpExpr) && ((OpExpr *) (node))->opretset))
! /*
! * Data structures for split_pathtarget_at_srfs(). To preserve the identity
! * of sortgroupref items even if they are textually equal(), what we track is
! * not just bare expressions but expressions plus their sortgroupref indexes.
! */
typedef struct
{
+ Node *expr; /* some subexpression of a PathTarget */
+ Index sortgroupref; /* its sortgroupref, or 0 if none */
+ } split_pathtarget_item;
+
+ typedef struct
+ {
+ /* This is a List of bare expressions: */
List *input_target_exprs; /* exprs available from input */
! /* These are Lists of Lists of split_pathtarget_items: */
! List *level_srfs; /* SRF exprs to evaluate at each level */
! List *level_input_vars; /* input vars needed at each level */
! List *level_input_srfs; /* input SRFs needed at each level */
! /* These are Lists of split_pathtarget_items: */
List *current_input_vars; /* vars needed in current subexpr */
List *current_input_srfs; /* SRFs needed in current subexpr */
+ /* Auxiliary data for current split_pathtarget_walker traversal: */
int current_depth; /* max SRF depth in current subexpr */
+ Index current_sgref; /* current subexpr's sortgroupref, or 0 */
} split_pathtarget_context;
static bool split_pathtarget_walker(Node *node,
split_pathtarget_context *context);
+ static void add_sp_item_to_pathtarget(PathTarget *target,
+ split_pathtarget_item *item);
+ static void add_sp_items_to_pathtarget(PathTarget *target, List *items);
/*****************************************************************************
*************** apply_pathtarget_labeling_to_tlist(List
*** 822,827 ****
--- 840,848 ----
* 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.
*
+ * It's also important that we preserve any sortgroupref annotation appearing
+ * in the given target, especially on expressions matching input_target items.
+ *
* 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 evaluatable SRFs.
*************** split_pathtarget_at_srfs(PlannerInfo *ro
*** 845,850 ****
--- 866,872 ----
int max_depth;
bool need_extra_projection;
List *prev_level_tlist;
+ int lci;
ListCell *lc,
*lc1,
*lc2,
*************** split_pathtarget_at_srfs(PlannerInfo *ro
*** 884,893 ****
--- 906,920 ----
need_extra_projection = false;
/* Scan each expression in the PathTarget looking for SRFs */
+ lci = 0;
foreach(lc, target->exprs)
{
Node *node = (Node *) lfirst(lc);
+ /* Tell split_pathtarget_walker about this expr's sortgroupref */
+ context.current_sgref = get_pathtarget_sortgroupref(target, lci);
+ lci++;
+
/*
* Find all SRFs and Vars (and Var-like nodes) in this expression, and
* enter them into appropriate lists within the context struct.
*************** split_pathtarget_at_srfs(PlannerInfo *ro
*** 981,996 ****
* This target should actually evaluate any SRFs of the current
* level, and it needs to propagate forward any Vars needed by
* later levels, as well as SRFs computed earlier and needed by
! * later levels. We rely on add_new_columns_to_pathtarget() to
! * remove duplicate items. Also, for safety, make a separate copy
! * of each item for each PathTarget.
*/
! add_new_columns_to_pathtarget(ntarget, copyObject(level_srfs));
for_each_cell(lc, lnext(lc2))
{
List *input_vars = (List *) lfirst(lc);
! add_new_columns_to_pathtarget(ntarget, copyObject(input_vars));
}
for_each_cell(lc, lnext(lc3))
{
--- 1008,1021 ----
* This target should actually evaluate any SRFs of the current
* level, and it needs to propagate forward any Vars needed by
* later levels, as well as SRFs computed earlier and needed by
! * later levels.
*/
! add_sp_items_to_pathtarget(ntarget, level_srfs);
for_each_cell(lc, lnext(lc2))
{
List *input_vars = (List *) lfirst(lc);
! add_sp_items_to_pathtarget(ntarget, input_vars);
}
for_each_cell(lc, lnext(lc3))
{
*************** split_pathtarget_at_srfs(PlannerInfo *ro
*** 999,1008 ****
foreach(lcx, input_srfs)
{
! Expr *srf = (Expr *) lfirst(lcx);
! if (list_member(prev_level_tlist, srf))
! add_new_column_to_pathtarget(ntarget, copyObject(srf));
}
}
set_pathtarget_cost_width(root, ntarget);
--- 1024,1033 ----
foreach(lcx, input_srfs)
{
! split_pathtarget_item *item = lfirst(lcx);
! if (list_member(prev_level_tlist, item->expr))
! add_sp_item_to_pathtarget(ntarget, item);
}
}
set_pathtarget_cost_width(root, ntarget);
*************** split_pathtarget_walker(Node *node, spli
*** 1037,1048 ****
* input_target can be treated like a Var (which indeed it will be after
* setrefs.c gets done with it), even if it's actually a SRF. Record it
* as being needed for the current expression, and ignore any
! * substructure.
*/
if (list_member(context->input_target_exprs, node))
{
context->current_input_vars = lappend(context->current_input_vars,
! node);
return false;
}
--- 1062,1078 ----
* input_target can be treated like a Var (which indeed it will be after
* setrefs.c gets done with it), even if it's actually a SRF. Record it
* as being needed for the current expression, and ignore any
! * substructure. (Note in particular that this preserves the identity of
! * any expressions that appear as sortgrouprefs in input_target.)
*/
if (list_member(context->input_target_exprs, node))
{
+ split_pathtarget_item *item = palloc(sizeof(split_pathtarget_item));
+
+ item->expr = node;
+ item->sortgroupref = context->current_sgref;
context->current_input_vars = lappend(context->current_input_vars,
! item);
return false;
}
*************** split_pathtarget_walker(Node *node, spli
*** 1057,1064 ****
IsA(node, GroupingFunc) ||
IsA(node, WindowFunc))
{
context->current_input_vars = lappend(context->current_input_vars,
! node);
return false;
}
--- 1087,1098 ----
IsA(node, GroupingFunc) ||
IsA(node, WindowFunc))
{
+ split_pathtarget_item *item = palloc(sizeof(split_pathtarget_item));
+
+ item->expr = node;
+ item->sortgroupref = context->current_sgref;
context->current_input_vars = lappend(context->current_input_vars,
! item);
return false;
}
*************** split_pathtarget_walker(Node *node, spli
*** 1068,1082 ****
--- 1102,1121 ----
*/
if (IS_SRF_CALL(node))
{
+ split_pathtarget_item *item = palloc(sizeof(split_pathtarget_item));
List *save_input_vars = context->current_input_vars;
List *save_input_srfs = context->current_input_srfs;
int save_current_depth = context->current_depth;
int srf_depth;
ListCell *lc;
+ item->expr = node;
+ item->sortgroupref = context->current_sgref;
+
context->current_input_vars = NIL;
context->current_input_srfs = NIL;
context->current_depth = 0;
+ context->current_sgref = 0; /* subexpressions are not sortgroup items */
(void) expression_tree_walker(node, split_pathtarget_walker,
(void *) context);
*************** split_pathtarget_walker(Node *node, spli
*** 1094,1100 ****
/* Record this SRF as needing to be evaluated at appropriate level */
lc = list_nth_cell(context->level_srfs, srf_depth);
! lfirst(lc) = lappend(lfirst(lc), node);
/* Record its inputs as being needed at the same level */
lc = list_nth_cell(context->level_input_vars, srf_depth);
--- 1133,1139 ----
/* Record this SRF as needing to be evaluated at appropriate level */
lc = list_nth_cell(context->level_srfs, srf_depth);
! lfirst(lc) = lappend(lfirst(lc), item);
/* Record its inputs as being needed at the same level */
lc = list_nth_cell(context->level_input_vars, srf_depth);
*************** split_pathtarget_walker(Node *node, spli
*** 1108,1114 ****
* surrounding expression.
*/
context->current_input_vars = save_input_vars;
! context->current_input_srfs = lappend(save_input_srfs, node);
context->current_depth = Max(save_current_depth, srf_depth);
/* We're done here */
--- 1147,1153 ----
* surrounding expression.
*/
context->current_input_vars = save_input_vars;
! context->current_input_srfs = lappend(save_input_srfs, item);
context->current_depth = Max(save_current_depth, srf_depth);
/* We're done here */
*************** split_pathtarget_walker(Node *node, spli
*** 1119,1124 ****
--- 1158,1236 ----
* Otherwise, the node is a scalar (non-set) expression, so recurse to
* examine its inputs.
*/
+ context->current_sgref = 0; /* subexpressions are not sortgroup items */
return expression_tree_walker(node, split_pathtarget_walker,
(void *) context);
}
+
+ /*
+ * Add a split_pathtarget_item to the PathTarget, unless a matching item is
+ * already present. This is like add_new_column_to_pathtarget, but allows
+ * for sortgrouprefs to be handled. An item having zero sortgroupref can
+ * be merged with one that has a sortgroupref, acquiring the latter's
+ * sortgroupref.
+ *
+ * Note that we don't worry about possibly adding duplicate sortgrouprefs
+ * to the PathTarget. That would be bad, but it should be impossible unless
+ * the target passed to split_pathtarget_at_srfs already had duplicates.
+ * As long as it didn't, we can have at most one split_pathtarget_item with
+ * any particular nonzero sortgroupref.
+ */
+ static void
+ add_sp_item_to_pathtarget(PathTarget *target, split_pathtarget_item *item)
+ {
+ int lci;
+ ListCell *lc;
+
+ /*
+ * Look for a pre-existing entry that is equal() and does not have a
+ * conflicting sortgroupref already.
+ */
+ lci = 0;
+ foreach(lc, target->exprs)
+ {
+ Node *node = (Node *) lfirst(lc);
+ Index sgref = get_pathtarget_sortgroupref(target, lci);
+
+ if ((item->sortgroupref == sgref ||
+ item->sortgroupref == 0 ||
+ sgref == 0) &&
+ equal(item->expr, node))
+ {
+ /* Found a match. Assign item's sortgroupref if it has one. */
+ if (item->sortgroupref)
+ {
+ if (target->sortgrouprefs == NULL)
+ {
+ target->sortgrouprefs = (Index *)
+ palloc0(list_length(target->exprs) * sizeof(Index));
+ }
+ target->sortgrouprefs[lci] = item->sortgroupref;
+ }
+ return;
+ }
+ lci++;
+ }
+
+ /*
+ * No match, so add item to PathTarget. Copy the expr for safety.
+ */
+ add_column_to_pathtarget(target, (Expr *) copyObject(item->expr),
+ item->sortgroupref);
+ }
+
+ /*
+ * Apply add_sp_item_to_pathtarget to each element of list.
+ */
+ static void
+ add_sp_items_to_pathtarget(PathTarget *target, List *items)
+ {
+ ListCell *lc;
+
+ foreach(lc, items)
+ {
+ split_pathtarget_item *item = lfirst(lc);
+
+ add_sp_item_to_pathtarget(target, item);
+ }
+ }
diff --git a/src/test/regress/expected/select_parallel.out b/src/test/regress/expected/select_parallel.out
index e48e394..cd0b945 100644
*** a/src/test/regress/expected/select_parallel.out
--- b/src/test/regress/expected/select_parallel.out
*************** explain (costs off, verbose)
*** 659,664 ****
--- 659,726 ----
(11 rows)
drop function sp_simple_func(integer);
+ -- test handling of SRFs in targetlist (bug in 10.0)
+ explain (costs off)
+ select count(*), generate_series(1,2) from tenk1 group by twenty;
+ QUERY PLAN
+ ----------------------------------------------------------
+ ProjectSet
+ -> Finalize GroupAggregate
+ Group Key: twenty
+ -> Gather Merge
+ Workers Planned: 4
+ -> Partial GroupAggregate
+ Group Key: twenty
+ -> Sort
+ Sort Key: twenty
+ -> Parallel Seq Scan on tenk1
+ (10 rows)
+
+ select count(*), generate_series(1,2) from tenk1 group by twenty;
+ count | generate_series
+ -------+-----------------
+ 500 | 1
+ 500 | 2
+ 500 | 1
+ 500 | 2
+ 500 | 1
+ 500 | 2
+ 500 | 1
+ 500 | 2
+ 500 | 1
+ 500 | 2
+ 500 | 1
+ 500 | 2
+ 500 | 1
+ 500 | 2
+ 500 | 1
+ 500 | 2
+ 500 | 1
+ 500 | 2
+ 500 | 1
+ 500 | 2
+ 500 | 1
+ 500 | 2
+ 500 | 1
+ 500 | 2
+ 500 | 1
+ 500 | 2
+ 500 | 1
+ 500 | 2
+ 500 | 1
+ 500 | 2
+ 500 | 1
+ 500 | 2
+ 500 | 1
+ 500 | 2
+ 500 | 1
+ 500 | 2
+ 500 | 1
+ 500 | 2
+ 500 | 1
+ 500 | 2
+ (40 rows)
+
-- test gather merge with parallel leader participation disabled
set parallel_leader_participation = off;
explain (costs off)
diff --git a/src/test/regress/sql/select_parallel.sql b/src/test/regress/sql/select_parallel.sql
index 31045d7..7771e05 100644
*** a/src/test/regress/sql/select_parallel.sql
--- b/src/test/regress/sql/select_parallel.sql
*************** explain (costs off, verbose)
*** 261,266 ****
--- 261,273 ----
drop function sp_simple_func(integer);
+ -- test handling of SRFs in targetlist (bug in 10.0)
+
+ explain (costs off)
+ select count(*), generate_series(1,2) from tenk1 group by twenty;
+
+ select count(*), generate_series(1,2) from tenk1 group by twenty;
+
-- test gather merge with parallel leader participation disabled
set parallel_leader_participation = off;