Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit a209936

Browse files
committed
Fix case of window function + aggregate + GROUP BY expression.
In commit 1bc16a9 I added a minor optimization to drop the component variables of a GROUP BY expression from the target list computed at the aggregation level of a query, if those Vars weren't referenced elsewhere in the tlist. However, I overlooked that the window-function planning code would deconstruct such expressions and thus need to have access to their component variables. Fix it to not do that. While at it, I removed the distinction between volatile and nonvolatile window partition/order expressions: the code now computes all of them at the aggregation level. This saves a relatively expensive check for volatility, and it's unclear that the resulting plan isn't better anyway. Per bug #7535 from Louis-David Mitterrand. Back-patch to 9.2.
1 parent 6b896f5 commit a209936

File tree

3 files changed

+137
-44
lines changed

3 files changed

+137
-44
lines changed

src/backend/optimizer/plan/planner.c

Lines changed: 119 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,8 @@ static void locate_grouping_columns(PlannerInfo *root,
9090
AttrNumber *groupColIdx);
9191
static List *postprocess_setop_tlist(List *new_tlist, List *orig_tlist);
9292
static List *select_active_windows(PlannerInfo *root, WindowFuncLists *wflists);
93-
static List *add_volatile_sort_exprs(List *window_tlist, List *tlist,
94-
List *activeWindows);
93+
static List *make_windowInputTargetList(PlannerInfo *root,
94+
List *tlist, List *activeWindows);
9595
static List *make_pathkeys_for_window(PlannerInfo *root, WindowClause *wc,
9696
List *tlist, bool canonicalize);
9797
static void get_column_info_for_window(PlannerInfo *root, WindowClause *wc,
@@ -1555,31 +1555,25 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
15551555

15561556
/*
15571557
* The "base" targetlist for all steps of the windowing process is
1558-
* a flat tlist of all Vars and Aggs needed in the result. (In
1558+
* a flat tlist of all Vars and Aggs needed in the result. (In
15591559
* some cases we wouldn't need to propagate all of these all the
15601560
* way to the top, since they might only be needed as inputs to
15611561
* WindowFuncs. It's probably not worth trying to optimize that
1562-
* though.) We also need any volatile sort expressions, because
1563-
* make_sort_from_pathkeys won't add those on its own, and anyway
1564-
* we want them evaluated only once at the bottom of the stack. As
1565-
* we climb up the stack, we add outputs for the WindowFuncs
1566-
* computed at each level. Also, each input tlist has to present
1567-
* all the columns needed to sort the data for the next WindowAgg
1568-
* step. That's handled internally by make_sort_from_pathkeys,
1569-
* but we need the copyObject steps here to ensure that each plan
1570-
* node has a separately modifiable tlist.
1571-
*
1572-
* Note: it's essential here to use PVC_INCLUDE_AGGREGATES so that
1573-
* Vars mentioned only in aggregate expressions aren't pulled out
1574-
* as separate targetlist entries. Otherwise we could be putting
1575-
* ungrouped Vars directly into an Agg node's tlist, resulting in
1576-
* undefined behavior.
1562+
* though.) We also add window partitioning and sorting
1563+
* expressions to the base tlist, to ensure they're computed only
1564+
* once at the bottom of the stack (that's critical for volatile
1565+
* functions). As we climb up the stack, we'll add outputs for
1566+
* the WindowFuncs computed at each level.
1567+
*/
1568+
window_tlist = make_windowInputTargetList(root,
1569+
tlist,
1570+
activeWindows);
1571+
1572+
/*
1573+
* The copyObject steps here are needed to ensure that each plan
1574+
* node has a separately modifiable tlist. (XXX wouldn't a
1575+
* shallow list copy do for that?)
15771576
*/
1578-
window_tlist = flatten_tlist(tlist,
1579-
PVC_INCLUDE_AGGREGATES,
1580-
PVC_INCLUDE_PLACEHOLDERS);
1581-
window_tlist = add_volatile_sort_exprs(window_tlist, tlist,
1582-
activeWindows);
15831577
result_plan->targetlist = (List *) copyObject(window_tlist);
15841578

15851579
foreach(l, activeWindows)
@@ -1603,9 +1597,11 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
16031597
* really have to sort. Even when no explicit sort is needed,
16041598
* we need to have suitable resjunk items added to the input
16051599
* plan's tlist for any partitioning or ordering columns that
1606-
* aren't plain Vars. Furthermore, this way we can use
1607-
* existing infrastructure to identify which input columns are
1608-
* the interesting ones.
1600+
* aren't plain Vars. (In theory, make_windowInputTargetList
1601+
* should have provided all such columns, but let's not assume
1602+
* that here.) Furthermore, this way we can use existing
1603+
* infrastructure to identify which input columns are the
1604+
* interesting ones.
16091605
*/
16101606
if (window_pathkeys)
16111607
{
@@ -3006,18 +3002,57 @@ select_active_windows(PlannerInfo *root, WindowFuncLists *wflists)
30063002
}
30073003

