mirror of
https://github.com/postgres/postgres.git
synced 2025-07-07 00:36:50 +03:00
Fix bugs with degenerate window ORDER BY clauses in GROUPS/RANGE mode.
nodeWindowAgg.c failed to cope with the possibility that no ordering columns are defined in the window frame for GROUPS mode or RANGE OFFSET mode, leading to assertion failures or odd errors, as reported by Masahiko Sawada and Lukas Eder. In RANGE OFFSET mode, an ordering column is really required, so add an Assert about that. In GROUPS mode, the code would work, except that the node initialization code wasn't in sync with the execution code about when to set up tuplestore read pointers and spare slots. Fix the latter for consistency's sake (even though I think the changes described below make the out-of-sync cases unreachable for now). Per SQL spec, a single ordering column is required for RANGE OFFSET mode, and at least one ordering column is required for GROUPS mode. The parser enforced the former but not the latter; add a check for that. We were able to reach the no-ordering-column cases even with fully spec compliant queries, though, because the planner would drop partitioning and ordering columns from the generated plan if they were redundant with earlier columns according to the redundant-pathkey logic, for instance "PARTITION BY x ORDER BY y" in the presence of a "WHERE x=y" qual. While in principle that's an optimization that could save some pointless comparisons at runtime, it seems unlikely to be meaningful in the real world. I think this behavior was not so much an intentional optimization as a side-effect of an ancient decision to construct the plan node's ordering-column info by reverse-engineering the PathKeys of the input path. If we give up redundant-column removal then it takes very little code to generate the plan node info directly from the WindowClause, ensuring that we have the expected number of ordering columns in all cases. (If anyone does complain about this, the planner could perhaps be taught to remove redundant columns only when it's safe to do so, ie *not* in RANGE OFFSET mode. But I doubt anyone ever will.) With these changes, the WindowAggPath.winpathkeys field is not used for anything anymore, so remove it. The test cases added here are not actually very interesting given the removal of the redundant-column-removal logic, but they would represent important corner cases if anyone ever tries to put that back. Tom Lane and Masahiko Sawada. Back-patch to v11 where RANGE OFFSET and GROUPS modes were added. Discussion: https://postgr.es/m/CAD21AoDrWqycq-w_+Bx1cjc+YUhZ11XTj9rfxNiNDojjBx8Fjw@mail.gmail.com Discussion: https://postgr.es/m/153086788677.17476.8002640580496698831@wrigleys.postgresql.org
This commit is contained in:
@ -107,15 +107,6 @@ static WindowAgg *create_windowagg_plan(PlannerInfo *root, WindowAggPath *best_p
|
||||
static SetOp *create_setop_plan(PlannerInfo *root, SetOpPath *best_path,
|
||||
int flags);
|
||||
static RecursiveUnion *create_recursiveunion_plan(PlannerInfo *root, RecursiveUnionPath *best_path);
|
||||
static void get_column_info_for_window(PlannerInfo *root, WindowClause *wc,
|
||||
List *tlist,
|
||||
int numSortCols, AttrNumber *sortColIdx,
|
||||
int *partNumCols,
|
||||
AttrNumber **partColIdx,
|
||||
Oid **partOperators,
|
||||
int *ordNumCols,
|
||||
AttrNumber **ordColIdx,
|
||||
Oid **ordOperators);
|
||||
static LockRows *create_lockrows_plan(PlannerInfo *root, LockRowsPath *best_path,
|
||||
int flags);
|
||||
static ModifyTable *create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path);
|
||||
@ -2160,19 +2151,17 @@ create_windowagg_plan(PlannerInfo *root, WindowAggPath *best_path)
|
||||
{
|
||||
WindowAgg *plan;
|
||||
WindowClause *wc = best_path->winclause;
|
||||
int numPart = list_length(wc->partitionClause);
|
||||
int numOrder = list_length(wc->orderClause);
|
||||
Plan *subplan;
|
||||
List *tlist;
|
||||
int numsortkeys;
|
||||
AttrNumber *sortColIdx;
|
||||
Oid *sortOperators;
|
||||
Oid *collations;
|
||||
bool *nullsFirst;
|
||||
int partNumCols;
|
||||
AttrNumber *partColIdx;
|
||||
Oid *partOperators;
|
||||
int ordNumCols;
|
||||
AttrNumber *ordColIdx;
|
||||
Oid *ordOperators;
|
||||
ListCell *lc;
|
||||
|
||||
/*
|
||||
* WindowAgg can project, so no need to be terribly picky about child
|
||||
@ -2183,32 +2172,43 @@ create_windowagg_plan(PlannerInfo *root, WindowAggPath *best_path)
|
||||
tlist = build_path_tlist(root, &best_path->path);
|
||||
|
||||
/*
|
||||
* We shouldn't need to actually sort, but it's convenient to use
|
||||
* prepare_sort_from_pathkeys to identify the input's sort columns.
|
||||
* Convert SortGroupClause lists into arrays of attr indexes and equality
|
||||
* operators, as wanted by executor. (Note: in principle, it's possible
|
||||
* to drop some of the sort columns, if they were proved redundant by
|
||||
* pathkey logic. However, it doesn't seem worth going out of our way to
|
||||
* optimize such cases. In any case, we must *not* remove the ordering
|
||||
* column for RANGE OFFSET cases, as the executor needs that for in_range
|
||||
* tests even if it's known to be equal to some partitioning column.)
|
||||
*/
|
||||
subplan = prepare_sort_from_pathkeys(subplan,
|
||||
best_path->winpathkeys,
|
||||
NULL,
|
||||
NULL,
|
||||
false,
|
||||
&numsortkeys,
|
||||
&sortColIdx,
|
||||
&sortOperators,
|
||||
&collations,
|
||||
&nullsFirst);
|
||||
partColIdx = (AttrNumber *) palloc(sizeof(AttrNumber) * numPart);
|
||||
partOperators = (Oid *) palloc(sizeof(Oid) * numPart);
|
||||
|
||||
/* Now deconstruct that into partition and ordering portions */
|
||||
get_column_info_for_window(root,
|
||||
wc,
|
||||
subplan->targetlist,
|
||||
numsortkeys,
|
||||
sortColIdx,
|
||||
&partNumCols,
|
||||
&partColIdx,
|
||||
&partOperators,
|
||||
&ordNumCols,
|
||||
&ordColIdx,
|
||||
&ordOperators);
|
||||
partNumCols = 0;
|
||||
foreach(lc, wc->partitionClause)
|
||||
{
|
||||
SortGroupClause *sgc = (SortGroupClause *) lfirst(lc);
|
||||
TargetEntry *tle = get_sortgroupclause_tle(sgc, subplan->targetlist);
|
||||
|
||||
Assert(OidIsValid(sgc->eqop));
|
||||
partColIdx[partNumCols] = tle->resno;
|
||||
partOperators[partNumCols] = sgc->eqop;
|
||||
partNumCols++;
|
||||
}
|
||||
|
||||
ordColIdx = (AttrNumber *) palloc(sizeof(AttrNumber) * numOrder);
|
||||
ordOperators = (Oid *) palloc(sizeof(Oid) * numOrder);
|
||||
|
||||
ordNumCols = 0;
|
||||
foreach(lc, wc->orderClause)
|
||||
{
|
||||
SortGroupClause *sgc = (SortGroupClause *) lfirst(lc);
|
||||
TargetEntry *tle = get_sortgroupclause_tle(sgc, subplan->targetlist);
|
||||
|
||||
Assert(OidIsValid(sgc->eqop));
|
||||
ordColIdx[ordNumCols] = tle->resno;
|
||||
ordOperators[ordNumCols] = sgc->eqop;
|
||||
ordNumCols++;
|
||||
}
|
||||
|
||||
/* And finally we can make the WindowAgg node */
|
||||
plan = make_windowagg(tlist,
|
||||
@ -2234,112 +2234,6 @@ create_windowagg_plan(PlannerInfo *root, WindowAggPath *best_path)
|
||||
return plan;
|
||||
}
|
||||
|
||||
/*
|
||||
* get_column_info_for_window
|
||||
* Get the partitioning/ordering column numbers and equality operators
|
||||
* for a WindowAgg node.
|
||||
*
|
||||
* This depends on the behavior of planner.c's make_pathkeys_for_window!
|
||||
*
|
||||
* We are given the target WindowClause and an array of the input column
|
||||
* numbers associated with the resulting pathkeys. In the easy case, there
|
||||
* are the same number of pathkey columns as partitioning + ordering columns
|
||||
* and we just have to copy some data around. However, it's possible that
|
||||
* some of the original partitioning + ordering columns were eliminated as
|
||||
* redundant during the transformation to pathkeys. (This can happen even
|
||||
* though the parser gets rid of obvious duplicates. A typical scenario is a
|
||||
* window specification "PARTITION BY x ORDER BY y" coupled with a clause
|
||||
* "WHERE x = y" that causes the two sort columns to be recognized as
|
||||
* redundant.) In that unusual case, we have to work a lot harder to
|
||||
* determine which keys are significant.
|
||||
*
|
||||
* The method used here is a bit brute-force: add the sort columns to a list
|
||||
* one at a time and note when the resulting pathkey list gets longer. But
|
||||
* it's a sufficiently uncommon case that a faster way doesn't seem worth
|
||||
* the amount of code refactoring that'd be needed.
|
||||
*/
|
||||
static void
|
||||
get_column_info_for_window(PlannerInfo *root, WindowClause *wc, List *tlist,
|
||||
int numSortCols, AttrNumber *sortColIdx,
|
||||
int *partNumCols,
|
||||
AttrNumber **partColIdx,
|
||||
Oid **partOperators,
|
||||
int *ordNumCols,
|
||||
AttrNumber **ordColIdx,
|
||||
Oid **ordOperators)
|
||||
{
|
||||
int numPart = list_length(wc->partitionClause);
|
||||
int numOrder = list_length(wc->orderClause);
|
||||
|
||||
if (numSortCols == numPart + numOrder)
|
||||
{
|
||||
/* easy case */
|
||||
*partNumCols = numPart;
|
||||
*partColIdx = sortColIdx;
|
||||
*partOperators = extract_grouping_ops(wc->partitionClause);
|
||||
*ordNumCols = numOrder;
|
||||
*ordColIdx = sortColIdx + numPart;
|
||||
*ordOperators = extract_grouping_ops(wc->orderClause);
|
||||
}
|
||||
else
|
||||
{
|
||||
List *sortclauses;
|
||||
List *pathkeys;
|
||||
int scidx;
|
||||
ListCell *lc;
|
||||
|
||||
/* first, allocate what's certainly enough space for the arrays */
|
||||
*partNumCols = 0;
|
||||
*partColIdx = (AttrNumber *) palloc(numPart * sizeof(AttrNumber));
|
||||
*partOperators = (Oid *) palloc(numPart * sizeof(Oid));
|
||||
*ordNumCols = 0;
|
||||
*ordColIdx = (AttrNumber *) palloc(numOrder * sizeof(AttrNumber));
|
||||
*ordOperators = (Oid *) palloc(numOrder * sizeof(Oid));
|
||||
sortclauses = NIL;
|
||||
pathkeys = NIL;
|
||||
scidx = 0;
|
||||
foreach(lc, wc->partitionClause)
|
||||
{
|
||||
SortGroupClause *sgc = (SortGroupClause *) lfirst(lc);
|
||||
List *new_pathkeys;
|
||||
|
||||
sortclauses = lappend(sortclauses, sgc);
|
||||
new_pathkeys = make_pathkeys_for_sortclauses(root,
|
||||
sortclauses,
|
||||
tlist);
|
||||
if (list_length(new_pathkeys) > list_length(pathkeys))
|
||||
{
|
||||
/* this sort clause is actually significant */
|
||||
(*partColIdx)[*partNumCols] = sortColIdx[scidx++];
|
||||
(*partOperators)[*partNumCols] = sgc->eqop;
|
||||
(*partNumCols)++;
|
||||
pathkeys = new_pathkeys;
|
||||
}
|
||||
}
|
||||
foreach(lc, wc->orderClause)
|
||||
{
|
||||
SortGroupClause *sgc = (SortGroupClause *) lfirst(lc);
|
||||
List *new_pathkeys;
|
||||
|
||||
sortclauses = lappend(sortclauses, sgc);
|
||||
new_pathkeys = make_pathkeys_for_sortclauses(root,
|
||||
sortclauses,
|
||||
tlist);
|
||||
if (list_length(new_pathkeys) > list_length(pathkeys))
|
||||
{
|
||||
/* this sort clause is actually significant */
|
||||
(*ordColIdx)[*ordNumCols] = sortColIdx[scidx++];
|
||||
(*ordOperators)[*ordNumCols] = sgc->eqop;
|
||||
(*ordNumCols)++;
|
||||
pathkeys = new_pathkeys;
|
||||
}
|
||||
}
|
||||
/* complain if we didn't eat exactly the right number of sort cols */
|
||||
if (scidx != numSortCols)
|
||||
elog(ERROR, "failed to deconstruct sort operators into partitioning/ordering operators");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* create_setop_plan
|
||||
*
|
||||
|
@ -4604,8 +4604,7 @@ create_one_window_path(PlannerInfo *root,
|
||||
path = (Path *)
|
||||
create_windowagg_path(root, window_rel, path, window_target,
|
||||
wflists->windowFuncs[wc->winref],
|
||||
wc,
|
||||
window_pathkeys);
|
||||
wc);
|
||||
}
|
||||
|
||||
add_path(window_rel, path);
|
||||
@ -5466,8 +5465,6 @@ make_window_input_target(PlannerInfo *root,
|
||||
* The required ordering is first the PARTITION keys, then the ORDER keys.
|
||||
* In the future we might try to implement windowing using hashing, in which
|
||||
* case the ordering could be relaxed, but for now we always sort.
|
||||
*
|
||||
* Caution: if you change this, see createplan.c's get_column_info_for_window!
|
||||
*/
|
||||
static List *
|
||||
make_pathkeys_for_window(PlannerInfo *root, WindowClause *wc,
|
||||
|
@ -3072,10 +3072,9 @@ create_minmaxagg_path(PlannerInfo *root,
|
||||
* 'target' is the PathTarget to be computed
|
||||
* 'windowFuncs' is a list of WindowFunc structs
|
||||
* 'winclause' is a WindowClause that is common to all the WindowFuncs
|
||||
* 'winpathkeys' is the pathkeys for the PARTITION keys + ORDER keys
|
||||
*
|
||||
* The actual sort order of the input must match winpathkeys, but might
|
||||
* have additional keys after those.
|
||||
* The input must be sorted according to the WindowClause's PARTITION keys
|
||||
* plus ORDER BY keys.
|
||||
*/
|
||||
WindowAggPath *
|
||||
create_windowagg_path(PlannerInfo *root,
|
||||
@ -3083,8 +3082,7 @@ create_windowagg_path(PlannerInfo *root,
|
||||
Path *subpath,
|
||||
PathTarget *target,
|
||||
List *windowFuncs,
|
||||
WindowClause *winclause,
|
||||
List *winpathkeys)
|
||||
WindowClause *winclause)
|
||||
{
|
||||
WindowAggPath *pathnode = makeNode(WindowAggPath);
|
||||
|
||||
@ -3102,7 +3100,6 @@ create_windowagg_path(PlannerInfo *root,
|
||||
|
||||
pathnode->subpath = subpath;
|
||||
pathnode->winclause = winclause;
|
||||
pathnode->winpathkeys = winpathkeys;
|
||||
|
||||
/*
|
||||
* For costing purposes, assume that there are no redundant partitioning
|
||||
|
Reference in New Issue
Block a user