1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-08 11:42:09 +03:00

Support window functions a la SQL:2008.

Hitoshi Harada, with some kibitzing from Heikki and Tom.
This commit is contained in:
Tom Lane
2008-12-28 18:54:01 +00:00
parent 38e9348282
commit 95b07bc7f5
92 changed files with 6720 additions and 321 deletions

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.247 2008/12/18 18:20:33 tgl Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.248 2008/12/28 18:53:56 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -82,6 +82,18 @@ static void locate_grouping_columns(PlannerInfo *root,
List *sub_tlist,
AttrNumber *groupColIdx);
static List *postprocess_setop_tlist(List *new_tlist, List *orig_tlist);
static List *select_active_windows(PlannerInfo *root, WindowFuncLists *wflists);
static List *make_pathkeys_for_window(PlannerInfo *root, WindowClause *wc,
List *tlist, bool canonicalize);
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);
/*****************************************************************************
@ -852,6 +864,8 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
AggClauseCounts agg_counts;
int numGroupCols;
bool use_hashed_grouping = false;
WindowFuncLists *wflists = NULL;
List *activeWindows = NIL;
MemSet(&agg_counts, 0, sizeof(AggClauseCounts));
@ -866,6 +880,22 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
/* Preprocess targetlist */
tlist = preprocess_targetlist(root, tlist);
/*
* Locate any window functions in the tlist. (We don't need to look
* anywhere else, since expressions used in ORDER BY will be in there
* too.) Note that they could all have been eliminated by constant
* folding, in which case we don't need to do any more work.
*/
if (parse->hasWindowFuncs)
{
wflists = find_window_functions((Node *) tlist,
list_length(parse->windowClause));
if (wflists->numWindowFuncs > 0)
activeWindows = select_active_windows(root, wflists);
else
parse->hasWindowFuncs = false;
}
/*
* Generate appropriate target list for subplan; may be different from
* tlist if grouping or aggregation is needed.
@ -890,6 +920,19 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
else
root->group_pathkeys = NIL;
/* We consider only the first (bottom) window in pathkeys logic */
if (activeWindows != NIL)
{
WindowClause *wc = (WindowClause *) linitial(activeWindows);
root->window_pathkeys = make_pathkeys_for_window(root,
wc,
tlist,
false);
}
else
root->window_pathkeys = NIL;
if (parse->distinctClause &&
grouping_is_sortable(parse->distinctClause))
root->distinct_pathkeys =
@ -927,11 +970,12 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
* Figure out whether we want a sorted result from query_planner.
*
* If we have a sortable GROUP BY clause, then we want a result sorted
* properly for grouping. Otherwise, if there's a sortable DISTINCT
* clause that's more rigorous than the ORDER BY clause, we try to
* produce output that's sufficiently well sorted for the DISTINCT.
* Otherwise, if there is an ORDER BY clause, we want to sort by the
* ORDER BY clause.
* properly for grouping. Otherwise, if we have window functions to
* evaluate, we try to sort for the first window. Otherwise, if
* there's a sortable DISTINCT clause that's more rigorous than the
* ORDER BY clause, we try to produce output that's sufficiently well
* sorted for the DISTINCT. Otherwise, if there is an ORDER BY
* clause, we want to sort by the ORDER BY clause.
*
* Note: if we have both ORDER BY and GROUP BY, and ORDER BY is a
* superset of GROUP BY, it would be tempting to request sort by ORDER
@ -942,6 +986,8 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
*/
if (root->group_pathkeys)
root->query_pathkeys = root->group_pathkeys;
else if (root->window_pathkeys)
root->query_pathkeys = root->window_pathkeys;
else if (list_length(root->distinct_pathkeys) >
list_length(root->sort_pathkeys))
root->query_pathkeys = root->distinct_pathkeys;
@ -1092,10 +1138,10 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
*
* Below this point, any tlist eval cost for added-on nodes
* should be accounted for as we create those nodes.
* Presently, of the node types we can add on, only Agg and
* Group project new tlists (the rest just copy their input
* tuples) --- so make_agg() and make_group() are responsible
* for computing the added cost.
* Presently, of the node types we can add on, only Agg,
* WindowAgg, and Group project new tlists (the rest just copy
* their input tuples) --- so make_agg(), make_windowagg() and
* make_group() are responsible for computing the added cost.
*/
cost_qual_eval(&tlist_cost, sub_tlist, root);
result_plan->startup_cost += tlist_cost.startup;
@ -1225,6 +1271,142 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
NULL);
}
} /* end of non-minmax-aggregate case */
/*
* Since each window function could require a different sort order,
* we stack up a WindowAgg node for each window, with sort steps
* between them as needed.
*/
if (activeWindows)
{
List *window_tlist;
ListCell *l;
/*
* If the top-level plan node is one that cannot do expression
* evaluation, we must insert a Result node to project the
* desired tlist. (In some cases this might not really be
* required, but it's not worth trying to avoid it.) Note that
* on second and subsequent passes through the following loop,
* the top-level node will be a WindowAgg which we know can
* project; so we only need to check once.
*/
if (!is_projection_capable_plan(result_plan))
{
result_plan = (Plan *) make_result(root,
NIL,
NULL,
result_plan);
}
/*
* The "base" targetlist for all steps of the windowing process
* is a flat tlist of all Vars and Aggs needed in the result.
* (In some cases we wouldn't need to propagate all of these
* all the way to the top, since they might only be needed as
* inputs to WindowFuncs. It's probably not worth trying to
* optimize that though.) As we climb up the stack, we add
* outputs for the WindowFuncs computed at each level. Also,
* each input tlist has to present all the columns needed to
* sort the data for the next WindowAgg step. That's handled
* internally by make_sort_from_pathkeys, but we need the
* copyObject steps here to ensure that each plan node has
* a separately modifiable tlist.
*/
window_tlist = flatten_tlist(tlist);
if (parse->hasAggs)
window_tlist = add_to_flat_tlist(window_tlist,
pull_agg_clause((Node *) tlist));
result_plan->targetlist = (List *) copyObject(window_tlist);
foreach(l, activeWindows)
{
WindowClause *wc = (WindowClause *) lfirst(l);
List *window_pathkeys;
int partNumCols;
AttrNumber *partColIdx;
Oid *partOperators;
int ordNumCols;
AttrNumber *ordColIdx;
Oid *ordOperators;
window_pathkeys = make_pathkeys_for_window(root,
wc,
tlist,
true);
/*
* This is a bit tricky: we build a sort node even if we don't
* really have to sort. Even when no explicit sort is needed,
* we need to have suitable resjunk items added to the input
* plan's tlist for any partitioning or ordering columns that
* aren't plain Vars. Furthermore, this way we can use
* existing infrastructure to identify which input columns are
* the interesting ones.
*/
if (window_pathkeys)
{
Sort *sort_plan;
sort_plan = make_sort_from_pathkeys(root,
result_plan,
window_pathkeys,
-1.0);
if (!pathkeys_contained_in(window_pathkeys,
current_pathkeys))
{
/* we do indeed need to sort */
result_plan = (Plan *) sort_plan;
current_pathkeys = window_pathkeys;
}
/* In either case, extract the per-column information */
get_column_info_for_window(root, wc, tlist,
sort_plan->numCols,
sort_plan->sortColIdx,
&partNumCols,
&partColIdx,
&partOperators,
&ordNumCols,
&ordColIdx,
&ordOperators);
}
else
{
/* empty window specification, nothing to sort */
partNumCols = 0;
partColIdx = NULL;
partOperators = NULL;
ordNumCols = 0;
ordColIdx = NULL;
ordOperators = NULL;
}
if (lnext(l))
{
/* Add the current WindowFuncs to the running tlist */
window_tlist = add_to_flat_tlist(window_tlist,
wflists->windowFuncs[wc->winref]);
}
else
{
/* Install the original tlist in the topmost WindowAgg */
window_tlist = tlist;
}
/* ... and make the WindowAgg plan node */
result_plan = (Plan *)
make_windowagg(root,
(List *) copyObject(window_tlist),
list_length(wflists->windowFuncs[wc->winref]),
partNumCols,
partColIdx,
partOperators,
ordNumCols,
ordColIdx,
ordOperators,
result_plan);
}
}
} /* end of if (setOperations) */
/*
@ -2030,7 +2212,8 @@ make_subplanTargetList(PlannerInfo *root,
* If we're not grouping or aggregating, there's nothing to do here;
* query_planner should receive the unmodified target list.
*/
if (!parse->hasAggs && !parse->groupClause && !root->hasHavingQual)
if (!parse->hasAggs && !parse->groupClause && !root->hasHavingQual &&
!parse->hasWindowFuncs)
{
*need_tlist_eval = true;
return tlist;
@ -2039,7 +2222,9 @@ make_subplanTargetList(PlannerInfo *root,
/*
* Otherwise, start with a "flattened" tlist (having just the vars
* mentioned in the targetlist and HAVING qual --- but not upper-level
* Vars; they will be replaced by Params later on).
* Vars; they will be replaced by Params later on). Note this includes
* vars used in resjunk items, so we are covering the needs of ORDER BY
* and window specifications.
*/
sub_tlist = flatten_tlist(tlist);
extravars = pull_var_clause(parse->havingQual, true);
@ -2066,7 +2251,7 @@ make_subplanTargetList(PlannerInfo *root,
{
SortGroupClause *grpcl = (SortGroupClause *) lfirst(gl);
Node *groupexpr = get_sortgroupclause_expr(grpcl, tlist);
TargetEntry *te = NULL;
TargetEntry *te;
/*
* Find or make a matching sub_tlist entry. If the groupexpr
@ -2074,20 +2259,10 @@ make_subplanTargetList(PlannerInfo *root,
* won't make multiple groupClause entries for the same TLE.)
*/
if (groupexpr && IsA(groupexpr, Var))
{
ListCell *sl;
te = tlist_member(groupexpr, sub_tlist);
else
te = NULL;
foreach(sl, sub_tlist)
{
TargetEntry *lte = (TargetEntry *) lfirst(sl);
if (equal(groupexpr, lte->expr))
{
te = lte;
break;
}
}
}
if (!te)
{
te = makeTargetEntry((Expr *) groupexpr,
@ -2112,7 +2287,7 @@ make_subplanTargetList(PlannerInfo *root,
*
* This is only needed if we don't use the sub_tlist chosen by
* make_subplanTargetList. We have to forget the column indexes found
* by that routine and re-locate the grouping vars in the real sub_tlist.
* by that routine and re-locate the grouping exprs in the real sub_tlist.
*/
static void
locate_grouping_columns(PlannerInfo *root,
@ -2137,18 +2312,10 @@ locate_grouping_columns(PlannerInfo *root,
{
SortGroupClause *grpcl = (SortGroupClause *) lfirst(gl);
Node *groupexpr = get_sortgroupclause_expr(grpcl, tlist);
TargetEntry *te = NULL;
ListCell *sl;
TargetEntry *te = tlist_member(groupexpr, sub_tlist);
foreach(sl, sub_tlist)
{
te = (TargetEntry *) lfirst(sl);
if (equal(groupexpr, te->expr))
break;
}
if (!sl)
if (!te)
elog(ERROR, "failed to locate grouping columns");
groupColIdx[keyno++] = te->resno;
}
}
@ -2190,3 +2357,219 @@ postprocess_setop_tlist(List *new_tlist, List *orig_tlist)
elog(ERROR, "resjunk output columns are not implemented");
return new_tlist;
}
/*
* select_active_windows
* Create a list of the "active" window clauses (ie, those referenced
* by non-deleted WindowFuncs) in the order they are to be executed.
*/
static List *
select_active_windows(PlannerInfo *root, WindowFuncLists *wflists)
{
List *result;
List *actives;
ListCell *lc;
/* First, make a list of the active windows */
actives = NIL;
foreach(lc, root->parse->windowClause)
{
WindowClause *wc = (WindowClause *) lfirst(lc);
/* It's only active if wflists shows some related WindowFuncs */
Assert(wc->winref <= wflists->maxWinRef);
if (wflists->windowFuncs[wc->winref] != NIL)
actives = lappend(actives, wc);
}
/*
* Now, ensure that windows with identical partitioning/ordering clauses
* are adjacent in the list. This is required by the SQL standard, which
* says that only one sort is to be used for such windows, even if they
* are otherwise distinct (eg, different names or framing clauses).
*
* There is room to be much smarter here, for example detecting whether
* one window's sort keys are a prefix of another's (so that sorting
* for the latter would do for the former), or putting windows first
* that match a sort order available for the underlying query. For the
* moment we are content with meeting the spec.
*/
result = NIL;
while (actives != NIL)
{
WindowClause *wc = (WindowClause *) linitial(actives);
ListCell *prev;
ListCell *next;
/* Move wc from actives to result */
actives = list_delete_first(actives);
result = lappend(result, wc);
/* Now move any matching windows from actives to result */
prev = NULL;
for (lc = list_head(actives); lc; lc = next)
{
WindowClause *wc2 = (WindowClause *) lfirst(lc);
next = lnext(lc);
if (equal(wc->partitionClause, wc2->partitionClause) &&
equal(wc->orderClause, wc2->orderClause))
{
actives = list_delete_cell(actives, lc, prev);
result = lappend(result, wc2);
}
else
prev = lc;
}
}
return result;
}
/*
* make_pathkeys_for_window
* Create a pathkeys list describing the required input ordering
* for the given WindowClause.
*
* 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.
*/
static List *
make_pathkeys_for_window(PlannerInfo *root, WindowClause *wc,
List *tlist, bool canonicalize)
{
List *window_pathkeys;
List *window_sortclauses;
/* Throw error if can't sort */
if (!grouping_is_sortable(wc->partitionClause))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("could not implement window PARTITION BY"),
errdetail("Window partitioning columns must be of sortable datatypes.")));
if (!grouping_is_sortable(wc->orderClause))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("could not implement window ORDER BY"),
errdetail("Window ordering columns must be of sortable datatypes.")));
/* Okay, make the combined pathkeys */
window_sortclauses = list_concat(list_copy(wc->partitionClause),
list_copy(wc->orderClause));
window_pathkeys = make_pathkeys_for_sortclauses(root,
window_sortclauses,
tlist,
canonicalize);
list_free(window_sortclauses);
return window_pathkeys;
}
/*----------
* get_column_info_for_window
* Get the partitioning/ordering column numbers and equality operators
* for a WindowAgg node.
*
* This depends on the behavior of 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,
true);
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,
true);
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");
}
}