30083004
/*
3009-
* add_volatile_sort_exprs
3010-
* Identify any volatile sort/group expressions used by the active
3011-
* windows, and add them to window_tlist if not already present.
3012-
* Return the modified window_tlist.
3005+
* make_windowInputTargetList
3006+
* Generate appropriate target list for initial input to WindowAgg nodes.
3007+
*
3008+
* When grouping_planner inserts one or more WindowAgg nodes into the plan,
3009+
* this function computes the initial target list to be computed by the node
3010+
* just below the first WindowAgg. This list must contain all values needed
3011+
* to evaluate the window functions, compute the final target list, and
3012+
* perform any required final sort step. If multiple WindowAggs are needed,
3013+
* each intermediate one adds its window function results onto this tlist;
3014+
* only the topmost WindowAgg computes the actual desired target list.
3015+
*
3016+
* This function is much like make_subplanTargetList, though not quite enough
3017+
* like it to share code. As in that function, we flatten most expressions
3018+
* into their component variables. But we do not want to flatten window
3019+
* PARTITION BY/ORDER BY clauses, since that might result in multiple
3020+
* evaluations of them, which would be bad (possibly even resulting in
3021+
* inconsistent answers, if they contain volatile functions). Also, we must
3022+
* not flatten GROUP BY clauses that were left unflattened by
3023+
* make_subplanTargetList, because we may no longer have access to the
3024+
* individual Vars in them.
3025+
*
3026+
* Another key difference from make_subplanTargetList is that we don't flatten
3027+
* Aggref expressions, since those are to be computed below the window
3028+
* functions and just referenced like Vars above that.
3029+
*
3030+
* 'tlist' is the query's final target list.
3031+
* 'activeWindows' is the list of active windows previously identified by
3032+
* select_active_windows.
3033+
*
3034+
* The result is the targetlist to be computed by the plan node immediately
3035+
* below the first WindowAgg node.
30133036
*/
30143037
static List *
3015-
add_volatile_sort_exprs(List *window_tlist, List *tlist, List *activeWindows)
3038+
make_windowInputTargetList(PlannerInfo *root,
3039+
List *tlist,
3040+
List *activeWindows)
30163041
{
3017-
Bitmapset *sgrefs = NULL;
3042+
Query *parse = root->parse;
3043+
Bitmapset *sgrefs;
3044+
List *new_tlist;
3045+
List *flattenable_cols;
3046+
List *flattenable_vars;
30183047
ListCell *lc;
30193048

3020-
/* First, collect the sortgrouprefs of the windows into a bitmapset */
3049+
Assert(parse->hasWindowFuncs);
3050+
3051+
/*
3052+
* Collect the sortgroupref numbers of window PARTITION/ORDER BY clauses
3053+
* into a bitmapset for convenient reference below.
3054+
*/
3055+
sgrefs = NULL;
30213056
foreach(lc, activeWindows)
30223057
{
30233058
WindowClause *wc = (WindowClause *) lfirst(lc);
@@ -3037,34 +3072,74 @@ add_volatile_sort_exprs(List *window_tlist, List *tlist, List *activeWindows)
30373072
}
30383073
}
30393074

