Extend the set of frame options supported for window functions.

This patch allows the frame to start from CURRENT ROW (in either RANGE or
ROWS mode), and it also adds support for ROWS n PRECEDING and ROWS n FOLLOWING
start and end points.  (RANGE value PRECEDING/FOLLOWING isn't there yet ---
the grammar works, but that's all.)

Hitoshi Harada, reviewed by Pavel Stehule
This commit is contained in:
Tom Lane 2010-02-12 17:33:21 +00:00
parent a5348fafd1
commit ec4be2ee68
28 changed files with 1237 additions and 254 deletions

View File

@ -1,4 +1,4 @@
<!-- $PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.501 2010/02/07 20:48:09 tgl Exp $ -->
<!-- $PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.502 2010/02/12 17:33:19 tgl Exp $ -->
<chapter id="functions">
<title>Functions and Operators</title>
@ -10559,21 +10559,23 @@ SELECT xmlagg(x) FROM (SELECT x FROM test ORDER BY y DESC) AS tab;
<function>nth_value</> consider only the rows within the <quote>window
frame</>, which by default contains the rows from the start of the
partition through the last peer of the current row. This is
likely to give unhelpful results for <function>nth_value</> and
particularly <function>last_value</>. You can redefine the frame as
being the whole partition by adding <literal>ROWS BETWEEN UNBOUNDED
PRECEDING AND UNBOUNDED FOLLOWING</> to the <literal>OVER</> clause.
See <xref linkend="syntax-window-functions"> for more information.
likely to give unhelpful results for <function>last_value</> and
sometimes also <function>nth_value</>. You can redefine the frame by
adding a suitable frame specification (<literal>RANGE</> or
<literal>ROWS</>) to the <literal>OVER</> clause.
See <xref linkend="syntax-window-functions"> for more information
about frame specifications.
</para>
<para>
When an aggregate function is used as a window function, it aggregates
over the rows within the current row's window frame. To obtain
aggregation over the whole partition, omit <literal>ORDER BY</> or use
<literal>ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING</>.
over the rows within the current row's window frame.
An aggregate used with <literal>ORDER BY</> and the default window frame
definition produces a <quote>running sum</> type of behavior, which may or
may not be what's wanted.
may not be what's wanted. To obtain
aggregation over the whole partition, omit <literal>ORDER BY</> or use
<literal>ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING</>.
Other frame specifications can be used to obtain other effects.
</para>
<note>

View File

@ -1,5 +1,5 @@
<!--
$PostgreSQL: pgsql/doc/src/sgml/ref/select.sgml,v 1.128 2009/10/28 14:55:37 tgl Exp $
$PostgreSQL: pgsql/doc/src/sgml/ref/select.sgml,v 1.129 2010/02/12 17:33:19 tgl Exp $
PostgreSQL documentation
-->
@ -616,27 +616,66 @@ WINDOW <replaceable class="parameter">window_name</replaceable> AS ( <replaceabl
<para>
The optional <replaceable class="parameter">frame_clause</> defines
the <firstterm>window frame</> for window functions that depend on the
frame (not all do). It can be one of
frame (not all do). The window frame is a set of related rows for
each row of the query (called the <firstterm>current row</>).
The <replaceable class="parameter">frame_clause</> can be one of
<synopsis>
RANGE UNBOUNDED PRECEDING
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
ROWS UNBOUNDED PRECEDING
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
[ RANGE | ROWS ] <replaceable>frame_start</>
[ RANGE | ROWS ] BETWEEN <replaceable>frame_start</> AND <replaceable>frame_end</>
</synopsis>
The first two are equivalent and are also the default: they set the
frame to be all rows from the partition start up through the current row's
last peer in the <literal>ORDER BY</> ordering (which means all rows if
there is no <literal>ORDER BY</>). The options
<literal>RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING</> and
<literal>ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING</>
are also equivalent: they always select all rows in the partition.
Lastly, <literal>ROWS UNBOUNDED PRECEDING</> or its verbose equivalent
<literal>ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW</> select
all rows up through the current row (regardless of duplicates).
Beware that this option can produce implementation-dependent results
if the <literal>ORDER BY</> ordering does not order the rows uniquely.
where <replaceable>frame_start</> and <replaceable>frame_end</> can be
one of
<synopsis>
UNBOUNDED PRECEDING
<replaceable>value</replaceable> PRECEDING
CURRENT ROW
<replaceable>value</replaceable> FOLLOWING
UNBOUNDED FOLLOWING
</synopsis>
If <replaceable>frame_end</> is omitted it defaults to <literal>CURRENT
ROW</>. Restrictions are that
<replaceable>frame_start</> cannot be <literal>UNBOUNDED FOLLOWING</>,
<replaceable>frame_end</> cannot be <literal>UNBOUNDED PRECEDING</>,
and the <replaceable>frame_end</> choice cannot appear earlier in the
above list than the <replaceable>frame_start</> choice &mdash; for example
<literal>RANGE BETWEEN CURRENT ROW AND <replaceable>value</>
PRECEDING</literal> is not allowed.
</para>
<para>
The default framing option is <literal>RANGE UNBOUNDED PRECEDING</>,
which is the same as <literal>RANGE BETWEEN UNBOUNDED PRECEDING AND
CURRENT ROW</>; it sets the frame to be all rows from the partition start
up through the current row's last peer in the <literal>ORDER BY</>
ordering (which means all rows if there is no <literal>ORDER BY</>).
In general, <literal>UNBOUNDED PRECEDING</> means that the frame
starts with the first row of the partition, and similarly
<literal>UNBOUNDED FOLLOWING</> means that the frame ends with the last
row of the partition (regardless of <literal>RANGE</> or <literal>ROWS</>
mode). In <literal>ROWS</> mode, <literal>CURRENT ROW</>
means that the frame starts or ends with the current row; but in
<literal>RANGE</> mode it means that the frame starts or ends with
the current row's first or last peer in the <literal>ORDER BY</> ordering.
The <replaceable>value</> <literal>PRECEDING</> and
<replaceable>value</> <literal>FOLLOWING</> cases are currently only
allowed in <literal>ROWS</> mode. They indicate that the frame starts
or ends with the row that many rows before or after the current row.
<replaceable>value</replaceable> must be an integer expression not
containing any variables, aggregate functions, or window functions.
The value must not be null or negative; but it can be zero, which
selects the current row itself.
</para>
<para>
Beware that the <literal>ROWS</> options can produce unpredictable
results if the <literal>ORDER BY</> ordering does not order the rows
uniquely. The <literal>RANGE</> options are designed to ensure that
rows that are peers in the <literal>ORDER BY</> ordering are treated
alike; any two peer rows will be both in or both not in the frame.
</para>
<para>

View File

@ -1,4 +1,4 @@
<!-- $PostgreSQL: pgsql/doc/src/sgml/syntax.sgml,v 1.141 2010/02/04 00:19:28 tgl Exp $ -->
<!-- $PostgreSQL: pgsql/doc/src/sgml/syntax.sgml,v 1.142 2010/02/12 17:33:19 tgl Exp $ -->
<chapter id="sql-syntax">
<title>SQL Syntax</title>
@ -1667,14 +1667,21 @@ SELECT array_agg(a ORDER BY b DESC) FROM table;
and the optional <replaceable class="parameter">frame_clause</replaceable>
can be one of
<synopsis>
RANGE UNBOUNDED PRECEDING
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
ROWS UNBOUNDED PRECEDING
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
[ RANGE | ROWS ] <replaceable>frame_start</>
[ RANGE | ROWS ] BETWEEN <replaceable>frame_start</> AND <replaceable>frame_end</>
</synopsis>
where <replaceable>frame_start</> and <replaceable>frame_end</> can be
one of
<synopsis>
UNBOUNDED PRECEDING
<replaceable>value</replaceable> PRECEDING
CURRENT ROW
<replaceable>value</replaceable> FOLLOWING
UNBOUNDED FOLLOWING
</synopsis>
</para>
<para>
Here, <replaceable>expression</replaceable> represents any value
expression that does not itself contain window function calls.
The <literal>PARTITION BY</> and <literal>ORDER BY</> lists have
@ -1699,19 +1706,35 @@ ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
The <replaceable class="parameter">frame_clause</replaceable> specifies
the set of rows constituting the <firstterm>window frame</>, for those
window functions that act on the frame instead of the whole partition.
If <replaceable>frame_end</> is omitted it defaults to <literal>CURRENT
ROW</>. Restrictions are that
<replaceable>frame_start</> cannot be <literal>UNBOUNDED FOLLOWING</>,
<replaceable>frame_end</> cannot be <literal>UNBOUNDED PRECEDING</>,
and the <replaceable>frame_end</> choice cannot appear earlier in the
above list than the <replaceable>frame_start</> choice &mdash; for example
<literal>RANGE BETWEEN CURRENT ROW AND <replaceable>value</>
PRECEDING</literal> is not allowed.
The default framing option is <literal>RANGE UNBOUNDED PRECEDING</>,
which is the same as <literal>RANGE BETWEEN UNBOUNDED PRECEDING AND
CURRENT ROW</>; it selects rows up through the current row's last
peer in the <literal>ORDER BY</> ordering (which means all rows if
there is no <literal>ORDER BY</>). The options
<literal>RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING</> and
<literal>ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING</>
are also equivalent: they always select all rows in the partition.
Lastly, <literal>ROWS UNBOUNDED PRECEDING</> or its verbose equivalent
<literal>ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW</> select
all rows up through the current row (regardless of duplicates).
Beware that this option can produce implementation-dependent results
if the <literal>ORDER BY</> ordering does not order the rows uniquely.
CURRENT ROW</>; it sets the frame to be all rows from the partition start
up through the current row's last peer in the <literal>ORDER BY</>
ordering (which means all rows if there is no <literal>ORDER BY</>).
In general, <literal>UNBOUNDED PRECEDING</> means that the frame
starts with the first row of the partition, and similarly
<literal>UNBOUNDED FOLLOWING</> means that the frame ends with the last
row of the partition (regardless of <literal>RANGE</> or <literal>ROWS</>
mode). In <literal>ROWS</> mode, <literal>CURRENT ROW</>
means that the frame starts or ends with the current row; but in
<literal>RANGE</> mode it means that the frame starts or ends with
the current row's first or last peer in the <literal>ORDER BY</> ordering.
The <replaceable>value</> <literal>PRECEDING</> and
<replaceable>value</> <literal>FOLLOWING</> cases are currently only
allowed in <literal>ROWS</> mode. They indicate that the frame starts
or ends with the row that many rows before or after the current row.
<replaceable>value</replaceable> must be an integer expression not
containing any variables, aggregate functions, or window functions.
The value must not be null or negative; but it can be zero, which
selects the current row itself.
</para>
<para>

View File

@ -71,7 +71,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/executor/nodeAgg.c,v 1.172 2010/02/08 20:39:51 tgl Exp $
* $PostgreSQL: pgsql/src/backend/executor/nodeAgg.c,v 1.173 2010/02/12 17:33:19 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -1999,7 +1999,7 @@ AggCheckCallContext(FunctionCallInfo fcinfo, MemoryContext *aggcontext)
if (fcinfo->context && IsA(fcinfo->context, WindowAggState))
{
if (aggcontext)
*aggcontext = ((WindowAggState *) fcinfo->context)->wincontext;
*aggcontext = ((WindowAggState *) fcinfo->context)->aggcontext;
return AGG_CONTEXT_WINDOW;
}

View File

@ -27,7 +27,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/executor/nodeWindowAgg.c,v 1.9 2010/01/02 16:57:45 momjian Exp $
* $PostgreSQL: pgsql/src/backend/executor/nodeWindowAgg.c,v 1.10 2010/02/12 17:33:19 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -165,6 +165,7 @@ static void release_partition(WindowAggState *winstate);
static bool row_is_in_frame(WindowAggState *winstate, int64 pos,
TupleTableSlot *slot);
static void update_frameheadpos(WindowObject winobj, TupleTableSlot *slot);
static void update_frametailpos(WindowObject winobj, TupleTableSlot *slot);
static WindowStatePerAggData *initialize_peragg(WindowAggState *winstate,
@ -193,7 +194,7 @@ initialize_windowaggregate(WindowAggState *winstate,
peraggstate->transValue = peraggstate->initValue;
else
{
oldContext = MemoryContextSwitchTo(winstate->wincontext);
oldContext = MemoryContextSwitchTo(winstate->aggcontext);
peraggstate->transValue = datumCopy(peraggstate->initValue,
peraggstate->transtypeByVal,
peraggstate->transtypeLen);
@ -258,10 +259,10 @@ advance_windowaggregate(WindowAggState *winstate,
* already checked that the agg's input type is binary-compatible
* with its transtype, so straight copy here is OK.)
*
* We must copy the datum into wincontext if it is pass-by-ref. We
* We must copy the datum into aggcontext if it is pass-by-ref. We
* do not need to pfree the old transValue, since it's NULL.
*/
MemoryContextSwitchTo(winstate->wincontext);
MemoryContextSwitchTo(winstate->aggcontext);
peraggstate->transValue = datumCopy(fcinfo->arg[1],
peraggstate->transtypeByVal,
peraggstate->transtypeLen);
@ -294,7 +295,7 @@ advance_windowaggregate(WindowAggState *winstate,
newVal = FunctionCallInvoke(fcinfo);
/*
* If pass-by-ref datatype, must copy the new value into wincontext and
* If pass-by-ref datatype, must copy the new value into aggcontext and
* pfree the prior transValue. But if transfn returned a pointer to its
* first input, we don't need to do anything.
*/
@ -303,7 +304,7 @@ advance_windowaggregate(WindowAggState *winstate,
{
if (!fcinfo->isnull)
{
MemoryContextSwitchTo(winstate->wincontext);
MemoryContextSwitchTo(winstate->aggcontext);
newVal = datumCopy(newVal,
peraggstate->transtypeByVal,
peraggstate->transtypeLen);
@ -390,6 +391,7 @@ eval_windowaggregates(WindowAggState *winstate)
int i;
MemoryContext oldContext;
ExprContext *econtext;
WindowObject agg_winobj;
TupleTableSlot *agg_row_slot;
numaggs = winstate->numaggs;
@ -398,10 +400,14 @@ eval_windowaggregates(WindowAggState *winstate)
/* final output execution is in ps_ExprContext */
econtext = winstate->ss.ps.ps_ExprContext;
agg_winobj = winstate->agg_winobj;
agg_row_slot = winstate->agg_row_slot;
/*
* Currently, we support only a subset of the SQL-standard window framing
* rules. In all the supported cases, the window frame always consists of
* rules.
*
* If the frame start is UNBOUNDED_PRECEDING, the window frame consists of
* a contiguous group of rows extending forward from the start of the
* partition, and rows only enter the frame, never exit it, as the current
* row advances forward. This makes it possible to use an incremental
@ -413,6 +419,10 @@ eval_windowaggregates(WindowAggState *winstate)
* damage the running transition value, but we have the same assumption
* in nodeAgg.c too (when it rescans an existing hash table).
*
* For other frame start rules, we discard the aggregate state and re-run
* the aggregates whenever the frame head row moves. We can still
* optimize as above whenever successive rows share the same frame head.
*
* In many common cases, multiple rows share the same frame and hence the
* same aggregate value. (In particular, if there's no ORDER BY in a RANGE
* window, then all rows are peers and so they all have window frame equal
@ -424,21 +434,65 @@ eval_windowaggregates(WindowAggState *winstate)
* accumulated into the aggregate transition values. Whenever we start a
* new peer group, we accumulate forward to the end of the peer group.
*
* TODO: In the future, we should implement the full SQL-standard set of
* framing rules. We could implement the other cases by recalculating the
* aggregates whenever a row exits the frame. That would be pretty slow,
* though. For aggregates like SUM and COUNT we could implement a
* "negative transition function" that would be called for each row as it
* exits the frame. We'd have to think about avoiding recalculation of
* volatile arguments of aggregate functions, too.
* TODO: Rerunning aggregates from the frame start can be pretty slow.
* For some aggregates like SUM and COUNT we could avoid that by
* implementing a "negative transition function" that would be called for
* each row as it exits the frame. We'd have to think about avoiding
* recalculation of volatile arguments of aggregate functions, too.
*/
/*
* If we've already aggregated up through current row, reuse the saved
* result values. NOTE: this test works for the currently supported
* framing rules, but will need fixing when more are added.
* First, update the frame head position.
*/
if (winstate->aggregatedupto > winstate->currentpos)
update_frameheadpos(agg_winobj, winstate->temp_slot_1);
/*
* Initialize aggregates on first call for partition, or if the frame
* head position moved since last time.
*/
if (winstate->currentpos == 0 ||
winstate->frameheadpos != winstate->aggregatedbase)
{
/*
* Discard transient aggregate values
*/
MemoryContextResetAndDeleteChildren(winstate->aggcontext);
for (i = 0; i < numaggs; i++)
{
peraggstate = &winstate->peragg[i];
wfuncno = peraggstate->wfuncno;
initialize_windowaggregate(winstate,
&winstate->perfunc[wfuncno],
peraggstate);
}
/*
* If we created a mark pointer for aggregates, keep it pushed up
* to frame head, so that tuplestore can discard unnecessary rows.
*/
if (agg_winobj->markptr >= 0)
WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
/*
* Initialize for loop below
*/
ExecClearTuple(agg_row_slot);
winstate->aggregatedbase = winstate->frameheadpos;
winstate->aggregatedupto = winstate->frameheadpos;
}
/*
* In UNBOUNDED_FOLLOWING mode, we don't have to recalculate aggregates
* except when the frame head moves. In END_CURRENT_ROW mode, we only
* have to recalculate when the frame head moves or currentpos has advanced
* past the place we'd aggregated up to. Check for these cases and if
* so, reuse the saved result values.
*/
if ((winstate->frameOptions & (FRAMEOPTION_END_UNBOUNDED_FOLLOWING |
FRAMEOPTION_END_CURRENT_ROW)) &&
winstate->aggregatedbase <= winstate->currentpos &&
winstate->aggregatedupto > winstate->currentpos)
{
for (i = 0; i < numaggs; i++)
{
@ -450,37 +504,20 @@ eval_windowaggregates(WindowAggState *winstate)
return;
}
/* Initialize aggregates on first call for partition */
if (winstate->currentpos == 0)
{
for (i = 0; i < numaggs; i++)
{
peraggstate = &winstate->peragg[i];
wfuncno = peraggstate->wfuncno;
initialize_windowaggregate(winstate,
&winstate->perfunc[wfuncno],
peraggstate);
}
}
/*
* Advance until we reach a row not in frame (or end of partition).
*
* Note the loop invariant: agg_row_slot is either empty or holds the row
* at position aggregatedupto. The agg_ptr read pointer must always point
* to the next row to read into agg_row_slot.
* at position aggregatedupto. We advance aggregatedupto after processing
* a row.
*/
agg_row_slot = winstate->agg_row_slot;
for (;;)
{
/* Fetch next row if we didn't already */
if (TupIsNull(agg_row_slot))
{
spool_tuples(winstate, winstate->aggregatedupto);
tuplestore_select_read_pointer(winstate->buffer,
winstate->agg_ptr);
if (!tuplestore_gettupleslot(winstate->buffer, true, true,
agg_row_slot))
if (!window_gettupleslot(agg_winobj, winstate->aggregatedupto,
agg_row_slot))
break; /* must be end of partition */
}
@ -544,11 +581,11 @@ eval_windowaggregates(WindowAggState *winstate)
pfree(DatumGetPointer(peraggstate->resultValue));
/*
* If pass-by-ref, copy it into our global context.
* If pass-by-ref, copy it into our aggregate context.
*/
if (!*isnull)
{
oldContext = MemoryContextSwitchTo(winstate->wincontext);
oldContext = MemoryContextSwitchTo(winstate->aggcontext);
peraggstate->resultValue =
datumCopy(*result,
peraggstate->resulttypeByVal,
@ -624,11 +661,12 @@ begin_partition(WindowAggState *winstate)
int i;
winstate->partition_spooled = false;
winstate->framehead_valid = false;
winstate->frametail_valid = false;
winstate->spooled_rows = 0;
winstate->currentpos = 0;
winstate->frameheadpos = 0;
winstate->frametailpos = -1;
winstate->aggregatedupto = 0;
ExecClearTuple(winstate->agg_row_slot);
/*
@ -654,18 +692,39 @@ begin_partition(WindowAggState *winstate)
winstate->buffer = tuplestore_begin_heap(false, false, work_mem);
/*
* Set up read pointers for the tuplestore. The current and agg pointers
* don't need BACKWARD capability, but the per-window-function read
* pointers do.
* Set up read pointers for the tuplestore. The current pointer doesn't
* need BACKWARD capability, but the per-window-function read pointers do,
* and the aggregate pointer does if frame start is movable.
*/
winstate->current_ptr = 0; /* read pointer 0 is pre-allocated */
/* reset default REWIND capability bit for current ptr */
tuplestore_set_eflags(winstate->buffer, 0);
/* create a read pointer for aggregates, if needed */
/* create read pointers for aggregates, if needed */
if (winstate->numaggs > 0)
winstate->agg_ptr = tuplestore_alloc_read_pointer(winstate->buffer, 0);
{
WindowObject agg_winobj = winstate->agg_winobj;
int readptr_flags = 0;
/* If the frame head is potentially movable ... */
if (!(winstate->frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING))
{
/* ... create a mark pointer to track the frame head */
agg_winobj->markptr = tuplestore_alloc_read_pointer(winstate->buffer, 0);
/* and the read pointer will need BACKWARD capability */
readptr_flags |= EXEC_FLAG_BACKWARD;
}
agg_winobj->readptr = tuplestore_alloc_read_pointer(winstate->buffer,
readptr_flags);
agg_winobj->markpos = -1;
agg_winobj->seekpos = -1;
/* Also reset the row counters for aggregates */
winstate->aggregatedbase = 0;
winstate->aggregatedupto = 0;
}
/* create mark and read pointers for each real window function */
for (i = 0; i < numfuncs; i++)
@ -694,8 +753,8 @@ begin_partition(WindowAggState *winstate)
}
/*
* Read tuples from the outer node, up to position 'pos', and store them
* into the tuplestore. If pos is -1, reads the whole partition.
* Read tuples from the outer node, up to and including position 'pos', and
* store them into the tuplestore. If pos is -1, reads the whole partition.
*/
static void
spool_tuples(WindowAggState *winstate, int64 pos)
@ -789,7 +848,8 @@ release_partition(WindowAggState *winstate)
* any aggregate temp data). We don't rely on retail pfree because some
* aggregates might have allocated data we don't have direct pointers to.
*/
MemoryContextResetAndDeleteChildren(winstate->wincontext);
MemoryContextResetAndDeleteChildren(winstate->partcontext);
MemoryContextResetAndDeleteChildren(winstate->aggcontext);
if (winstate->buffer)
tuplestore_end(winstate->buffer);
@ -809,108 +869,303 @@ release_partition(WindowAggState *winstate)
static bool
row_is_in_frame(WindowAggState *winstate, int64 pos, TupleTableSlot *slot)
{
WindowAgg *node = (WindowAgg *) winstate->ss.ps.plan;
int frameOptions = node->frameOptions;
int frameOptions = winstate->frameOptions;
Assert(pos >= 0); /* else caller error */
/* We only support frame start mode UNBOUNDED PRECEDING for now */
Assert(frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING);
/* First, check frame starting conditions */
if (frameOptions & FRAMEOPTION_START_CURRENT_ROW)
{
if (frameOptions & FRAMEOPTION_ROWS)
{
/* rows before current row are out of frame */
if (pos < winstate->currentpos)
return false;
}
else if (frameOptions & FRAMEOPTION_RANGE)
{
/* preceding row that is not peer is out of frame */
if (pos < winstate->currentpos &&
!are_peers(winstate, slot, winstate->ss.ss_ScanTupleSlot))
return false;
}
else
Assert(false);
}
else if (frameOptions & FRAMEOPTION_START_VALUE)
{
if (frameOptions & FRAMEOPTION_ROWS)
{
int64 offset = DatumGetInt64(winstate->startOffsetValue);
/* In UNBOUNDED FOLLOWING mode, all partition rows are in frame */
if (frameOptions & FRAMEOPTION_END_UNBOUNDED_FOLLOWING)
return true;
/* rows before current row + offset are out of frame */
if (frameOptions & FRAMEOPTION_START_VALUE_PRECEDING)
offset = -offset;
/* Else frame tail mode must be CURRENT ROW */
Assert(frameOptions & FRAMEOPTION_END_CURRENT_ROW);
if (pos < winstate->currentpos + offset)
return false;
}
else if (frameOptions & FRAMEOPTION_RANGE)
{
/* parser should have rejected this */
elog(ERROR, "window frame with value offset is not implemented");
}
else
Assert(false);
}
/* if row is current row or a predecessor, it must be in frame */
if (pos <= winstate->currentpos)
return true;
/* Okay so far, now check frame ending conditions */
if (frameOptions & FRAMEOPTION_END_CURRENT_ROW)
{
if (frameOptions & FRAMEOPTION_ROWS)
{
/* rows after current row are out of frame */
if (pos > winstate->currentpos)
return false;
}
else if (frameOptions & FRAMEOPTION_RANGE)
{
/* following row that is not peer is out of frame */
if (pos > winstate->currentpos &&
!are_peers(winstate, slot, winstate->ss.ss_ScanTupleSlot))
return false;
}
else
Assert(false);
}
else if (frameOptions & FRAMEOPTION_END_VALUE)
{
if (frameOptions & FRAMEOPTION_ROWS)
{
int64 offset = DatumGetInt64(winstate->endOffsetValue);
/* In ROWS mode, *only* such rows are in frame */
if (frameOptions & FRAMEOPTION_ROWS)
return false;
/* rows after current row + offset are out of frame */
if (frameOptions & FRAMEOPTION_END_VALUE_PRECEDING)
offset = -offset;
/* Else must be RANGE mode */
Assert(frameOptions & FRAMEOPTION_RANGE);
if (pos > winstate->currentpos + offset)
return false;
}
else if (frameOptions & FRAMEOPTION_RANGE)
{
/* parser should have rejected this */
elog(ERROR, "window frame with value offset is not implemented");
}
else
Assert(false);
}
/* In frame iff it's a peer of current row */
return are_peers(winstate, slot, winstate->ss.ss_ScanTupleSlot);
/* If we get here, it's in frame */
return true;
}
/*
* update_frameheadpos
* make frameheadpos valid for the current row
*
* Uses the winobj's read pointer for any required fetches; hence, if the
* frame mode is one that requires row comparisons, the winobj's mark must
* not be past the currently known frame head. Also uses the specified slot
* for any required fetches.
*/
static void
update_frameheadpos(WindowObject winobj, TupleTableSlot *slot)
{
WindowAggState *winstate = winobj->winstate;
WindowAgg *node = (WindowAgg *) winstate->ss.ps.plan;
int frameOptions = winstate->frameOptions;
if (winstate->framehead_valid)
return; /* already known for current row */
if (frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING)
{
/* In UNBOUNDED PRECEDING mode, frame head is always row 0 */
winstate->frameheadpos = 0;
winstate->framehead_valid = true;
}
else if (frameOptions & FRAMEOPTION_START_CURRENT_ROW)
{
if (frameOptions & FRAMEOPTION_ROWS)
{
/* In ROWS mode, frame head is the same as current */
winstate->frameheadpos = winstate->currentpos;
winstate->framehead_valid = true;
}
else if (frameOptions & FRAMEOPTION_RANGE)
{
int64 fhprev;
/* If no ORDER BY, all rows are peers with each other */
if (node->ordNumCols == 0)
{
winstate->frameheadpos = 0;
winstate->framehead_valid = true;
return;
}
/*
* In RANGE START_CURRENT mode, frame head is the first row that
* is a peer of current row. We search backwards from current,
* which could be a bit inefficient if peer sets are large.
* Might be better to have a separate read pointer that moves
* forward tracking the frame head.
*/
fhprev = winstate->currentpos - 1;
for (;;)
{
/* assume the frame head can't go backwards */
if (fhprev < winstate->frameheadpos)
break;
if (!window_gettupleslot(winobj, fhprev, slot))
break; /* start of partition */
if (!are_peers(winstate, slot, winstate->ss.ss_ScanTupleSlot))
break; /* not peer of current row */
fhprev--;
}
winstate->frameheadpos = fhprev + 1;
winstate->framehead_valid = true;
}
else
Assert(false);
}
else if (frameOptions & FRAMEOPTION_START_VALUE)
{
if (frameOptions & FRAMEOPTION_ROWS)
{
/* In ROWS mode, bound is physically n before/after current */
int64 offset = DatumGetInt64(winstate->startOffsetValue);
if (frameOptions & FRAMEOPTION_START_VALUE_PRECEDING)
offset = -offset;
winstate->frameheadpos = winstate->currentpos + offset;
/* frame head can't go before first row */
if (winstate->frameheadpos < 0)
winstate->frameheadpos = 0;
else if (winstate->frameheadpos > winstate->currentpos)
{
/* make sure frameheadpos is not past end of partition */
spool_tuples(winstate, winstate->frameheadpos - 1);
if (winstate->frameheadpos > winstate->spooled_rows)
winstate->frameheadpos = winstate->spooled_rows;
}
winstate->framehead_valid = true;
}
else if (frameOptions & FRAMEOPTION_RANGE)
{
/* parser should have rejected this */
elog(ERROR, "window frame with value offset is not implemented");
}
else
Assert(false);
}
else
Assert(false);
}
/*
* update_frametailpos
* make frametailpos valid for the current row
*
* Uses the winobj's read pointer for any required fetches; the winobj's
* mark must not be past the currently known frame tail. Also uses the
* specified slot for any required fetches.
* Uses the winobj's read pointer for any required fetches; hence, if the
* frame mode is one that requires row comparisons, the winobj's mark must
* not be past the currently known frame tail. Also uses the specified slot
* for any required fetches.
*/
static void
update_frametailpos(WindowObject winobj, TupleTableSlot *slot)
{
WindowAggState *winstate = winobj->winstate;
WindowAgg *node = (WindowAgg *) winstate->ss.ps.plan;
int frameOptions = node->frameOptions;
int64 ftnext;
int frameOptions = winstate->frameOptions;
if (winstate->frametail_valid)
return; /* already known for current row */
/* We only support frame start mode UNBOUNDED PRECEDING for now */
Assert(frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING);
/* In UNBOUNDED FOLLOWING mode, all partition rows are in frame */
if (frameOptions & FRAMEOPTION_END_UNBOUNDED_FOLLOWING)
{
/* In UNBOUNDED FOLLOWING mode, all partition rows are in frame */
spool_tuples(winstate, -1);
winstate->frametailpos = winstate->spooled_rows - 1;
winstate->frametail_valid = true;
return;
}
/* Else frame tail mode must be CURRENT ROW */
Assert(frameOptions & FRAMEOPTION_END_CURRENT_ROW);
/* In ROWS mode, exactly the rows up to current are in frame */
if (frameOptions & FRAMEOPTION_ROWS)
else if (frameOptions & FRAMEOPTION_END_CURRENT_ROW)
{
winstate->frametailpos = winstate->currentpos;
winstate->frametail_valid = true;
return;
if (frameOptions & FRAMEOPTION_ROWS)
{
/* In ROWS mode, exactly the rows up to current are in frame */
winstate->frametailpos = winstate->currentpos;
winstate->frametail_valid = true;
}
else if (frameOptions & FRAMEOPTION_RANGE)
{
int64 ftnext;
/* If no ORDER BY, all rows are peers with each other */
if (node->ordNumCols == 0)
{
spool_tuples(winstate, -1);
winstate->frametailpos = winstate->spooled_rows - 1;
winstate->frametail_valid = true;
return;
}
/*
* Else we have to search for the first non-peer of the current
* row. We assume the current value of frametailpos is a lower
* bound on the possible frame tail location, ie, frame tail never
* goes backward, and that currentpos is also a lower bound, ie,
* frame end always >= current row.
*/
ftnext = Max(winstate->frametailpos, winstate->currentpos) + 1;
for (;;)
{
if (!window_gettupleslot(winobj, ftnext, slot))
break; /* end of partition */
if (!are_peers(winstate, slot, winstate->ss.ss_ScanTupleSlot))
break; /* not peer of current row */
ftnext++;
}
winstate->frametailpos = ftnext - 1;
winstate->frametail_valid = true;
}
else
Assert(false);
}
/* Else must be RANGE mode */
Assert(frameOptions & FRAMEOPTION_RANGE);
/* If no ORDER BY, all rows are peers with each other */
if (node->ordNumCols == 0)
else if (frameOptions & FRAMEOPTION_END_VALUE)
{
spool_tuples(winstate, -1);
winstate->frametailpos = winstate->spooled_rows - 1;
winstate->frametail_valid = true;
return;
}
if (frameOptions & FRAMEOPTION_ROWS)
{
/* In ROWS mode, bound is physically n before/after current */
int64 offset = DatumGetInt64(winstate->endOffsetValue);
/*
* Else we have to search for the first non-peer of the current row. We
* assume the current value of frametailpos is a lower bound on the
* possible frame tail location, ie, frame tail never goes backward, and
* that currentpos is also a lower bound, ie, current row is always in
* frame.
*/
ftnext = Max(winstate->frametailpos, winstate->currentpos) + 1;
for (;;)
{
if (!window_gettupleslot(winobj, ftnext, slot))
break; /* end of partition */
if (!are_peers(winstate, slot, winstate->ss.ss_ScanTupleSlot))
break; /* not peer of current row */
ftnext++;
if (frameOptions & FRAMEOPTION_END_VALUE_PRECEDING)
offset = -offset;
winstate->frametailpos = winstate->currentpos + offset;
/* smallest allowable value of frametailpos is -1 */
if (winstate->frametailpos < 0)
winstate->frametailpos = -1;
else if (winstate->frametailpos > winstate->currentpos)
{
/* make sure frametailpos is not past last row of partition */
spool_tuples(winstate, winstate->frametailpos);
if (winstate->frametailpos >= winstate->spooled_rows)
winstate->frametailpos = winstate->spooled_rows - 1;
}
winstate->frametail_valid = true;
}
else if (frameOptions & FRAMEOPTION_RANGE)
{
/* parser should have rejected this */
elog(ERROR, "window frame with value offset is not implemented");
}
else
Assert(false);
}
winstate->frametailpos = ftnext - 1;
winstate->frametail_valid = true;
else
Assert(false);
}
@ -953,6 +1208,73 @@ ExecWindowAgg(WindowAggState *winstate)
winstate->ss.ps.ps_TupFromTlist = false;
}
/*
* Compute frame offset values, if any, during first call.
*/
if (winstate->all_first)
{
int frameOptions = winstate->frameOptions;
ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
Datum value;
bool isnull;
int16 len;
bool byval;
if (frameOptions & FRAMEOPTION_START_VALUE)
{
Assert(winstate->startOffset != NULL);
value = ExecEvalExprSwitchContext(winstate->startOffset,
econtext,
&isnull,
NULL);
if (isnull)
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("frame starting offset must not be NULL")));
/* copy value into query-lifespan context */
get_typlenbyval(exprType((Node *) winstate->startOffset->expr),
&len, &byval);
winstate->startOffsetValue = datumCopy(value, byval, len);
if (frameOptions & FRAMEOPTION_ROWS)
{
/* value is known to be int8 */
int64 offset = DatumGetInt64(value);
if (offset < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("frame starting offset must not be negative")));
}
}
if (frameOptions & FRAMEOPTION_END_VALUE)
{
Assert(winstate->endOffset != NULL);
value = ExecEvalExprSwitchContext(winstate->endOffset,
econtext,
&isnull,
NULL);
if (isnull)
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("frame ending offset must not be NULL")));
/* copy value into query-lifespan context */
get_typlenbyval(exprType((Node *) winstate->endOffset->expr),
&len, &byval);
winstate->endOffsetValue = datumCopy(value, byval, len);
if (frameOptions & FRAMEOPTION_ROWS)
{
/* value is known to be int8 */
int64 offset = DatumGetInt64(value);
if (offset < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("frame ending offset must not be negative")));
}
}
winstate->all_first = false;
}
restart:
if (winstate->buffer == NULL)
{
@ -964,7 +1286,8 @@ restart:
{
/* Advance current row within partition */
winstate->currentpos++;
/* This might mean that the frame tail moves, too */
/* This might mean that the frame moves, too */
winstate->framehead_valid = false;
winstate->frametail_valid = false;
}
@ -1099,10 +1422,18 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
winstate->tmpcontext = tmpcontext;
ExecAssignExprContext(estate, &winstate->ss.ps);
/* Create long-lived context for storage of aggregate transvalues etc */
winstate->wincontext =
/* Create long-lived context for storage of partition-local memory etc */
winstate->partcontext =
AllocSetContextCreate(CurrentMemoryContext,
"WindowAggContext",
"WindowAgg_Partition",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
/* Create mid-lived context for aggregate trans values etc */
winstate->aggcontext =
AllocSetContextCreate(CurrentMemoryContext,
"WindowAgg_Aggregates",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
@ -1229,7 +1560,7 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
perfuncstate->numArguments = list_length(wfuncstate->args);
fmgr_info_cxt(wfunc->winfnoid, &perfuncstate->flinfo,
tmpcontext->ecxt_per_query_memory);
econtext->ecxt_per_query_memory);
perfuncstate->flinfo.fn_expr = (Node *) wfunc;
get_typlenbyval(wfunc->wintype,
&perfuncstate->resulttypeLen,
@ -1264,6 +1595,30 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
winstate->numfuncs = wfuncno + 1;
winstate->numaggs = aggno + 1;
/* Set up WindowObject for aggregates, if needed */
if (winstate->numaggs > 0)
{
WindowObject agg_winobj = makeNode(WindowObjectData);
agg_winobj->winstate = winstate;
agg_winobj->argstates = NIL;
agg_winobj->localmem = NULL;
/* make sure markptr = -1 to invalidate. It may not get used */
agg_winobj->markptr = -1;
agg_winobj->readptr = -1;
winstate->agg_winobj = agg_winobj;
}
/* copy frame options to state node for easy access */
winstate->frameOptions = node->frameOptions;
/* initialize frame bound offset expressions */
winstate->startOffset = ExecInitExpr((Expr *) node->startOffset,
(PlanState *) winstate);
winstate->endOffset = ExecInitExpr((Expr *) node->endOffset,
(PlanState *) winstate);
winstate->all_first = true;
winstate->partition_spooled = false;
winstate->more_partitions = false;
@ -1297,7 +1652,8 @@ ExecEndWindowAgg(WindowAggState *node)
node->ss.ps.ps_ExprContext = node->tmpcontext;
ExecFreeExprContext(&node->ss.ps);
MemoryContextDelete(node->wincontext);
MemoryContextDelete(node->partcontext);
MemoryContextDelete(node->aggcontext);
outerPlan = outerPlanState(node);
ExecEndNode(outerPlan);
@ -1315,6 +1671,7 @@ ExecReScanWindowAgg(WindowAggState *node, ExprContext *exprCtxt)
node->all_done = false;
node->ss.ps.ps_TupFromTlist = false;
node->all_first = true;
/* release tuplestore et al */
release_partition(node);
@ -1566,7 +1923,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
* There's no API to refetch the tuple at the current position. We have to
* move one tuple forward, and then one backward. (We don't do it the
* other way because we might try to fetch the row before our mark, which
* isn't allowed.)
* isn't allowed.) XXX this case could stand to be optimized.
*/
if (winobj->seekpos == pos)
{
@ -1616,8 +1973,8 @@ WinGetPartitionLocalMemory(WindowObject winobj, Size sz)
{
Assert(WindowObjectIsValid(winobj));
if (winobj->localmem == NULL)
winobj->localmem = MemoryContextAllocZero(winobj->winstate->wincontext,
sz);
winobj->localmem =
MemoryContextAllocZero(winobj->winstate->partcontext, sz);
return winobj->localmem;
}
@ -1791,7 +2148,30 @@ WinGetFuncArgInPartition(WindowObject winobj, int argno,
if (isout)
*isout = false;
if (set_mark)
WinSetMarkPosition(winobj, abs_pos);
{
int frameOptions = winstate->frameOptions;
int64 mark_pos = abs_pos;
/*
* In RANGE mode with a moving frame head, we must not let the
* mark advance past frameheadpos, since that row has to be
* fetchable during future update_frameheadpos calls.
*
* XXX it is very ugly to pollute window functions' marks with
* this consideration; it could for instance mask a logic bug
* that lets a window function fetch rows before what it had
* claimed was its mark. Perhaps use a separate mark for
* frame head probes?
*/
if ((frameOptions & FRAMEOPTION_RANGE) &&
!(frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING))
{
update_frameheadpos(winobj, winstate->temp_slot_2);
if (mark_pos > winstate->frameheadpos)
mark_pos = winstate->frameheadpos;
}
WinSetMarkPosition(winobj, mark_pos);
}
econtext->ecxt_outertuple = slot;
return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
econtext, isnull, NULL);
@ -1838,7 +2218,8 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
abs_pos = winstate->currentpos + relpos;
break;
case WINDOW_SEEK_HEAD:
abs_pos = relpos;
update_frameheadpos(winobj, slot);
abs_pos = winstate->frameheadpos + relpos;
break;
case WINDOW_SEEK_TAIL:
update_frametailpos(winobj, slot);
@ -1866,7 +2247,30 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
if (isout)
*isout = false;
if (set_mark)
WinSetMarkPosition(winobj, abs_pos);
{
int frameOptions = winstate->frameOptions;
int64 mark_pos = abs_pos;
/*
* In RANGE mode with a moving frame head, we must not let the
* mark advance past frameheadpos, since that row has to be
* fetchable during future update_frameheadpos calls.
*
* XXX it is very ugly to pollute window functions' marks with
* this consideration; it could for instance mask a logic bug
* that lets a window function fetch rows before what it had
* claimed was its mark. Perhaps use a separate mark for
* frame head probes?
*/
if ((frameOptions & FRAMEOPTION_RANGE) &&
!(frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING))
{
update_frameheadpos(winobj, winstate->temp_slot_2);
if (mark_pos > winstate->frameheadpos)
mark_pos = winstate->frameheadpos;
}
WinSetMarkPosition(winobj, mark_pos);
}
econtext->ecxt_outertuple = slot;
return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
econtext, isnull, NULL);

View File

@ -15,7 +15,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.460 2010/01/28 23:21:11 petere Exp $
* $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.461 2010/02/12 17:33:20 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -718,6 +718,8 @@ _copyWindowAgg(WindowAgg *from)
COPY_POINTER_FIELD(ordOperators, from->ordNumCols * sizeof(Oid));
}
COPY_SCALAR_FIELD(frameOptions);
COPY_NODE_FIELD(startOffset);
COPY_NODE_FIELD(endOffset);
return newnode;
}
@ -1848,6 +1850,8 @@ _copyWindowClause(WindowClause *from)
COPY_NODE_FIELD(partitionClause);
COPY_NODE_FIELD(orderClause);
COPY_SCALAR_FIELD(frameOptions);
COPY_NODE_FIELD(startOffset);
COPY_NODE_FIELD(endOffset);
COPY_SCALAR_FIELD(winref);
COPY_SCALAR_FIELD(copiedOrder);
@ -2076,6 +2080,8 @@ _copyWindowDef(WindowDef *from)
COPY_NODE_FIELD(partitionClause);
COPY_NODE_FIELD(orderClause);
COPY_SCALAR_FIELD(frameOptions);
COPY_NODE_FIELD(startOffset);
COPY_NODE_FIELD(endOffset);
COPY_LOCATION_FIELD(location);
return newnode;

View File

@ -22,7 +22,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.381 2010/01/28 23:21:11 petere Exp $
* $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.382 2010/02/12 17:33:20 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -2056,6 +2056,8 @@ _equalWindowDef(WindowDef *a, WindowDef *b)
COMPARE_NODE_FIELD(partitionClause);
COMPARE_NODE_FIELD(orderClause);
COMPARE_SCALAR_FIELD(frameOptions);
COMPARE_NODE_FIELD(startOffset);
COMPARE_NODE_FIELD(endOffset);
COMPARE_LOCATION_FIELD(location);
return true;
@ -2205,6 +2207,8 @@ _equalWindowClause(WindowClause *a, WindowClause *b)
COMPARE_NODE_FIELD(partitionClause);
COMPARE_NODE_FIELD(orderClause);
COMPARE_SCALAR_FIELD(frameOptions);
COMPARE_NODE_FIELD(startOffset);
COMPARE_NODE_FIELD(endOffset);
COMPARE_SCALAR_FIELD(winref);
COMPARE_SCALAR_FIELD(copiedOrder);

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/nodes/nodeFuncs.c,v 1.45 2010/01/02 16:57:46 momjian Exp $
* $PostgreSQL: pgsql/src/backend/nodes/nodeFuncs.c,v 1.46 2010/02/12 17:33:20 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -1298,6 +1298,10 @@ expression_tree_walker(Node *node,
return true;
if (walker(wc->orderClause, context))
return true;
if (walker(wc->startOffset, context))
return true;
if (walker(wc->endOffset, context))
return true;
}
break;
case T_CommonTableExpr:
@ -1950,6 +1954,8 @@ expression_tree_mutator(Node *node,
FLATCOPY(newnode, wc, WindowClause);
MUTATE(newnode->partitionClause, wc->partitionClause, List *);
MUTATE(newnode->orderClause, wc->orderClause, List *);
MUTATE(newnode->startOffset, wc->startOffset, Node *);
MUTATE(newnode->endOffset, wc->endOffset, Node *);
return (Node *) newnode;
}
break;
@ -2475,6 +2481,10 @@ bool
return true;
if (walker(wd->orderClause, context))
return true;
if (walker(wd->startOffset, context))
return true;
if (walker(wd->endOffset, context))
return true;
}
break;
case T_RangeSubselect:

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.381 2010/01/28 23:21:12 petere Exp $
* $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.382 2010/02/12 17:33:20 tgl Exp $
*
* NOTES
* Every node type that can appear in stored rules' parsetrees *must*
@ -610,6 +610,8 @@ _outWindowAgg(StringInfo str, WindowAgg *node)
appendStringInfo(str, " %u", node->ordOperators[i]);
WRITE_INT_FIELD(frameOptions);
WRITE_NODE_FIELD(startOffset);
WRITE_NODE_FIELD(endOffset);
}
static void
@ -2035,6 +2037,8 @@ _outWindowClause(StringInfo str, WindowClause *node)
WRITE_NODE_FIELD(partitionClause);
WRITE_NODE_FIELD(orderClause);
WRITE_INT_FIELD(frameOptions);
WRITE_NODE_FIELD(startOffset);
WRITE_NODE_FIELD(endOffset);
WRITE_UINT_FIELD(winref);
WRITE_BOOL_FIELD(copiedOrder);
}
@ -2326,6 +2330,8 @@ _outWindowDef(StringInfo str, WindowDef *node)
WRITE_NODE_FIELD(partitionClause);
WRITE_NODE_FIELD(orderClause);
WRITE_INT_FIELD(frameOptions);
WRITE_NODE_FIELD(startOffset);
WRITE_NODE_FIELD(endOffset);
WRITE_LOCATION_FIELD(location);
}

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.230 2010/01/02 16:57:46 momjian Exp $
* $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.231 2010/02/12 17:33:20 tgl Exp $
*
* NOTES
* Path and Plan nodes do not have any readfuncs support, because we
@ -279,6 +279,8 @@ _readWindowClause(void)
READ_NODE_FIELD(partitionClause);
READ_NODE_FIELD(orderClause);
READ_INT_FIELD(frameOptions);
READ_NODE_FIELD(startOffset);
READ_NODE_FIELD(endOffset);
READ_UINT_FIELD(winref);
READ_BOOL_FIELD(copiedOrder);

View File

@ -10,7 +10,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.270 2010/01/02 16:57:47 momjian Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.271 2010/02/12 17:33:20 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -3364,7 +3364,8 @@ make_windowagg(PlannerInfo *root, List *tlist,
int numWindowFuncs, Index winref,
int partNumCols, AttrNumber *partColIdx, Oid *partOperators,
int ordNumCols, AttrNumber *ordColIdx, Oid *ordOperators,
int frameOptions, Plan *lefttree)
int frameOptions, Node *startOffset, Node *endOffset,
Plan *lefttree)
{
WindowAgg *node = makeNode(WindowAgg);
Plan *plan = &node->plan;
@ -3379,6 +3380,8 @@ make_windowagg(PlannerInfo *root, List *tlist,
node->ordColIdx = ordColIdx;
node->ordOperators = ordOperators;
node->frameOptions = frameOptions;
node->startOffset = startOffset;
node->endOffset = endOffset;
copy_plan_costsize(plan, lefttree); /* only care about copying size */
cost_windowagg(&windowagg_path, root,

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.264 2010/02/10 03:38:35 tgl Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.265 2010/02/12 17:33:20 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -398,7 +398,10 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
root->hasPseudoConstantQuals = false;
/*
* Do expression preprocessing on targetlist and quals.
* Do expression preprocessing on targetlist and quals, as well as other
* random expressions in the querytree. Note that we do not need to
* handle sort/group expressions explicitly, because they are actually
* part of the targetlist.
*/
parse->targetList = (List *)
preprocess_expression(root, (Node *) parse->targetList,
@ -413,6 +416,17 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
parse->havingQual = preprocess_expression(root, parse->havingQual,
EXPRKIND_QUAL);
foreach(l, parse->windowClause)
{
WindowClause *wc = (WindowClause *) lfirst(l);
/* partitionClause/orderClause are sort/group expressions */
wc->startOffset = preprocess_expression(root, wc->startOffset,
EXPRKIND_LIMIT);
wc->endOffset = preprocess_expression(root, wc->endOffset,
EXPRKIND_LIMIT);
}
parse->limitOffset = preprocess_expression(root, parse->limitOffset,
EXPRKIND_LIMIT);
parse->limitCount = preprocess_expression(root, parse->limitCount,
@ -1513,6 +1527,8 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
ordColIdx,
ordOperators,
wc->frameOptions,
wc->startOffset,
wc->endOffset,
result_plan);
}
}

View File

@ -9,7 +9,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/plan/setrefs.c,v 1.157 2010/01/15 22:36:32 tgl Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/plan/setrefs.c,v 1.158 2010/02/12 17:33:20 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -466,10 +466,26 @@ set_plan_refs(PlannerGlobal *glob, Plan *plan, int rtoffset)
}
break;
case T_Agg:
case T_WindowAgg:
case T_Group:
set_upper_references(glob, plan, rtoffset);
break;
case T_WindowAgg:
{
WindowAgg *wplan = (WindowAgg *) plan;
set_upper_references(glob, plan, rtoffset);
/*
* Like Limit node limit/offset expressions, WindowAgg has
* frame offset expressions, which cannot contain subplan
* variable refs, so fix_scan_expr works for them.
*/
wplan->startOffset =
fix_scan_expr(glob, wplan->startOffset, rtoffset);
wplan->endOffset =
fix_scan_expr(glob, wplan->endOffset, rtoffset);
}
break;
case T_Result:
{
Result *splan = (Result *) plan;

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/plan/subselect.c,v 1.158 2010/01/18 18:17:45 tgl Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/plan/subselect.c,v 1.159 2010/02/12 17:33:20 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -2098,9 +2098,15 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
locally_added_param);
break;
case T_WindowAgg:
finalize_primnode(((WindowAgg *) plan)->startOffset,
&context);
finalize_primnode(((WindowAgg *) plan)->endOffset,
&context);
break;
case T_Hash:
case T_Agg:
case T_WindowAgg:
case T_Material:
case T_Sort:
case T_Unique:

View File

@ -11,7 +11,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.707 2010/02/08 04:33:54 tgl Exp $
* $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.708 2010/02/12 17:33:20 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
@ -434,8 +434,8 @@ static TypeName *TableFuncTypeName(List *columns);
%type <list> window_clause window_definition_list opt_partition_clause
%type <windef> window_definition over_clause window_specification
opt_frame_clause frame_extent frame_bound
%type <str> opt_existing_window_name
%type <ival> opt_frame_clause frame_extent frame_bound
/*
@ -578,8 +578,18 @@ static TypeName *TableFuncTypeName(List *columns);
* RANGE, ROWS to support opt_existing_window_name; and for RANGE, ROWS
* so that they can follow a_expr without creating
* postfix-operator problems.
*
* The frame_bound productions UNBOUNDED PRECEDING and UNBOUNDED FOLLOWING
* are even messier: since UNBOUNDED is an unreserved keyword (per spec!),
* there is no principled way to distinguish these from the productions
* a_expr PRECEDING/FOLLOWING. We hack this up by giving UNBOUNDED slightly
* lower precedence than PRECEDING and FOLLOWING. At present this doesn't
* appear to cause UNBOUNDED to be treated differently from other unreserved
* keywords anywhere else in the grammar, but it's definitely risky. We can
* blame any funny behavior of UNBOUNDED on the SQL standard, though.
*/
%nonassoc IDENT PARTITION RANGE ROWS
%nonassoc UNBOUNDED /* ideally should have same precedence as IDENT */
%nonassoc IDENT PARTITION RANGE ROWS PRECEDING FOLLOWING
%left Op OPERATOR /* multi-character ops and user-defined operators */
%nonassoc NOTNULL
%nonassoc ISNULL
@ -9907,6 +9917,8 @@ over_clause: OVER window_specification
n->partitionClause = NIL;
n->orderClause = NIL;
n->frameOptions = FRAMEOPTION_DEFAULTS;
n->startOffset = NULL;
n->endOffset = NULL;
n->location = @2;
$$ = n;
}
@ -9922,7 +9934,10 @@ window_specification: '(' opt_existing_window_name opt_partition_clause
n->refname = $2;
n->partitionClause = $3;
n->orderClause = $4;
n->frameOptions = $5;
/* copy relevant fields of opt_frame_clause */
n->frameOptions = $5->frameOptions;
n->startOffset = $5->startOffset;
n->endOffset = $5->endOffset;
n->location = @1;
$$ = n;
}
@ -9947,58 +9962,100 @@ opt_partition_clause: PARTITION BY expr_list { $$ = $3; }
;
/*
* For frame clauses, we return a WindowDef, but only some fields are used:
* frameOptions, startOffset, and endOffset.
*
* This is only a subset of the full SQL:2008 frame_clause grammar.
* We don't support <expression> PRECEDING, <expression> FOLLOWING,
* nor <window frame exclusion> yet.
* We don't support <window frame exclusion> yet.
*/
opt_frame_clause:
RANGE frame_extent
{
$$ = FRAMEOPTION_NONDEFAULT | FRAMEOPTION_RANGE | $2;
WindowDef *n = $2;
n->frameOptions |= FRAMEOPTION_NONDEFAULT | FRAMEOPTION_RANGE;
if (n->frameOptions & (FRAMEOPTION_START_VALUE_PRECEDING |
FRAMEOPTION_END_VALUE_PRECEDING))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("RANGE PRECEDING is only supported with UNBOUNDED"),
parser_errposition(@1)));
if (n->frameOptions & (FRAMEOPTION_START_VALUE_FOLLOWING |
FRAMEOPTION_END_VALUE_FOLLOWING))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("RANGE FOLLOWING is only supported with UNBOUNDED"),
parser_errposition(@1)));
$$ = n;
}
| ROWS frame_extent
{
$$ = FRAMEOPTION_NONDEFAULT | FRAMEOPTION_ROWS | $2;
WindowDef *n = $2;
n->frameOptions |= FRAMEOPTION_NONDEFAULT | FRAMEOPTION_ROWS;
$$ = n;
}
| /*EMPTY*/
{ $$ = FRAMEOPTION_DEFAULTS; }
{
WindowDef *n = makeNode(WindowDef);
n->frameOptions = FRAMEOPTION_DEFAULTS;
n->startOffset = NULL;
n->endOffset = NULL;
$$ = n;
}
;
frame_extent: frame_bound
{
WindowDef *n = $1;
/* reject invalid cases */
if ($1 & FRAMEOPTION_START_UNBOUNDED_FOLLOWING)
if (n->frameOptions & FRAMEOPTION_START_UNBOUNDED_FOLLOWING)
ereport(ERROR,
(errcode(ERRCODE_WINDOWING_ERROR),
errmsg("frame start cannot be UNBOUNDED FOLLOWING"),
parser_errposition(@1)));
if ($1 & FRAMEOPTION_START_CURRENT_ROW)
if (n->frameOptions & FRAMEOPTION_START_VALUE_FOLLOWING)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("frame start at CURRENT ROW is not implemented"),
(errcode(ERRCODE_WINDOWING_ERROR),
errmsg("frame starting from following row cannot end with current row"),
parser_errposition(@1)));
$$ = $1 | FRAMEOPTION_END_CURRENT_ROW;
n->frameOptions |= FRAMEOPTION_END_CURRENT_ROW;
$$ = n;
}
| BETWEEN frame_bound AND frame_bound
{
WindowDef *n1 = $2;
WindowDef *n2 = $4;
/* form merged options */
int frameOptions = n1->frameOptions;
/* shift converts START_ options to END_ options */
frameOptions |= n2->frameOptions << 1;
frameOptions |= FRAMEOPTION_BETWEEN;
/* reject invalid cases */
if ($2 & FRAMEOPTION_START_UNBOUNDED_FOLLOWING)
if (frameOptions & FRAMEOPTION_START_UNBOUNDED_FOLLOWING)
ereport(ERROR,
(errcode(ERRCODE_WINDOWING_ERROR),
errmsg("frame start cannot be UNBOUNDED FOLLOWING"),
parser_errposition(@2)));
if ($2 & FRAMEOPTION_START_CURRENT_ROW)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("frame start at CURRENT ROW is not implemented"),
parser_errposition(@2)));
if ($4 & FRAMEOPTION_START_UNBOUNDED_PRECEDING)
if (frameOptions & FRAMEOPTION_END_UNBOUNDED_PRECEDING)
ereport(ERROR,
(errcode(ERRCODE_WINDOWING_ERROR),
errmsg("frame end cannot be UNBOUNDED PRECEDING"),
parser_errposition(@4)));
/* shift converts START_ options to END_ options */
$$ = FRAMEOPTION_BETWEEN | $2 | ($4 << 1);
if ((frameOptions & FRAMEOPTION_START_CURRENT_ROW) &&
(frameOptions & FRAMEOPTION_END_VALUE_PRECEDING))
ereport(ERROR,
(errcode(ERRCODE_WINDOWING_ERROR),
errmsg("frame starting from current row cannot have preceding rows"),
parser_errposition(@4)));
if ((frameOptions & FRAMEOPTION_START_VALUE_FOLLOWING) &&
(frameOptions & (FRAMEOPTION_END_VALUE_PRECEDING |
FRAMEOPTION_END_CURRENT_ROW)))
ereport(ERROR,
(errcode(ERRCODE_WINDOWING_ERROR),
errmsg("frame starting from following row cannot have preceding rows"),
parser_errposition(@4)));
n1->frameOptions = frameOptions;
n1->endOffset = n2->startOffset;
$$ = n1;
}
;
@ -10010,15 +10067,43 @@ frame_extent: frame_bound
frame_bound:
UNBOUNDED PRECEDING
{
$$ = FRAMEOPTION_START_UNBOUNDED_PRECEDING;
WindowDef *n = makeNode(WindowDef);
n->frameOptions = FRAMEOPTION_START_UNBOUNDED_PRECEDING;
n->startOffset = NULL;
n->endOffset = NULL;
$$ = n;
}
| UNBOUNDED FOLLOWING
{
$$ = FRAMEOPTION_START_UNBOUNDED_FOLLOWING;
WindowDef *n = makeNode(WindowDef);
n->frameOptions = FRAMEOPTION_START_UNBOUNDED_FOLLOWING;
n->startOffset = NULL;
n->endOffset = NULL;
$$ = n;
}
| CURRENT_P ROW
{
$$ = FRAMEOPTION_START_CURRENT_ROW;
WindowDef *n = makeNode(WindowDef);
n->frameOptions = FRAMEOPTION_START_CURRENT_ROW;
n->startOffset = NULL;
n->endOffset = NULL;
$$ = n;
}
| a_expr PRECEDING
{
WindowDef *n = makeNode(WindowDef);
n->frameOptions = FRAMEOPTION_START_VALUE_PRECEDING;
n->startOffset = $1;
n->endOffset = NULL;
$$ = n;
}
| a_expr FOLLOWING
{
WindowDef *n = makeNode(WindowDef);
n->frameOptions = FRAMEOPTION_START_VALUE_FOLLOWING;
n->startOffset = $1;
n->endOffset = NULL;
$$ = n;
}
;
@ -10981,7 +11066,8 @@ unreserved_keyword:
* looks too much like a function call for an LR(1) parser.
*/
col_name_keyword:
BIGINT
BETWEEN
| BIGINT
| BIT
| BOOLEAN_P
| CHAR_P
@ -11040,7 +11126,6 @@ col_name_keyword:
*/
type_func_name_keyword:
AUTHORIZATION
| BETWEEN
| BINARY
| CONCURRENTLY
| CROSS

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/parser/parse_agg.c,v 1.90 2010/01/02 16:57:49 momjian Exp $
* $PostgreSQL: pgsql/src/backend/parser/parse_agg.c,v 1.91 2010/02/12 17:33:20 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -258,7 +258,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
continue;
if (equal(refwin->partitionClause, windef->partitionClause) &&
equal(refwin->orderClause, windef->orderClause) &&
refwin->frameOptions == windef->frameOptions)
refwin->frameOptions == windef->frameOptions &&
equal(refwin->startOffset, windef->startOffset) &&
equal(refwin->endOffset, windef->endOffset))
{
/* found a duplicate window specification */
wfunc->winref = winref;
@ -505,6 +507,7 @@ parseCheckWindowFuncs(ParseState *pstate, Query *qry)
parser_errposition(pstate,
locate_windowfunc(expr))));
}
/* startOffset and limitOffset were checked in transformFrameOffset */
}
}

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/parser/parse_clause.c,v 1.196 2010/02/07 20:48:10 tgl Exp $
* $PostgreSQL: pgsql/src/backend/parser/parse_clause.c,v 1.197 2010/02/12 17:33:20 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -72,6 +72,8 @@ static Node *transformFromClauseItem(ParseState *pstate, Node *n,
Relids *containedRels);
static Node *buildMergedJoinVar(ParseState *pstate, JoinType jointype,
Var *l_colvar, Var *r_colvar);
static void checkExprIsVarFree(ParseState *pstate, Node *n,
const char *constructName);
static TargetEntry *findTargetlistEntrySQL92(ParseState *pstate, Node *node,
List **tlist, int clause);
static TargetEntry *findTargetlistEntrySQL99(ParseState *pstate, Node *node,
@ -85,6 +87,8 @@ static List *addTargetToGroupList(ParseState *pstate, TargetEntry *tle,
List *grouplist, List *targetlist, int location,
bool resolveUnknown);
static WindowClause *findWindowClause(List *wclist, const char *name);
static Node *transformFrameOffset(ParseState *pstate, int frameOptions,
Node *clause);
/*
@ -1177,10 +1181,28 @@ transformLimitClause(ParseState *pstate, Node *clause,
qual = coerce_to_specific_type(pstate, qual, INT8OID, constructName);
/*
* LIMIT can't refer to any vars or aggregates of the current query
*/
if (contain_vars_of_level(qual, 0))
/* LIMIT can't refer to any vars or aggregates of the current query */
checkExprIsVarFree(pstate, qual, constructName);
return qual;
}
/*
* checkExprIsVarFree
* Check that given expr has no Vars of the current query level
* (and no aggregates or window functions, either).
*
* This is used to check expressions that have to have a consistent value
* across all rows of the query, such as a LIMIT. Arguably it should reject
* volatile functions, too, but we don't do that --- whatever value the
* function gives on first execution is what you get.
*
* constructName does not affect the semantics, but is used in error messages
*/
static void
checkExprIsVarFree(ParseState *pstate, Node *n, const char *constructName)
{
if (contain_vars_of_level(n, 0))
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
@ -1188,10 +1210,10 @@ transformLimitClause(ParseState *pstate, Node *clause,
errmsg("argument of %s must not contain variables",
constructName),
parser_errposition(pstate,
locate_var_of_level(qual, 0))));
locate_var_of_level(n, 0))));
}
if (pstate->p_hasAggs &&
checkExprHasAggs(qual))
checkExprHasAggs(n))
{
ereport(ERROR,
(errcode(ERRCODE_GROUPING_ERROR),
@ -1199,10 +1221,10 @@ transformLimitClause(ParseState *pstate, Node *clause,
errmsg("argument of %s must not contain aggregate functions",
constructName),
parser_errposition(pstate,
locate_agg_of_level(qual, 0))));
locate_agg_of_level(n, 0))));
}
if (pstate->p_hasWindowFuncs &&
checkExprHasWindowFuncs(qual))
checkExprHasWindowFuncs(n))
{
ereport(ERROR,
(errcode(ERRCODE_WINDOWING_ERROR),
@ -1210,10 +1232,8 @@ transformLimitClause(ParseState *pstate, Node *clause,
errmsg("argument of %s must not contain window functions",
constructName),
parser_errposition(pstate,
locate_windowfunc(qual))));
locate_windowfunc(n))));
}
return qual;
}
@ -1664,6 +1684,11 @@ transformWindowDefinitions(ParseState *pstate,
windef->refname),
parser_errposition(pstate, windef->location)));
wc->frameOptions = windef->frameOptions;
/* Process frame offset expressions */
wc->startOffset = transformFrameOffset(pstate, wc->frameOptions,
windef->startOffset);
wc->endOffset = transformFrameOffset(pstate, wc->frameOptions,
windef->endOffset);
wc->winref = winref;
result = lappend(result, wc);
@ -2166,3 +2191,47 @@ findWindowClause(List *wclist, const char *name)
return NULL;
}
/*
* transformFrameOffset
* Process a window frame offset expression
*/
static Node *
transformFrameOffset(ParseState *pstate, int frameOptions, Node *clause)
{
const char *constructName = NULL;
Node *node;
/* Quick exit if no offset expression */
if (clause == NULL)
return NULL;
/* Transform the raw expression tree */
node = transformExpr(pstate, clause);
if (frameOptions & FRAMEOPTION_ROWS)
{
/*
* Like LIMIT clause, simply coerce to int8
*/
constructName = "ROWS";
node = coerce_to_specific_type(pstate, node, INT8OID, constructName);
}
else if (frameOptions & FRAMEOPTION_RANGE)
{
/*
* this needs a lot of thought to decide how to support in the
* context of Postgres' extensible datatype framework
*/
constructName = "RANGE";
/* error was already thrown by gram.y, this is just a backstop */
elog(ERROR, "window frame with value offset is not implemented");
}
else
Assert(false);
/* Disallow variables and aggregates in frame offsets */
checkExprIsVarFree(pstate, node, constructName);
return node;
}

