Hi.
based on https://github.com/assam258-5892/postgres/tree/RPR-20260930
I have made some non-trivial refactoring in nodeWindowAgg.c, see attached.
The attached patch commit message is as follows:
================
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 (same as the master
branch) 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.
================
--
jian
https://www.enterprisedb.com/
From 6be6ca9bdfb97f8f5fd9052bc9b8d3eea61e4549 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
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