3075+
/* Add in sortgroupref numbers of GROUP BY clauses, too */
3076+
foreach(lc, parse->groupClause)
3077+
{
3078+
SortGroupClause *grpcl = (SortGroupClause *) lfirst(lc);
3079+
3080+
sgrefs = bms_add_member(sgrefs, grpcl->tleSortGroupRef);
3081+
}
3082+
30403083
/*
3041-
* Now scan the original tlist to find the referenced expressions. Any
3042-
* that are volatile must be added to window_tlist.
3043-
*
3044-
* Note: we know that the input window_tlist contains no items marked with
3045-
* ressortgrouprefs, so we don't have to worry about collisions of the
3046-
* reference numbers.
3084+
* Construct a tlist containing all the non-flattenable tlist items, and
3085+
* save aside the others for a moment.
30473086
*/
3087+
new_tlist = NIL;
3088+
flattenable_cols = NIL;
3089+
30483090
foreach(lc, tlist)
30493091
{
30503092
TargetEntry *tle = (TargetEntry *) lfirst(lc);
30513093

3094+
/*
3095+
* Don't want to deconstruct window clauses or GROUP BY items. (Note
3096+
* that such items can't contain window functions, so it's okay to
3097+
* compute them below the WindowAgg nodes.)
3098+
*/
30523099
if (tle->ressortgroupref != 0 &&
3053-
bms_is_member(tle->ressortgroupref, sgrefs) &&
3054-
contain_volatile_functions((Node *) tle->expr))
3100+
bms_is_member(tle->ressortgroupref, sgrefs))
30553101
{
3102+
/* Don't want to deconstruct this value, so add to new_tlist */
30563103
TargetEntry *newtle;
30573104

30583105
newtle = makeTargetEntry(tle->expr,
3059-
list_length(window_tlist) + 1,
3106+
list_length(new_tlist) + 1,
30603107
NULL,
30613108
false);
3109+
/* Preserve its sortgroupref marking, in case it's volatile */
30623110
newtle->ressortgroupref = tle->ressortgroupref;
3063-
window_tlist = lappend(window_tlist, newtle);
3111+
new_tlist = lappend(new_tlist, newtle);
3112+
}
3113+
else
3114+
{
3115+
/*
3116+
* Column is to be flattened, so just remember the expression for
3117+
* later call to pull_var_clause. There's no need for
3118+
* pull_var_clause to examine the TargetEntry node itself.
3119+
*/
3120+
flattenable_cols = lappend(flattenable_cols, tle->expr);
30643121
}
30653122
}
30663123

3067-
return window_tlist;
3124+
/*
3125+
* Pull out all the Vars and Aggrefs mentioned in flattenable columns, and
3126+
* add them to the result tlist if not already present. (Some might be
3127+
* there already because they're used directly as window/group clauses.)
3128+
*
3129+
* Note: it's essential to use PVC_INCLUDE_AGGREGATES here, so that the
3130+
* Aggrefs are placed in the Agg node's tlist and not left to be computed
3131+
* at higher levels.
3132+
*/
3133+
flattenable_vars = pull_var_clause((Node *) flattenable_cols,
3134+
PVC_INCLUDE_AGGREGATES,
3135+
PVC_INCLUDE_PLACEHOLDERS);
3136+
new_tlist = add_to_flat_tlist(new_tlist, flattenable_vars);
3137+
3138+
/* clean up cruft */
3139+
list_free(flattenable_vars);
3140+
list_free(flattenable_cols);
3141+
3142+
return new_tlist;
30683143
}
30693144

30703145
/*

src/test/regress/expected/window.out

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,18 @@ group by ten order by ten;
614614
9 | 10040184 | 7
615615
(10 rows)
616616

617+
-- window and aggregate with GROUP BY expression (9.2 bug)
618+
explain (costs off)
619+
select first_value(max(x)) over (), y
620+
from (select unique1 as x, ten+four as y from tenk1) ss
621+
group by y;
622+
QUERY PLAN
623+
-------------------------------
624+
WindowAgg
625+
-> HashAggregate
626+
-> Seq Scan on tenk1
627+
(3 rows)
628+
617629
-- test non-default frame specifications
618630
SELECT four, ten,
619631
sum(ten) over (partition by four order by ten),

src/test/regress/sql/window.sql

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,12 @@ select ten,
145145
from tenk1
146146
group by ten order by ten;
147147

148+
-- window and aggregate with GROUP BY expression (9.2 bug)
149+
explain (costs off)
150+
select first_value(max(x)) over (), y
151+
from (select unique1 as x, ten+four as y from tenk1) ss
152+
group by y;
153+
148154
-- test non-default frame specifications
149155
SELECT four, ten,
150156
sum(ten) over (partition by four order by ten),

0 commit comments

Comments
 (0)