View File

@ -9,7 +9,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.320 2010/01/21 06:11:45 itagaki Exp $
* $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.321 2010/02/12 17:33:20 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -3160,6 +3160,16 @@ get_rule_windowspec(WindowClause *wc, List *targetList,
appendStringInfoString(buf, "UNBOUNDED PRECEDING ");
else if (wc->frameOptions & FRAMEOPTION_START_CURRENT_ROW)
appendStringInfoString(buf, "CURRENT ROW ");
else if (wc->frameOptions & FRAMEOPTION_START_VALUE)
{
get_rule_expr(wc->startOffset, context, false);
if (wc->frameOptions & FRAMEOPTION_START_VALUE_PRECEDING)
appendStringInfoString(buf, " PRECEDING ");
else if (wc->frameOptions & FRAMEOPTION_START_VALUE_FOLLOWING)
appendStringInfoString(buf, " FOLLOWING ");
else
Assert(false);
}
else
Assert(false);
if (wc->frameOptions & FRAMEOPTION_BETWEEN)
@ -3169,6 +3179,16 @@ get_rule_windowspec(WindowClause *wc, List *targetList,
appendStringInfoString(buf, "UNBOUNDED FOLLOWING ");
else if (wc->frameOptions & FRAMEOPTION_END_CURRENT_ROW)
appendStringInfoString(buf, "CURRENT ROW ");
else if (wc->frameOptions & FRAMEOPTION_END_VALUE)
{
get_rule_expr(wc->endOffset, context, false);
if (wc->frameOptions & FRAMEOPTION_END_VALUE_PRECEDING)
appendStringInfoString(buf, " PRECEDING ");
else if (wc->frameOptions & FRAMEOPTION_END_VALUE_FOLLOWING)
appendStringInfoString(buf, " FOLLOWING ");
else
Assert(false);
}
else
Assert(false);
}

View File

@ -37,7 +37,7 @@
* Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.583 2010/02/07 20:48:11 tgl Exp $
* $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.584 2010/02/12 17:33:20 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -53,6 +53,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 201002071
#define CATALOG_VERSION_NO 201002121
#endif

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.217 2010/01/05 23:25:36 tgl Exp $
* $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.218 2010/02/12 17:33:20 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -1595,23 +1595,36 @@ typedef struct WindowAggState
FmgrInfo *ordEqfunctions; /* equality funcs for ordering columns */
Tuplestorestate *buffer; /* stores rows of current partition */
int current_ptr; /* read pointer # for current */
int agg_ptr; /* read pointer # for aggregates */
int64 spooled_rows; /* total # of rows in buffer */
int64 currentpos; /* position of current row in partition */
int64 frameheadpos; /* current frame head position */
int64 frametailpos; /* current frame tail position */
/* use struct pointer to avoid including windowapi.h here */
struct WindowObjectData *agg_winobj; /* winobj for aggregate fetches */
int64 aggregatedbase; /* start row for current aggregates */
int64 aggregatedupto; /* rows before this one are aggregated */
MemoryContext wincontext; /* context for partition-lifespan data */
int frameOptions; /* frame_clause options, see WindowDef */
ExprState *startOffset; /* expression for starting bound offset */
ExprState *endOffset; /* expression for ending bound offset */
Datum startOffsetValue; /* result of startOffset evaluation */
Datum endOffsetValue; /* result of endOffset evaluation */
MemoryContext partcontext; /* context for partition-lifespan data */
MemoryContext aggcontext; /* context for each aggregate data */
ExprContext *tmpcontext; /* short-term evaluation context */
bool all_first; /* true if the scan is starting */
bool all_done; /* true if the scan is finished */
bool partition_spooled; /* true if all tuples in current
* partition have been spooled into
* tuplestore */
bool more_partitions;/* true if there's more partitions after this
* one */
bool frametail_valid;/* true if frametailpos is known up to date
* for current row */
bool more_partitions; /* true if there's more partitions after
* this one */
bool framehead_valid; /* true if frameheadpos is known up to date
* for current row */
bool frametail_valid; /* true if frametailpos is known up to date
* for current row */
TupleTableSlot *first_part_slot; /* first tuple of current or next
* partition */

View File

@ -13,7 +13,7 @@
* Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.428 2010/02/08 04:33:54 tgl Exp $
* $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.429 2010/02/12 17:33:20 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -393,6 +393,8 @@ typedef struct WindowDef
List *partitionClause; /* PARTITION BY expression list */
List *orderClause; /* ORDER BY (list of SortBy) */
int frameOptions; /* frame_clause options, see below */
Node *startOffset; /* expression for starting bound, if any */
Node *endOffset; /* expression for ending bound, if any */
int location; /* parse location, or -1 if none/unknown */
} WindowDef;
@ -414,6 +416,15 @@ typedef struct WindowDef
#define FRAMEOPTION_END_UNBOUNDED_FOLLOWING 0x00080 /* end is U. F. */
#define FRAMEOPTION_START_CURRENT_ROW 0x00100 /* start is C. R. */
#define FRAMEOPTION_END_CURRENT_ROW 0x00200 /* end is C. R. */
#define FRAMEOPTION_START_VALUE_PRECEDING 0x00400 /* start is V. P. */
#define FRAMEOPTION_END_VALUE_PRECEDING 0x00800 /* end is V. P. */
#define FRAMEOPTION_START_VALUE_FOLLOWING 0x01000 /* start is V. F. */
#define FRAMEOPTION_END_VALUE_FOLLOWING 0x02000 /* end is V. F. */
#define FRAMEOPTION_START_VALUE \
(FRAMEOPTION_START_VALUE_PRECEDING | FRAMEOPTION_START_VALUE_FOLLOWING)
#define FRAMEOPTION_END_VALUE \
(FRAMEOPTION_END_VALUE_PRECEDING | FRAMEOPTION_END_VALUE_FOLLOWING)
#define FRAMEOPTION_DEFAULTS \
(FRAMEOPTION_RANGE | FRAMEOPTION_START_UNBOUNDED_PRECEDING | \
@ -799,6 +810,8 @@ typedef struct WindowClause
List *partitionClause; /* PARTITION BY list */
List *orderClause; /* ORDER BY list */
int frameOptions; /* frame_clause options, see WindowDef */
Node *startOffset; /* expression for starting bound, if any */
Node *endOffset; /* expression for ending bound, if any */
Index winref; /* ID referenced by window functions */
bool copiedOrder; /* did we copy orderClause from refname? */
} WindowClause;

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/nodes/plannodes.h,v 1.115 2010/01/02 16:58:04 momjian Exp $
* $PostgreSQL: pgsql/src/include/nodes/plannodes.h,v 1.116 2010/02/12 17:33:21 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -552,6 +552,8 @@ typedef struct WindowAgg
AttrNumber *ordColIdx; /* their indexes in the target list */
Oid *ordOperators; /* equality operators for ordering columns */
int frameOptions; /* frame_clause options, see WindowDef */
Node *startOffset; /* expression for starting bound, if any */
Node *endOffset; /* expression for ending bound, if any */
} WindowAgg;
/* ----------------

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/optimizer/planmain.h,v 1.124 2010/01/15 22:36:35 tgl Exp $
* $PostgreSQL: pgsql/src/include/optimizer/planmain.h,v 1.125 2010/02/12 17:33:21 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -62,7 +62,8 @@ extern WindowAgg *make_windowagg(PlannerInfo *root, List *tlist,
int numWindowFuncs, Index winref,
int partNumCols, AttrNumber *partColIdx, Oid *partOperators,
int ordNumCols, AttrNumber *ordColIdx, Oid *ordOperators,
int frameOptions, Plan *lefttree);
int frameOptions, Node *startOffset, Node *endOffset,
Plan *lefttree);
extern Group *make_group(PlannerInfo *root, List *tlist, List *qual,
int numGroupCols, AttrNumber *grpColIdx, Oid *grpOperators,
double numGroups,

View File

@ -11,7 +11,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/include/parser/kwlist.h,v 1.11 2010/02/08 04:33:55 tgl Exp $
* $PostgreSQL: pgsql/src/include/parser/kwlist.h,v 1.12 2010/02/12 17:33:21 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -53,7 +53,7 @@ PG_KEYWORD("authorization", AUTHORIZATION, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("backward", BACKWARD, UNRESERVED_KEYWORD)
PG_KEYWORD("before", BEFORE, UNRESERVED_KEYWORD)
PG_KEYWORD("begin", BEGIN_P, UNRESERVED_KEYWORD)
PG_KEYWORD("between", BETWEEN, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("between", BETWEEN, COL_NAME_KEYWORD)
PG_KEYWORD("bigint", BIGINT, COL_NAME_KEYWORD)
PG_KEYWORD("binary", BINARY, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("bit", BIT, COL_NAME_KEYWORD)

View File

@ -728,6 +728,193 @@ FROM (select distinct ten, four from tenk1) ss;
3 | 2 | 4 | 2
(20 rows)
SELECT sum(unique1) over (order by four range between current row and unbounded following),
unique1, four
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
45 | 0 | 0
45 | 8 | 0
45 | 4 | 0
33 | 5 | 1
33 | 9 | 1
33 | 1 | 1
18 | 6 | 2
18 | 2 | 2
10 | 3 | 3
10 | 7 | 3
(10 rows)
SELECT sum(unique1) over (rows between current row and unbounded following),
unique1, four
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
45 | 4 | 0
41 | 2 | 2
39 | 1 | 1
38 | 6 | 2
32 | 9 | 1
23 | 8 | 0
15 | 5 | 1
10 | 3 | 3
7 | 7 | 3
0 | 0 | 0
(10 rows)
SELECT sum(unique1) over (rows between 2 preceding and 2 following),
unique1, four
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
7 | 4 | 0
13 | 2 | 2
22 | 1 | 1
26 | 6 | 2
29 | 9 | 1
31 | 8 | 0
32 | 5 | 1
23 | 3 | 3
15 | 7 | 3
10 | 0 | 0
(10 rows)
SELECT sum(unique1) over (rows between 2 preceding and 1 preceding),
unique1, four
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
| 4 | 0
4 | 2 | 2
6 | 1 | 1
3 | 6 | 2
7 | 9 | 1
15 | 8 | 0
17 | 5 | 1
13 | 3 | 3
8 | 7 | 3
10 | 0 | 0
(10 rows)
SELECT sum(unique1) over (rows between 1 following and 3 following),
unique1, four
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
9 | 4 | 0
16 | 2 | 2
23 | 1 | 1
22 | 6 | 2
16 | 9 | 1
15 | 8 | 0
10 | 5 | 1
7 | 3 | 3
0 | 7 | 3
| 0 | 0
(10 rows)
SELECT sum(unique1) over (rows between unbounded preceding and 1 following),
unique1, four
FROM tenk1 WHERE unique1 < 10;
sum | unique1 | four
-----+---------+------
6 | 4 | 0
7 | 2 | 2
13 | 1 | 1
22 | 6 | 2
30 | 9 | 1
35 | 8 | 0
38 | 5 | 1
45 | 3 | 3
45 | 7 | 3
45 | 0 | 0
(10 rows)
SELECT sum(unique1) over (w range between current row and unbounded following),
unique1, four
FROM tenk1 WHERE unique1 < 10 WINDOW w AS (order by four);
sum | unique1 | four
-----+---------+------
45 | 0 | 0
45 | 8 | 0
45 | 4 | 0
33 | 5 | 1
33 | 9 | 1
33 | 1 | 1
18 | 6 | 2
18 | 2 | 2
10 | 3 | 3
10 | 7 | 3
(10 rows)
-- fail: not implemented yet
SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding),
unique1, four
FROM tenk1 WHERE unique1 < 10;
ERROR: RANGE PRECEDING is only supported with UNBOUNDED
LINE 1: SELECT sum(unique1) over (order by four range between 2::int...
^
SELECT first_value(unique1) over w,
nth_value(unique1, 2) over w AS nth_2,
last_value(unique1) over w, unique1, four
FROM tenk1 WHERE unique1 < 10
WINDOW w AS (order by four range between current row and unbounded following);
first_value | nth_2 | last_value | unique1 | four
-------------+-------+------------+---------+------
0 | 8 | 7 | 0 | 0
0 | 8 | 7 | 8 | 0
0 | 8 | 7 | 4 | 0
5 | 9 | 7 | 5 | 1
5 | 9 | 7 | 9 | 1
5 | 9 | 7 | 1 | 1
6 | 2 | 7 | 6 | 2
6 | 2 | 7 | 2 | 2
3 | 7 | 7 | 3 | 3
3 | 7 | 7 | 7 | 3
(10 rows)
SELECT sum(unique1) over
(rows (SELECT unique1 FROM tenk1 ORDER BY unique1 LIMIT 1) + 1 PRECEDING),
unique1
FROM tenk1 WHERE unique1 < 10;
sum | unique1
-----+---------
4 | 4
6 | 2
3 | 1
7 | 6
15 | 9
17 | 8
13 | 5
8 | 3
10 | 7
7 | 0
(10 rows)
CREATE TEMP VIEW v_window AS
SELECT i, sum(i) over (order by i rows between 1 preceding and 1 following) as sum_rows
FROM generate_series(1, 10) i;
SELECT * FROM v_window;
i | sum_rows
----+----------
1 | 3
2 | 6
3 | 9
4 | 12
5 | 15
6 | 18
7 | 21
8 | 24
9 | 27
10 | 19
(10 rows)
SELECT pg_get_viewdef('v_window');
pg_get_viewdef
---------------------------------------------------------------------------------------------------------------------------------
SELECT i.i, sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS sum_rows FROM generate_series(1, 10) i(i);
(1 row)
-- with UNION
SELECT count(*) OVER (PARTITION BY four) FROM (SELECT * FROM tenk1 UNION ALL SELECT * FROM tenk2)s LIMIT 0;
count

View File

@ -1,5 +1,5 @@
# ----------
# $PostgreSQL: pgsql/src/test/regress/parallel_schedule,v 1.60 2010/02/07 22:40:33 tgl Exp $
# $PostgreSQL: pgsql/src/test/regress/parallel_schedule,v 1.61 2010/02/12 17:33:21 tgl Exp $
#
# By convention, we put no more than twenty tests in any one parallel group;
# this limits the number of connections needed to run the tests.
@ -78,18 +78,19 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
test: privileges
test: misc
# rules cannot run concurrently with any test that creates a view
test: rules
# ----------
# Another group of parallel tests
# ----------
test: select_views portals_p2 rules foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap
test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap
# ----------
# Another group of parallel tests
# NB: temp.sql does a reconnect which transiently uses 2 connections,
# so keep this parallel group to at most 19 tests
# ----------
# "plpgsql" cannot run concurrently with "rules", nor can "plancache"
test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml
# run stats by itself because its delay may be insufficient under heavy load

View File

@ -1,4 +1,4 @@
# $PostgreSQL: pgsql/src/test/regress/serial_schedule,v 1.55 2010/01/28 23:21:13 petere Exp $
# $PostgreSQL: pgsql/src/test/regress/serial_schedule,v 1.56 2010/02/12 17:33:21 tgl Exp $
# This should probably be in an order similar to parallel_schedule.
test: tablespace
test: boolean
@ -89,9 +89,9 @@ test: namespace
test: prepared_xacts
test: privileges
test: misc
test: rules
test: select_views
test: portals_p2
test: rules
test: foreign_key
test: cluster
test: dependency

View File

@ -161,6 +161,58 @@ SELECT four, ten/4 as two,
last_value(ten/4) over (partition by four order by ten/4 rows between unbounded preceding and current row)
FROM (select distinct ten, four from tenk1) ss;
SELECT sum(unique1) over (order by four range between current row and unbounded following),
unique1, four
FROM tenk1 WHERE unique1 < 10;
SELECT sum(unique1) over (rows between current row and unbounded following),
unique1, four
FROM tenk1 WHERE unique1 < 10;
SELECT sum(unique1) over (rows between 2 preceding and 2 following),
unique1, four
FROM tenk1 WHERE unique1 < 10;
SELECT sum(unique1) over (rows between 2 preceding and 1 preceding),
unique1, four
FROM tenk1 WHERE unique1 < 10;
SELECT sum(unique1) over (rows between 1 following and 3 following),
unique1, four
FROM tenk1 WHERE unique1 < 10;
SELECT sum(unique1) over (rows between unbounded preceding and 1 following),
unique1, four
FROM tenk1 WHERE unique1 < 10;
SELECT sum(unique1) over (w range between current row and unbounded following),
unique1, four
FROM tenk1 WHERE unique1 < 10 WINDOW w AS (order by four);
-- fail: not implemented yet
SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding),
unique1, four
FROM tenk1 WHERE unique1 < 10;
SELECT first_value(unique1) over w,
nth_value(unique1, 2) over w AS nth_2,
last_value(unique1) over w, unique1, four
FROM tenk1 WHERE unique1 < 10
WINDOW w AS (order by four range between current row and unbounded following);
SELECT sum(unique1) over
(rows (SELECT unique1 FROM tenk1 ORDER BY unique1 LIMIT 1) + 1 PRECEDING),
unique1
FROM tenk1 WHERE unique1 < 10;
CREATE TEMP VIEW v_window AS
SELECT i, sum(i) over (order by i rows between 1 preceding and 1 following) as sum_rows
FROM generate_series(1, 10) i;
SELECT * FROM v_window;
SELECT pg_get_viewdef('v_window');
-- with UNION
SELECT count(*) OVER (PARTITION BY four) FROM (SELECT * FROM tenk1 UNION ALL SELECT * FROM tenk2)s LIMIT 0;