diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2008-12-31 00:08:39 +0000 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2008-12-31 00:08:39 +0000 |
commit | 8e8854daa2b4b3ef9e3fc1a56c79608a70018058 (patch) | |
tree | a6c4cf4b43acc764a38071843c10dc3edb3ca32e /src/backend/executor/nodeWindowAgg.c | |
parent | 0fb9be7acfb31ba38c1bbdd7883d5d03f6e261e5 (diff) | |
download | postgresql-8e8854daa2b4b3ef9e3fc1a56c79608a70018058.tar.gz postgresql-8e8854daa2b4b3ef9e3fc1a56c79608a70018058.zip |
Add some basic support for window frame clauses to the window-functions
patch. This includes the ability to force the frame to cover the whole
partition, and the ability to make the frame end exactly on the current row
rather than its last ORDER BY peer. Supporting any more of the full SQL
frame-clause syntax will require nontrivial hacking on the window aggregate
code, so it'll have to wait for 8.5 or beyond.
Diffstat (limited to 'src/backend/executor/nodeWindowAgg.c')
-rw-r--r-- | src/backend/executor/nodeWindowAgg.c | 436 |
1 files changed, 249 insertions, 187 deletions
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c index 37ef9a5e830..181e66a8964 100644 --- a/src/backend/executor/nodeWindowAgg.c +++ b/src/backend/executor/nodeWindowAgg.c @@ -27,7 +27,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/nodeWindowAgg.c,v 1.1 2008/12/28 18:53:55 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/nodeWindowAgg.c,v 1.2 2008/12/31 00:08:35 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -118,11 +118,10 @@ typedef struct WindowStatePerAggData bool initValueIsNull; /* - * cached value for non-moving frame + * cached value for current frame boundaries */ Datum resultValue; bool resultValueIsNull; - bool hasResult; /* * We need the len and byval info for the agg's input, result, and @@ -164,6 +163,10 @@ static void begin_partition(WindowAggState *winstate); static void spool_tuples(WindowAggState *winstate, int64 pos); static void release_partition(WindowAggState *winstate); +static bool row_is_in_frame(WindowAggState *winstate, int64 pos, + TupleTableSlot *slot); +static void update_frametailpos(WindowObject winobj, TupleTableSlot *slot); + static WindowStatePerAggData *initialize_peragg(WindowAggState *winstate, WindowFunc *wfunc, WindowStatePerAgg peraggstate); @@ -198,6 +201,7 @@ initialize_windowaggregate(WindowAggState *winstate, } peraggstate->transValueIsNull = peraggstate->initValueIsNull; peraggstate->noTransValue = peraggstate->initValueIsNull; + peraggstate->resultValueIsNull = true; } /* @@ -385,9 +389,7 @@ eval_windowaggregates(WindowAggState *winstate) int i; MemoryContext oldContext; ExprContext *econtext; - TupleTableSlot *first_peer_slot = winstate->first_peer_slot; - TupleTableSlot *slot; - bool first; + TupleTableSlot *agg_row_slot; numaggs = winstate->numaggs; if (numaggs == 0) @@ -397,35 +399,45 @@ eval_windowaggregates(WindowAggState *winstate) econtext = winstate->ss.ps.ps_ExprContext; /* - * We don't currently support explicitly-specified window frames. That - * means that the window frame always includes all the rows in the - * partition preceding and including the current row, and all its - * peers. As a special case, if there's no ORDER BY, all rows are peers, - * so the window frame includes all rows in the partition. + * Currently, we support only a subset of the SQL-standard window framing + * rules. In all the supported cases, the window frame always 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 strategy for evaluating aggregates: we run the transition + * function for each row added to the frame, and run the final function + * whenever we need the current aggregate value. This is considerably + * more efficient than the naive approach of re-running the entire + * aggregate calculation for each current row. It does assume that the + * final function doesn't damage the running transition value. (Some + * C-coded aggregates do that for efficiency's sake --- but they are + * supposed to do so only when their fcinfo->context is an AggState, not + * a WindowAggState.) * - * When there's peer rows, all rows in a peer group will have the same - * aggregate values. The values will be calculated when current position - * reaches the first peer row, and on all the following peer rows we will - * just return the saved results. + * 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 to the whole partition.) We optimize such cases by + * calculating the aggregate value once when we reach the first row of a + * peer group, and then returning the saved value for all subsequent rows. * - * 'aggregatedupto' keeps track of the last row that has already been - * accumulated for the aggregates. When the current row has no peers, - * aggregatedupto will be the same as the current row after this - * function. If there are peer rows, all peers will be accumulated in one - * call of this function, and aggregatedupto will be ahead of the current - * position. If there's no ORDER BY, and thus all rows are peers, the - * first call will aggregate all rows in the partition. + * 'aggregatedupto' keeps track of the first row that has not yet been + * 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 could implement sliding frames by recalculating - * the aggregate 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 all the rows - * that exit the frame. + * 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. */ /* * If we've already aggregated up through current row, reuse the - * saved result values + * saved result values. NOTE: this test works for the currently + * supported framing rules, but will need fixing when more are added. */ if (winstate->aggregatedupto > winstate->currentpos) { @@ -440,76 +452,61 @@ eval_windowaggregates(WindowAggState *winstate) } /* Initialize aggregates on first call for partition */ - for (i = 0; i < numaggs; i++) + if (winstate->currentpos == 0) { - peraggstate = &winstate->peragg[i]; - wfuncno = peraggstate->wfuncno; - if (!peraggstate->hasResult) + for (i = 0; i < numaggs; i++) + { + peraggstate = &winstate->peragg[i]; + wfuncno = peraggstate->wfuncno; initialize_windowaggregate(winstate, &winstate->perfunc[wfuncno], - &winstate->peragg[i]); - } - - /* - * If this is the first call for this partition, fetch the first row - * for comparing peer rows. On subsequent calls, we'll always read - * ahead until we reach the first non-peer row, and store that row in - * first_peer_slot, for use in the next call. - */ - if (TupIsNull(first_peer_slot)) - { - spool_tuples(winstate, winstate->aggregatedupto); - tuplestore_select_read_pointer(winstate->buffer, winstate->agg_ptr); - if (!tuplestore_gettupleslot(winstate->buffer, true, first_peer_slot)) - elog(ERROR, "unexpected end of tuplestore"); + peraggstate); + } } /* - * Advance until we reach the next non-peer row + * 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. */ - first = true; + agg_row_slot = winstate->agg_row_slot; for (;;) { - if (!first) + /* Fetch next row if we didn't already */ + if (TupIsNull(agg_row_slot)) { - /* Fetch the next row, and see if it's a peer */ spool_tuples(winstate, winstate->aggregatedupto); tuplestore_select_read_pointer(winstate->buffer, winstate->agg_ptr); - slot = winstate->temp_slot_1; - if (!tuplestore_gettupleslot(winstate->buffer, true, slot)) - break; - if (!are_peers(winstate, first_peer_slot, slot)) - { - ExecCopySlot(first_peer_slot, slot); - break; - } - } - else - { - /* - * On first iteration, just accumulate the tuple saved from - * last call - */ - slot = first_peer_slot; - first = false; + if (!tuplestore_gettupleslot(winstate->buffer, true, agg_row_slot)) + break; /* must be end of partition */ } - /* set tuple context for evaluation of aggregate arguments */ - winstate->tmpcontext->ecxt_outertuple = slot; + /* Exit loop (for now) if not in frame */ + if (!row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot)) + break; + /* Set tuple context for evaluation of aggregate arguments */ + winstate->tmpcontext->ecxt_outertuple = agg_row_slot; + + /* Accumulate row into the aggregates */ for (i = 0; i < numaggs; i++) { - wfuncno = winstate->peragg[i].wfuncno; - + peraggstate = &winstate->peragg[i]; + wfuncno = peraggstate->wfuncno; advance_windowaggregate(winstate, &winstate->perfunc[wfuncno], - &winstate->peragg[i]); - + peraggstate); } + /* Reset per-input-tuple context after each tuple */ ResetExprContext(winstate->tmpcontext); + + /* And advance the aggregated-row state */ winstate->aggregatedupto++; + ExecClearTuple(agg_row_slot); } /* @@ -526,37 +523,44 @@ eval_windowaggregates(WindowAggState *winstate) isnull = &econtext->ecxt_aggnulls[wfuncno]; finalize_windowaggregate(winstate, &winstate->perfunc[wfuncno], - peraggstate, result, isnull); + peraggstate, + result, isnull); /* - * save the result for the next (non-shrinking frame) call. + * save the result in case next row shares the same frame. + * + * XXX in some framing modes, eg ROWS/END_CURRENT_ROW, we can know + * in advance that the next row can't possibly share the same frame. + * Is it worth detecting that and skipping this code? */ - if (!peraggstate->resulttypeByVal && !*isnull) + if (!peraggstate->resulttypeByVal) { /* - * clear old resultValue in order not to leak memory. + * clear old resultValue in order not to leak memory. (Note: + * the new result can't possibly be the same datum as old + * resultValue, because we never passed it to the trans function.) */ - if (peraggstate->hasResult && - (DatumGetPointer(peraggstate->resultValue) != - DatumGetPointer(*result)) && - !peraggstate->resultValueIsNull) + if (!peraggstate->resultValueIsNull) pfree(DatumGetPointer(peraggstate->resultValue)); /* * If pass-by-ref, copy it into our global context. */ - oldContext = MemoryContextSwitchTo(winstate->wincontext); - peraggstate->resultValue = datumCopy(*result, - peraggstate->resulttypeByVal, - peraggstate->resulttypeLen); - MemoryContextSwitchTo(oldContext); + if (!*isnull) + { + oldContext = MemoryContextSwitchTo(winstate->wincontext); + peraggstate->resultValue = + datumCopy(*result, + peraggstate->resulttypeByVal, + peraggstate->resulttypeLen); + MemoryContextSwitchTo(oldContext); + } } else { peraggstate->resultValue = *result; } peraggstate->resultValueIsNull = *isnull; - peraggstate->hasResult = true; } } @@ -620,14 +624,16 @@ begin_partition(WindowAggState *winstate) int i; winstate->partition_spooled = false; + winstate->frametail_valid = false; winstate->spooled_rows = 0; winstate->currentpos = 0; winstate->frametailpos = -1; winstate->aggregatedupto = 0; + ExecClearTuple(winstate->agg_row_slot); /* * If this is the very first partition, we need to fetch the first - * input row to store in it. + * input row to store in first_part_slot. */ if (TupIsNull(winstate->first_part_slot)) { @@ -775,35 +781,138 @@ release_partition(WindowAggState *winstate) /* Release any partition-local state of this window function */ if (perfuncstate->winobj) perfuncstate->winobj->localmem = NULL; - - /* Reset agg result cache */ - if (perfuncstate->plain_agg) - { - int aggno = perfuncstate->aggno; - WindowStatePerAggData *peraggstate = &winstate->peragg[aggno]; - - peraggstate->resultValueIsNull = true; - peraggstate->hasResult = false; - } } /* * Release all partition-local memory (in particular, any partition-local - * state or aggregate temp data that we might have trashed our pointers - * to in the above loop). We don't rely on retail pfree because some + * state that we might have trashed our pointers to in the above loop, and + * 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); - /* Ensure eval_windowaggregates will see next call as partition start */ - ExecClearTuple(winstate->first_peer_slot); - if (winstate->buffer) tuplestore_end(winstate->buffer); winstate->buffer = NULL; winstate->partition_spooled = false; } +/* + * row_is_in_frame + * Determine whether a row is in the current row's window frame according + * to our window framing rule + * + * The caller must have already determined that the row is in the partition + * and fetched it into a slot. This function just encapsulates the framing + * rules. + */ +static bool +row_is_in_frame(WindowAggState *winstate, int64 pos, TupleTableSlot *slot) +{ + WindowAgg *node = (WindowAgg *) winstate->ss.ps.plan; + int frameOptions = node->frameOptions; + + Assert(pos >= 0); /* else caller error */ + + /* 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) + return true; + + /* Else frame tail mode must be CURRENT ROW */ + Assert(frameOptions & FRAMEOPTION_END_CURRENT_ROW); + + /* if row is current row or a predecessor, it must be in frame */ + if (pos <= winstate->currentpos) + return true; + + /* In ROWS mode, *only* such rows are in frame */ + if (frameOptions & FRAMEOPTION_ROWS) + return false; + + /* Else must be RANGE mode */ + Assert(frameOptions & FRAMEOPTION_RANGE); + + /* In frame iff it's a peer of current row */ + return are_peers(winstate, slot, winstate->ss.ss_ScanTupleSlot); +} + +/* + * 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. + */ +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; + + 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) + { + 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) + { + winstate->frametailpos = winstate->currentpos; + winstate->frametail_valid = true; + return; + } + + /* Else must be RANGE mode */ + Assert(frameOptions & FRAMEOPTION_RANGE); + + /* 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, 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++; + } + winstate->frametailpos = ftnext - 1; + winstate->frametail_valid = true; +} + /* ----------------- * ExecWindowAgg @@ -849,11 +958,14 @@ restart: { /* Initialize for first partition and set current row = 0 */ begin_partition(winstate); + /* If there are no input rows, we'll detect that and exit below */ } else { /* Advance current row within partition */ winstate->currentpos++; + /* This might mean that the frame tail moves, too */ + winstate->frametail_valid = false; } /* @@ -887,8 +999,7 @@ restart: ResetExprContext(econtext); /* - * Read the current row from the tuplestore, and save in ScanTupleSlot - * for possible use by WinGetFuncArgCurrent or the final projection step. + * Read the current row from the tuplestore, and save in ScanTupleSlot. * (We can't rely on the outerplan's output slot because we may have to * read beyond the current row.) * @@ -1002,7 +1113,7 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags) ExecInitScanTupleSlot(estate, &winstate->ss); ExecInitResultTupleSlot(estate, &winstate->ss.ps); winstate->first_part_slot = ExecInitExtraTupleSlot(estate); - winstate->first_peer_slot = ExecInitExtraTupleSlot(estate); + winstate->agg_row_slot = ExecInitExtraTupleSlot(estate); winstate->temp_slot_1 = ExecInitExtraTupleSlot(estate); winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate); @@ -1031,7 +1142,7 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags) ExecSetSlotDescriptor(winstate->first_part_slot, winstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor); - ExecSetSlotDescriptor(winstate->first_peer_slot, + ExecSetSlotDescriptor(winstate->agg_row_slot, winstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor); ExecSetSlotDescriptor(winstate->temp_slot_1, winstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor); @@ -1081,6 +1192,10 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags) AclResult aclresult; int i; + if (wfunc->winref != node->winref) /* planner screwed up? */ + elog(ERROR, "WindowFunc with winref %u assigned to WindowAgg with winref %u", + wfunc->winref, node->winref); + /* Look for a previous duplicate window function */ for (i = 0; i <= wfuncno; i++) { @@ -1183,7 +1298,7 @@ ExecEndWindowAgg(WindowAggState *node) ExecClearTuple(node->ss.ss_ScanTupleSlot); ExecClearTuple(node->first_part_slot); - ExecClearTuple(node->first_peer_slot); + ExecClearTuple(node->agg_row_slot); ExecClearTuple(node->temp_slot_1); ExecClearTuple(node->temp_slot_2); @@ -1219,7 +1334,7 @@ ExecReScanWindowAgg(WindowAggState *node, ExprContext *exprCtxt) /* release all temp tuples, but especially first_part_slot */ ExecClearTuple(node->ss.ss_ScanTupleSlot); ExecClearTuple(node->first_part_slot); - ExecClearTuple(node->first_peer_slot); + ExecClearTuple(node->agg_row_slot); ExecClearTuple(node->temp_slot_1); ExecClearTuple(node->temp_slot_2); @@ -1411,6 +1526,8 @@ GetAggInitVal(Datum textInitVal, Oid transtype) /* * are_peers * compare two rows to see if they are equal according to the ORDER BY clause + * + * NB: this does not consider the window frame mode. */ static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1, @@ -1430,7 +1547,8 @@ are_peers(WindowAggState *winstate, TupleTableSlot *slot1, /* * window_gettupleslot - * Fetch the pos'th tuple of the current partition into the slot + * Fetch the pos'th tuple of the current partition into the slot, + * using the winobj's read pointer * * Returns true if successful, false if no such row */ @@ -1582,6 +1700,8 @@ WinSetMarkPosition(WindowObject winobj, int64 markpos) * WinRowsArePeers * Compare two rows (specified by absolute position in window) to see * if they are equal according to the ORDER BY clause. + * + * NB: this does not consider the window frame mode. */ bool WinRowsArePeers(WindowObject winobj, int64 pos1, int64 pos2) @@ -1593,7 +1713,6 @@ WinRowsArePeers(WindowObject winobj, int64 pos1, int64 pos2) bool res; Assert(WindowObjectIsValid(winobj)); - winstate = winobj->winstate; node = (WindowAgg *) winstate->ss.ps.plan; @@ -1642,27 +1761,28 @@ WinGetFuncArgInPartition(WindowObject winobj, int argno, int relpos, int seektype, bool set_mark, bool *isnull, bool *isout) { + WindowAggState *winstate; ExprContext *econtext; TupleTableSlot *slot; bool gottuple; int64 abs_pos; Assert(WindowObjectIsValid(winobj)); - - econtext = winobj->winstate->ss.ps.ps_ExprContext; - slot = winobj->winstate->temp_slot_1; + winstate = winobj->winstate; + econtext = winstate->ss.ps.ps_ExprContext; + slot = winstate->temp_slot_1; switch (seektype) { case WINDOW_SEEK_CURRENT: - abs_pos = winobj->winstate->currentpos + relpos; + abs_pos = winstate->currentpos + relpos; break; case WINDOW_SEEK_HEAD: abs_pos = relpos; break; case WINDOW_SEEK_TAIL: - spool_tuples(winobj->winstate, -1); - abs_pos = winobj->winstate->spooled_rows - 1 + relpos; + spool_tuples(winstate, -1); + abs_pos = winstate->spooled_rows - 1 + relpos; break; default: elog(ERROR, "unrecognized window seek type: %d", seektype); @@ -1670,10 +1790,7 @@ WinGetFuncArgInPartition(WindowObject winobj, int argno, break; } - if (abs_pos >= 0) - gottuple = window_gettupleslot(winobj, abs_pos, slot); - else - gottuple = false; + gottuple = window_gettupleslot(winobj, abs_pos, slot); if (!gottuple) { @@ -1717,34 +1834,28 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno, int relpos, int seektype, bool set_mark, bool *isnull, bool *isout) { + WindowAggState *winstate; ExprContext *econtext; TupleTableSlot *slot; bool gottuple; int64 abs_pos; - int64 frametailpos; Assert(WindowObjectIsValid(winobj)); - - /* if no ordering columns, partition and frame are the same thing */ - if (((WindowAgg *) winobj->winstate->ss.ps.plan)->ordNumCols == 0) - return WinGetFuncArgInPartition(winobj, argno, relpos, seektype, - set_mark, isnull, isout); - - econtext = winobj->winstate->ss.ps.ps_ExprContext; - slot = winobj->winstate->temp_slot_1; - frametailpos = winobj->winstate->frametailpos; + winstate = winobj->winstate; + econtext = winstate->ss.ps.ps_ExprContext; + slot = winstate->temp_slot_1; switch (seektype) { case WINDOW_SEEK_CURRENT: - abs_pos = winobj->winstate->currentpos + relpos; + abs_pos = winstate->currentpos + relpos; break; case WINDOW_SEEK_HEAD: abs_pos = relpos; break; case WINDOW_SEEK_TAIL: - /* abs_pos is calculated later */ - abs_pos = 0; /* keep compiler quiet */ + update_frametailpos(winobj, slot); + abs_pos = winstate->frametailpos + relpos; break; default: elog(ERROR, "unrecognized window seek type: %d", seektype); @@ -1752,58 +1863,9 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno, break; } - /* - * Seek for frame tail. If the tail position is before current, - * always check if the tail is after the current or not. - */ - if (frametailpos <= winobj->winstate->currentpos) - { - int64 add = 1; - - for (;;) - { - spool_tuples(winobj->winstate, winobj->winstate->currentpos + add); - if (winobj->winstate->spooled_rows > winobj->winstate->currentpos + add) - { - /* - * When seektype is not TAIL, we may optimize not to - * spool unnecessary tuples. In TAIL mode, we need to search - * until we find a row that's definitely not a peer. - */ - if (!WinRowsArePeers(winobj, winobj->winstate->currentpos, - winobj->winstate->currentpos + add) || - (seektype != WINDOW_SEEK_TAIL && - winobj->winstate->currentpos + add < abs_pos)) - break; - add++; - } - else - { - /* - * If hit the partition end, the last row is the frame tail. - */ - break; - } - } - frametailpos = winobj->winstate->currentpos + add - 1; - winobj->winstate->frametailpos = frametailpos; - } - - if (seektype == WINDOW_SEEK_TAIL) - { - abs_pos = frametailpos + relpos; - } - - /* - * If there is an ORDER BY (we don't support other window frame - * specifications yet), the frame runs from first row of the partition - * to the last peer of the current row. Otherwise the frame is the - * whole partition. - */ - if (abs_pos < 0 || abs_pos > frametailpos) - gottuple = false; - else - gottuple = window_gettupleslot(winobj, abs_pos, slot); + gottuple = window_gettupleslot(winobj, abs_pos, slot); + if (gottuple) + gottuple = row_is_in_frame(winstate, abs_pos, slot); if (!gottuple) { |