From 6be6ca9bdfb97f8f5fd9052bc9b8d3eea61e4549 Mon Sep 17 00:00:00 2001 From: jian he Date: Wed, 21 Oct 2026 14:33:38 +0800 Subject: [PATCH v55 1/1] Separate resolving the reduced frame from consuming it ExecWindowAgg() resolves the reduced frame for every row it emits, by calling ensure_reduced_frame() before any window function or aggregate runs; that is the only place the match is driven. Everything after it should only consumes the recorded match through get_reduced_frame_status(), which reports how any row position relates to it. row_is_in_reduced_frame should be removed. row_is_in_reduced_frame() blurred that division: it re-ran ensure_reduced_frame() and then get_reduced_frame_status() again, so consumers looked as if they might resolve the match themselves. While at it, I found the change to WinGetFuncArgInFrame() hard to review: the new WinGetSlotInFrame() mixes RPR logic into the existing frame code. IGNORE NULLS already it's own function: ignorenulls_getfuncarginframe(). Similarly we can left WinGetFuncArgInFrame() unchanged and moved the RPR logic into a dedicated function, WinGetFuncArgInReducedFrame(). That duplicates some code, but the result is much cleaner, and I think the duplication is worth it. --- src/backend/executor/README.rpr | 77 ++-- src/backend/executor/nodeWindowAgg.c | 576 ++++++++++++--------------- 2 files changed, 300 insertions(+), 353 deletions(-) diff --git a/src/backend/executor/README.rpr b/src/backend/executor/README.rpr index 16e158fdcf..7612bada04 100644 --- a/src/backend/executor/README.rpr +++ b/src/backend/executor/README.rpr @@ -1265,14 +1265,20 @@ Chapter VII NFA Execution: 3-Phase Model VII-1. Entry Point and Overall Flow -ExecWindowAgg() drives the match once for every row of the scan by -calling ensure_reduced_frame(), so the match tracks the row scan rather -than frame access. That function is idempotent: it calls -update_frameheadpos() and update_reduced_frame() only when -get_reduced_frame_status() reports the row as RF_NOT_DETERMINED. -row_is_in_reduced_frame(), which the frame access paths call to classify -a row, goes through ensure_reduced_frame() first and then reads the -recorded result. +ExecWindowAgg() resolves the reduced frame for every row it emits, by +calling ensure_reduced_frame() before any window function or aggregate +runs. This is the *only* place the match is driven. + +The labor is split in two: ExecWindowAgg() produces the reduced frame range, and +everything downstream only consumes it. The aggregate loop in +eval_windowaggregates() and the frame access in WinGetFuncArgInReducedFrame() call +get_reduced_frame_status() to classify a row against the reduced frame range, +and must never resolve or modify it themselves. + +Driving the match from ExecWindowAgg() also keeps it in step with the row scan. +The RPR machinery expects rows to be resolved in order; if resolution were left +to frame access, a function that never reads its frame (nth_value() with a NULL +offset) would skip a row and leave the match state one row behind. Flow of update_reduced_frame(): @@ -2145,30 +2151,22 @@ not been evaluated yet (RF_NOT_DETERMINED). A row's status against the current match result can be obtained by calling get_reduced_frame_status(). -The frame accessors in nodeWindowAgg.c -- WinGetSlotInFrame() and -ignorenulls_getfuncarginframe(), the latter serving IGNORE NULLS -- -clip every seek to the reduced frame. Each asks -row_is_in_reduced_frame() about frameheadpos and takes the length it -returns as the frame's extent: a WINDOW_SEEK_HEAD relpos at or past -that length is out of frame, as is a WINDOW_SEEK_TAIL relpos whose -backward distance reaches it, the tail case landing on -frameheadpos + relpos + length - 1. That arithmetic assumes the reduced -frame is one contiguous run beginning at frameheadpos, which holds only -because RPR rejects EXCLUDE (III-1) and pins the frame start at -CURRENT ROW; relaxing either restriction means revisiting both -accessors. The same guarantee is why a WINDOW_SEEK_TAIL access under -RPR marks frameheadpos rather than the accessed row: the frame start -never moves backwards, so the mark can never end up ahead of a row a -later fetch still needs. +The frame accessors clip every seek to the reduced frame, the rows +[frameheadpos, frameheadpos + match length). That arithmetic assumes the +reduced frame is one contiguous run starting at frameheadpos, which holds only +because RPR rejects EXCLUDE and pins the frame start at CURRENT ROW; relaxing +either restriction means revisiting both accessors: WinGetFuncArgInFrame() +hands an RPR window to WinGetFuncArgInReducedFrame(), and IGNORE NULLS goes +through ignorenulls_getfuncarginframe(). The same guarantee lets a +WINDOW_SEEK_TAIL access under RPR mark frameheadpos rather than the accessed +row: the frame start never moves backwards, so the mark cannot get ahead of a +row a later fetch needs. An empty match is still recorded as a success: update_reduced_frame() passes it to ExecRPRRecordContextSuccess() with length 0, so EXPLAIN -counts it under NFA Matched. The frame accessors never see that 0, -though. row_is_in_reduced_frame() returns -1 for RF_EMPTY_MATCH exactly -as for RF_UNMATCHED, so the row's reduced frame is empty. It cannot -return 0 there: 0 is its "RPR not defined" answer, which the accessors -and the aggregation loop of XI-5 read as the unclipped full frame. -RF_FRAME_HEAD returns the match length (>= 1) and RF_SKIPPED returns -2. +counts it under NFA Matched. For the frame accessors it is no different +from no match: get_reduced_frame_status() reports RF_EMPTY_MATCH, not +RF_FRAME_HEAD, so the row's reduced frame is empty. XI-2. AFTER MATCH SKIP @@ -2245,15 +2243,18 @@ XI-4. Bounded Frame Handling XI-5. Window Aggregates over the Reduced Frame - RPR switches off the moving-aggregate path. eval_windowaggregates() - marks every plain aggregate for restart on every row when - rpr_is_defined(), because one row's reduced frame need not overlap the - next row's at all, and overlap is the assumption an inverse transition - function rests on. The aggregation loop then reads the reduced frame - itself to find where to stop: it stops when currentpos is determined - but aggregatedupto is not, when row_is_in_reduced_frame() reports an - unmatched row, or when the base row of the aggregation turns out to be - an interior (RF_SKIPPED) row of a match. + RPR switches off the moving-aggregate path. Normally, when the + current row moves from N to N+1, the new frame is the old frame minus + a few rows at the front plus a few rows at the end. PostgreSQL + exploits that: instead of recomputing the aggregate, it subtracts the + dropped rows with the aggregate's inverse transition function and adds + the new ones. + + With RPR the frame of row N+1 is the match that starts at row N+1, + which can be any set of rows; it is not the frame of row N with some + rows dropped. There is nothing to subtract, so + eval_windowaggregates() restarts every plain aggregate on every row + when rpr_is_defined(). Chapter XII Worked Example: Full Execution Trace ============================================================================ diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c index 01cec0f849..8d76b31ef9 100644 --- a/src/backend/executor/nodeWindowAgg.c +++ b/src/backend/executor/nodeWindowAgg.c @@ -227,9 +227,10 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype); static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1, TupleTableSlot *slot2); -static int WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot, - int relpos, int seektype, bool set_mark, - bool *isnull, bool *isout); +static Datum WinGetFuncArgInReducedFrame(WindowObject winobj, int argno, + int relpos, int seektype, + bool set_mark, bool *isnull, + bool *isout); static bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot); @@ -249,7 +250,6 @@ static uint8 get_notnull_info(WindowObject winobj, static void put_notnull_info(WindowObject winobj, int64 pos, int argno, bool isnull); static bool rpr_is_defined(WindowAggState *winstate); -static int64 row_is_in_reduced_frame(WindowObject winobj, int64 pos); static void ensure_reduced_frame(WindowObject winobj, int64 pos); static void clear_reduced_frame(WindowAggState *winstate); @@ -1048,32 +1048,44 @@ eval_windowaggregates(WindowAggState *winstate) if (rpr_is_defined(winstate)) { + int status; + /* - * If currentpos is already decided but aggregatedupto is not yet - * determined, we've passed the last reduced frame. + * ExecWindowAgg already resolved the current row's match status */ - if (get_reduced_frame_status(winstate, winstate->currentpos) - != RF_NOT_DETERMINED && - get_reduced_frame_status(winstate, winstate->aggregatedupto) - == RF_NOT_DETERMINED) - break; + Assert(get_reduced_frame_status(winstate, winstate->currentpos) != + RF_NOT_DETERMINED); + + status = get_reduced_frame_status(winstate, + winstate->aggregatedupto); /* - * Calculate the reduced frame for aggregatedupto. + * Exit loop once aggregatedupto leaves the current row's reduced + * frame: it has passed the match's last row, or the current row + * begins no match at all. */ - ret = row_is_in_reduced_frame(winstate->agg_winobj, - winstate->aggregatedupto); - if (ret == -1) /* unmatched row */ + if (status == RF_NOT_DETERMINED || + status == RF_UNMATCHED || + status == RF_EMPTY_MATCH) break; /* - * Check if current row is inside a match but not the head - * (skipped), and it's the base row for aggregation. + * The current row may lie inside a match that an earlier row + * started. We still get here for it, because every output row + * computes its own aggregates and row_is_in_frame() above checks + * only the physical frame. Such a row cannot start a match of + * its own, so its reduced frame is empty and nothing is + * aggregated. + * + * Only the current row needs this check, and it is the row being + * looked at while nothing has been aggregated yet, that is, while + * aggregatedupto still equals aggregatedbase. */ - if (get_reduced_frame_status(winstate, - winstate->aggregatedupto) == RF_SKIPPED && + if (status == RF_SKIPPED && winstate->aggregatedupto == winstate->aggregatedbase) break; + + /* RF_FRAME_HEAD, or a later row of the match: aggregate it */ } /* Set tuple context for evaluation of aggregate arguments */ @@ -2567,6 +2579,8 @@ ExecWindowAgg(PlanState *pstate) elog(ERROR, "unexpected end of tuplestore"); } + Assert(!rpr_is_defined(winstate) || winstate->status == WINDOWAGG_RUN); + /* don't evaluate the window functions when we're in pass-through mode */ if (winstate->status == WINDOWAGG_RUN) { @@ -2580,10 +2594,9 @@ ExecWindowAgg(PlanState *pstate) clear_reduced_frame(winstate); /* - * Drive the row pattern match every row, so it tracks the row - * scan rather than frame access: a window function that skips - * the frame (e.g. nth_value() with a NULL offset) must not - * leave the match state behind currentpos. + * Resolve the current row's match here, once per row and + * before any window function or aggregate runs. This is the + * only place the match is driven. */ Assert(winstate->nav_winobj != NULL); ensure_reduced_frame(winstate->nav_winobj, @@ -3819,13 +3832,6 @@ ignorenulls_getfuncarginframe(WindowObject winobj, int argno, /* rejecting relpos > 0 is easy and simplifies code below */ if (relpos > 0) goto out_of_frame; - - /* - * RPR cares about frame head pos. Need to call - * update_frameheadpos - */ - update_frameheadpos(winstate); - update_frametailpos(winstate); abs_pos = winstate->frametailpos - 1; mark_pos = 0; /* keep compiler quiet */ @@ -3838,36 +3844,33 @@ ignorenulls_getfuncarginframe(WindowObject winobj, int argno, } /* - * Get the next nonnull value in the frame, moving forward or backward - * until we find a value or reach the frame's end. + * Under RPR, search only the reduced frame: the match that starts at the + * current row, if any. */ - - /* - * Check whether current row is in reduced frame. - */ - num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos); - if (num_reduced_frame < 0) /* unmatched or skipped row */ - goto out_of_frame; - else if (num_reduced_frame > 0) /* the first row of the reduced frame */ + if (rpr_is_defined(winstate)) { - /* - * Early check if row could be out of reduced frame. When RPR is - * enabled, EXCLUDE clause cannot be specified and the frame is always - * contiguous. So we can safely perform the following checks. Note, - * however, it is possible that a row is out of reduced frame if - * there's a NULL in the middle. So we need to check it in the - * following do loop. - */ - if (seektype == WINDOW_SEEK_HEAD && relpos >= num_reduced_frame) + int status; + + /* both seek types need the frame head of the reduced frame */ + update_frameheadpos(winstate); + + status = get_reduced_frame_status(winstate, winstate->frameheadpos); + + /* ExecWindowAgg should already resolved the reduced frame */ + Assert(status != RF_NOT_DETERMINED); + + if (status != RF_FRAME_HEAD) goto out_of_frame; - if (seektype == WINDOW_SEEK_TAIL) - { - if (notnull_relpos >= num_reduced_frame) - goto out_of_frame; - /* not out of reduced frame. Set abspos as a starting point */ + num_reduced_frame = winstate->rpr_match_length; + + /* the reduced frame has only num_reduced_frame rows */ + if (notnull_relpos >= num_reduced_frame) + goto out_of_frame; + + /* a tail search starts at the reduced frame's last row */ + if (seektype == WINDOW_SEEK_TAIL) abs_pos = winstate->frameheadpos + num_reduced_frame - 1; - } } do @@ -4461,69 +4464,6 @@ rpr_is_defined(WindowAggState *winstate) return winstate->rpPattern != NULL; } -/* - * ----------------- - * row_is_in_reduced_frame - * Determine whether a row is in the current row's reduced window frame - * according to row pattern matching - * - * If pos is not yet determined, the match is first driven forward by - * ensure_reduced_frame(). - * - * Returns: - * = 0, RPR is not defined. - * >0, if the row is the first in the reduced frame. Return the number of rows - * in the reduced frame. - * -1, if the row is unmatched or starts an empty match - * -2, if the row is inside the current match but is not its first row (an - * interior row of the match) - * ----------------- - */ -static int64 -row_is_in_reduced_frame(WindowObject winobj, int64 pos) -{ - WindowAggState *winstate = winobj->winstate; - int state; - int64 rtn; - - if (!rpr_is_defined(winstate)) - { - /* - * RPR is not defined. Assume that we are always in the reduced window - * frame. - */ - rtn = 0; - return rtn; - } - - ensure_reduced_frame(winobj, pos); - - state = get_reduced_frame_status(winstate, pos); - - switch (state) - { - case RF_FRAME_HEAD: - rtn = winstate->rpr_match_length; - break; - - case RF_SKIPPED: - rtn = -2; - break; - - case RF_UNMATCHED: - case RF_EMPTY_MATCH: - rtn = -1; - break; - - default: - elog(ERROR, "unrecognized state: %d at: " INT64_FORMAT, - state, pos); - break; - } - - return rtn; -} - /* * ensure_reduced_frame * Drive the row pattern match forward so pos is resolved. @@ -4900,231 +4840,80 @@ rpr_prepare_row(WindowObject winobj, int64 pos, RPRVarMatch *varMatched) } /* - * WinGetSlotInFrame - * slot: TupleTableSlot to store the result - * relpos: signed rowcount offset from the seek position - * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL - * set_mark: If the row is found/in frame and set_mark is true, the mark is - * moved to the row as a side-effect. - * isnull: output argument, receives isnull status of result - * isout: output argument, set to indicate whether target row position - * is out of frame (can pass NULL if caller doesn't care about this) + * WinGetFuncArgInReducedFrame + * WinGetFuncArgInFrame() for a row pattern window. * - * Returns 0 if we successfully got the slot, or nonzero if out of frame. - * (isout is also set in the latter case.) + * A row pattern frame starts at CURRENT ROW and allows no EXCLUDE, so the + * reduced frame is the contiguous run [frameheadpos, frameheadpos + length) + * of the match that starts at the current row. ExecWindowAgg() has already + * resolved that match. */ -static int -WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot, - int relpos, int seektype, bool set_mark, - bool *isnull, bool *isout) +static Datum +WinGetFuncArgInReducedFrame(WindowObject winobj, int argno, + int relpos, int seektype, bool set_mark, + bool *isnull, bool *isout) { - WindowAggState *winstate; - int64 abs_pos; - int64 mark_pos; - int64 num_reduced_frame; + WindowAggState *winstate = winobj->winstate; + ExprContext *econtext = winstate->ss.ps.ps_ExprContext; + TupleTableSlot *slot = winstate->temp_slot_1; + int status; + int64 length; + int64 abs_pos = 0; + int64 mark_pos = 0; - Assert(WindowObjectIsValid(winobj)); - winstate = winobj->winstate; + update_frameheadpos(winstate); + status = get_reduced_frame_status(winstate, + winstate->frameheadpos); + + Assert(status != RF_NOT_DETERMINED); + + /* only a match head has a reduced frame */ + if (status != RF_FRAME_HEAD) + goto out_of_frame; + + length = winstate->rpr_match_length; switch (seektype) { case WINDOW_SEEK_CURRENT: elog(ERROR, "WINDOW_SEEK_CURRENT is not supported for WinGetFuncArgInFrame"); - abs_pos = mark_pos = 0; /* keep compiler quiet */ break; case WINDOW_SEEK_HEAD: - /* rejecting relpos < 0 is easy and simplifies code below */ - if (relpos < 0) - goto out_of_frame; - update_frameheadpos(winstate); - abs_pos = winstate->frameheadpos + relpos; - mark_pos = abs_pos; - - /* - * Account for exclusion option if one is active, but advance only - * abs_pos not mark_pos. This prevents changes of the current - * row's peer group from resulting in trying to fetch a row before - * some previous mark position. - * - * Note that in some corner cases such as current row being - * outside frame, these calculations are theoretically too simple, - * but it doesn't matter because we'll end up deciding the row is - * out of frame. We do not attempt to avoid fetching rows past - * end of frame; that would happen in some cases anyway. - */ - switch (winstate->frameOptions & FRAMEOPTION_EXCLUSION) - { - case 0: - /* no adjustment needed */ - break; - case FRAMEOPTION_EXCLUDE_CURRENT_ROW: - if (abs_pos >= winstate->currentpos && - winstate->currentpos >= winstate->frameheadpos) - abs_pos++; - break; - case FRAMEOPTION_EXCLUDE_GROUP: - update_grouptailpos(winstate); - if (abs_pos >= winstate->groupheadpos && - winstate->grouptailpos > winstate->frameheadpos) - { - int64 overlapstart = Max(winstate->groupheadpos, - winstate->frameheadpos); - - abs_pos += winstate->grouptailpos - overlapstart; - } - break; - case FRAMEOPTION_EXCLUDE_TIES: - update_grouptailpos(winstate); - if (abs_pos >= winstate->groupheadpos && - winstate->grouptailpos > winstate->frameheadpos) - { - int64 overlapstart = Max(winstate->groupheadpos, - winstate->frameheadpos); - - if (abs_pos == overlapstart) - abs_pos = winstate->currentpos; - else - abs_pos += winstate->grouptailpos - overlapstart - 1; - } - break; - default: - elog(ERROR, "unrecognized frame option state: 0x%x", - winstate->frameOptions); - break; - } - num_reduced_frame = row_is_in_reduced_frame(winobj, - winstate->frameheadpos); - if (num_reduced_frame < 0) + if (relpos < 0 || relpos >= length) goto out_of_frame; - else if (num_reduced_frame > 0) - if (relpos >= num_reduced_frame) - goto out_of_frame; + abs_pos = mark_pos = winstate->frameheadpos + relpos; break; case WINDOW_SEEK_TAIL: - /* rejecting relpos > 0 is easy and simplifies code below */ - if (relpos > 0) + if (relpos > 0 || relpos <= -length) goto out_of_frame; - - /* - * RPR cares about frame head pos. Need to call - * update_frameheadpos - */ - update_frameheadpos(winstate); - - update_frametailpos(winstate); - abs_pos = winstate->frametailpos - 1 + relpos; - - /* - * Account for exclusion option if one is active. If there is no - * exclusion, we can safely set the mark at the accessed row. But - * if there is, we can only mark the frame start, because we can't - * be sure how far back in the frame the exclusion might cause us - * to fetch in future. Furthermore, we have to actually check - * against frameheadpos here, since it's unsafe to try to fetch a - * row before frame start if the mark might be there already. - */ - switch (winstate->frameOptions & FRAMEOPTION_EXCLUSION) - { - case 0: - /* no adjustment needed */ - mark_pos = abs_pos; - break; - case FRAMEOPTION_EXCLUDE_CURRENT_ROW: - if (abs_pos <= winstate->currentpos && - winstate->currentpos < winstate->frametailpos) - abs_pos--; - update_frameheadpos(winstate); - if (abs_pos < winstate->frameheadpos) - goto out_of_frame; - mark_pos = winstate->frameheadpos; - break; - case FRAMEOPTION_EXCLUDE_GROUP: - update_grouptailpos(winstate); - if (abs_pos < winstate->grouptailpos && - winstate->groupheadpos < winstate->frametailpos) - { - int64 overlapend = Min(winstate->grouptailpos, - winstate->frametailpos); - - abs_pos -= overlapend - winstate->groupheadpos; - } - update_frameheadpos(winstate); - if (abs_pos < winstate->frameheadpos) - goto out_of_frame; - mark_pos = winstate->frameheadpos; - break; - case FRAMEOPTION_EXCLUDE_TIES: - update_grouptailpos(winstate); - if (abs_pos < winstate->grouptailpos && - winstate->groupheadpos < winstate->frametailpos) - { - int64 overlapend = Min(winstate->grouptailpos, - winstate->frametailpos); - - if (abs_pos == overlapend - 1) - abs_pos = winstate->currentpos; - else - abs_pos -= overlapend - 1 - winstate->groupheadpos; - } - update_frameheadpos(winstate); - if (abs_pos < winstate->frameheadpos) - goto out_of_frame; - mark_pos = winstate->frameheadpos; - break; - default: - elog(ERROR, "unrecognized frame option state: 0x%x", - winstate->frameOptions); - mark_pos = 0; /* keep compiler quiet */ - break; - } - - num_reduced_frame = row_is_in_reduced_frame(winobj, - winstate->frameheadpos); - /* zero means a non-RPR window, which has no reduced frame */ - if (num_reduced_frame < 0) - goto out_of_frame; - else if (num_reduced_frame > 0) - { - if (-relpos >= num_reduced_frame) - goto out_of_frame; - abs_pos = winstate->frameheadpos + relpos + - num_reduced_frame - 1; - } + abs_pos = winstate->frameheadpos + length - 1 + relpos; + /* the frame head never moves back, so marking it is safe */ + mark_pos = winstate->frameheadpos; break; default: elog(ERROR, "unrecognized window seek type: %d", seektype); - abs_pos = mark_pos = 0; /* keep compiler quiet */ break; } if (!window_gettupleslot(winobj, abs_pos, slot)) goto out_of_frame; - /* The code above does not detect all out-of-frame cases, so check */ - if (row_is_in_frame(winobj, abs_pos, slot, false) <= 0) - goto out_of_frame; + /* matching never runs past the frame end, so the match lies within it */ + Assert(row_is_in_frame(winobj, abs_pos, slot, false) > 0); if (isout) *isout = false; if (set_mark) - { - /* - * If RPR is enabled and seek type is WINDOW_SEEK_TAIL, we set the - * mark position unconditionally to frameheadpos. In this case the - * frame always starts at CURRENT_ROW and never goes back, thus - * setting the mark at the position is safe. - */ - if (winstate->rpPattern != NULL && seektype == WINDOW_SEEK_TAIL) - mark_pos = winstate->frameheadpos; WinSetMarkPosition(winobj, mark_pos); - } - return 0; + econtext->ecxt_outertuple = slot; + return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno), + econtext, isnull); out_of_frame: if (isout) *isout = true; *isnull = true; - return -1; + return (Datum) 0; } @@ -5494,6 +5283,8 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno, WindowAggState *winstate; ExprContext *econtext; TupleTableSlot *slot; + int64 abs_pos; + int64 mark_pos; Assert(WindowObjectIsValid(winobj)); winstate = winobj->winstate; @@ -5504,15 +5295,170 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno, return ignorenulls_getfuncarginframe(winobj, argno, relpos, seektype, set_mark, isnull, isout); - if (WinGetSlotInFrame(winobj, slot, - relpos, seektype, set_mark, - isnull, isout) == 0) + if (rpr_is_defined(winstate)) + return WinGetFuncArgInReducedFrame(winobj, argno, relpos, seektype, + set_mark, isnull, isout); + + switch (seektype) { - econtext->ecxt_outertuple = slot; - return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno), - econtext, isnull); + case WINDOW_SEEK_CURRENT: + elog(ERROR, "WINDOW_SEEK_CURRENT is not supported for WinGetFuncArgInFrame"); + abs_pos = mark_pos = 0; /* keep compiler quiet */ + break; + case WINDOW_SEEK_HEAD: + /* rejecting relpos < 0 is easy and simplifies code below */ + if (relpos < 0) + goto out_of_frame; + update_frameheadpos(winstate); + abs_pos = winstate->frameheadpos + relpos; + mark_pos = abs_pos; + + /* + * Account for exclusion option if one is active, but advance only + * abs_pos not mark_pos. This prevents changes of the current + * row's peer group from resulting in trying to fetch a row before + * some previous mark position. + * + * Note that in some corner cases such as current row being + * outside frame, these calculations are theoretically too simple, + * but it doesn't matter because we'll end up deciding the row is + * out of frame. We do not attempt to avoid fetching rows past + * end of frame; that would happen in some cases anyway. + */ + switch (winstate->frameOptions & FRAMEOPTION_EXCLUSION) + { + case 0: + /* no adjustment needed */ + break; + case FRAMEOPTION_EXCLUDE_CURRENT_ROW: + if (abs_pos >= winstate->currentpos && + winstate->currentpos >= winstate->frameheadpos) + abs_pos++; + break; + case FRAMEOPTION_EXCLUDE_GROUP: + update_grouptailpos(winstate); + if (abs_pos >= winstate->groupheadpos && + winstate->grouptailpos > winstate->frameheadpos) + { + int64 overlapstart = Max(winstate->groupheadpos, + winstate->frameheadpos); + + abs_pos += winstate->grouptailpos - overlapstart; + } + break; + case FRAMEOPTION_EXCLUDE_TIES: + update_grouptailpos(winstate); + if (abs_pos >= winstate->groupheadpos && + winstate->grouptailpos > winstate->frameheadpos) + { + int64 overlapstart = Max(winstate->groupheadpos, + winstate->frameheadpos); + + if (abs_pos == overlapstart) + abs_pos = winstate->currentpos; + else + abs_pos += winstate->grouptailpos - overlapstart - 1; + } + break; + default: + elog(ERROR, "unrecognized frame option state: 0x%x", + winstate->frameOptions); + break; + } + break; + case WINDOW_SEEK_TAIL: + /* rejecting relpos > 0 is easy and simplifies code below */ + if (relpos > 0) + goto out_of_frame; + update_frametailpos(winstate); + abs_pos = winstate->frametailpos - 1 + relpos; + + /* + * Account for exclusion option if one is active. If there is no + * exclusion, we can safely set the mark at the accessed row. But + * if there is, we can only mark the frame start, because we can't + * be sure how far back in the frame the exclusion might cause us + * to fetch in future. Furthermore, we have to actually check + * against frameheadpos here, since it's unsafe to try to fetch a + * row before frame start if the mark might be there already. + */ + switch (winstate->frameOptions & FRAMEOPTION_EXCLUSION) + { + case 0: + /* no adjustment needed */ + mark_pos = abs_pos; + break; + case FRAMEOPTION_EXCLUDE_CURRENT_ROW: + if (abs_pos <= winstate->currentpos && + winstate->currentpos < winstate->frametailpos) + abs_pos--; + update_frameheadpos(winstate); + if (abs_pos < winstate->frameheadpos) + goto out_of_frame; + mark_pos = winstate->frameheadpos; + break; + case FRAMEOPTION_EXCLUDE_GROUP: + update_grouptailpos(winstate); + if (abs_pos < winstate->grouptailpos && + winstate->groupheadpos < winstate->frametailpos) + { + int64 overlapend = Min(winstate->grouptailpos, + winstate->frametailpos); + + abs_pos -= overlapend - winstate->groupheadpos; + } + update_frameheadpos(winstate); + if (abs_pos < winstate->frameheadpos) + goto out_of_frame; + mark_pos = winstate->frameheadpos; + break; + case FRAMEOPTION_EXCLUDE_TIES: + update_grouptailpos(winstate); + if (abs_pos < winstate->grouptailpos && + winstate->groupheadpos < winstate->frametailpos) + { + int64 overlapend = Min(winstate->grouptailpos, + winstate->frametailpos); + + if (abs_pos == overlapend - 1) + abs_pos = winstate->currentpos; + else + abs_pos -= overlapend - 1 - winstate->groupheadpos; + } + update_frameheadpos(winstate); + if (abs_pos < winstate->frameheadpos) + goto out_of_frame; + mark_pos = winstate->frameheadpos; + break; + default: + elog(ERROR, "unrecognized frame option state: 0x%x", + winstate->frameOptions); + mark_pos = 0; /* keep compiler quiet */ + break; + } + break; + default: + elog(ERROR, "unrecognized window seek type: %d", seektype); + abs_pos = mark_pos = 0; /* keep compiler quiet */ + break; } + if (!window_gettupleslot(winobj, abs_pos, slot)) + goto out_of_frame; + + /* The code above does not detect all out-of-frame cases, so check */ + if (row_is_in_frame(winobj, abs_pos, slot, false) <= 0) + goto out_of_frame; + + if (isout) + *isout = false; + if (set_mark) + WinSetMarkPosition(winobj, mark_pos); + econtext->ecxt_outertuple = slot; + return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno), + econtext, isnull); + +out_of_frame: if (isout) *isout = true; *isnull = true; -- 2.34.1