v50-0001-Evaluate-navigation-offset-once-at-one-place.nocfbot

application/octet-stream

Filename: v50-0001-Evaluate-navigation-offset-once-at-one-place.nocfbot
Type: application/octet-stream
Part: 1
Message: Re: Row pattern recognition
From 3a3a9c89fa01496d91290eac16499047e7db1415 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 2 Jul 2026 17:23:51 +0800
Subject: [PATCH v50 1/2] Evaluate navigation offset once at one place

Row pattern navigation (PREV/NEXT/FIRST/LAST and their compounds) resolved
offsets (transform a expression to a int64 number) in ExecInitExprRec,
ExecEvalRPRNavSet, extract_const_offset, eval_define_offsets. Serval places
transform the same offset expression to a int64 number, that is too much!

With the attached, a single place (ExecInitWindowAgg->eval_define_offsets) to
handling this.

visit_nav_exec() now evaluates every offset expression at executor init and save
the result on the RPRNavExpr (offset / compound_offset); ExecEvalRPRNavSet()
simply reads those int64 values.  This removes:

  - the RPRNavOffsetKind enum and the offset/kind fields on the
    WindowAgg plan node.  The planner walk (visit_nav_plan /
    compute_define_metadata) now only classifies match_start
    dependency, which is all buildRPRPattern() needs to decide context
    absorption.

  - the per-invocation offset machinery in the executor: the Datum*
    offset arrays in the eval step, rpr_nav_get_compound_offset(), and
    the scattered NULL/negative checks, now centralized in
    eval_nav_offset.

The "retain all" case (backward reach overflows int64) is encoded as the
sentinel navMaxOffset < 0 rather than an enum value; advance_nav_mark() disables
tuplestore trim on it.  EXPLAIN reads the resolved offsets from the planstate
(ExecInitWindowAgg is reached for EXPLAIN without ANALYZE), so it now prints
concrete offsets instead of "runtime".

```
 We must still set nav_saved_outertuple (done above) so that
EEOP_RPR_NAV_RESTORE is a harmless no-op.
```
The above in ExecEvalRPRNavSet is wrong, EEOP_RPR_NAV_RESTORE will never be a
no-op. However we can make EEOP_RPR_NAV_RESTORE no-op, so the above comments
make sense.

Rename eval_nav_offset_helper to eval_nav_offset

Based on https://github.com/assam258-5892/postgres/commits/RPR
---
 src/backend/commands/explain.c                |  69 +---
 src/backend/executor/execExpr.c               |  56 +---
 src/backend/executor/execExprInterp.c         |  80 +----
 src/backend/executor/nodeWindowAgg.c          | 203 ++++++------
 src/backend/optimizer/plan/createplan.c       | 299 ++----------------
 src/include/executor/execExpr.h               |   6 +-
 src/include/nodes/execnodes.h                 |  10 +-
 src/include/nodes/parsenodes.h                |  15 -
 src/include/nodes/plannodes.h                 |  21 --
 src/include/nodes/primnodes.h                 |  41 ++-
 src/test/regress/expected/rpr_explain.out     |   6 +-
 src/test/regress/expected/rpr_integration.out |   2 +-
 src/tools/pgindent/typedefs.list              |   1 -
 13 files changed, 188 insertions(+), 621 deletions(-)

diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index a5d272b632..c7c2d71bc7 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -3212,11 +3212,6 @@ show_window_def(WindowAggState *planstate, List *ancestors, ExplainState *es)
 	/* Show Row Pattern Recognition pattern if present */
 	if (wagg->rpPattern != NULL)
 	{
-		RPRNavOffsetKind maxKind = wagg->navMaxOffsetKind;
-		int64		maxOffset = wagg->navMaxOffset;
-		RPRNavOffsetKind firstKind = wagg->navFirstOffsetKind;
-		int64		firstOffset = wagg->navFirstOffset;
-
 		char	   *patternStr = deparse_rpr_pattern(wagg->rpPattern);
 
 		ExplainPropertyText("Pattern", patternStr, es);
@@ -3224,58 +3219,24 @@ show_window_def(WindowAggState *planstate, List *ancestors, ExplainState *es)
 		pfree(patternStr);
 
 		/*
-		 * Show navigation offsets for tuplestore trim.  For EXPLAIN ANALYZE,
-		 * use the executor-resolved values (which may differ from the plan
-		 * when NEEDS_EVAL was resolved to FIXED or RETAIN_ALL at init).
+		 * Navigation offsets for tuplestore trim are resolved at executor
+		 * init, which runs even for plain EXPLAIN, so read the resolved
+		 * values from the planstate.  navMaxOffset < 0 is the retain-all
+		 * sentinel (trim disabled).
 		 */
-		if (es->analyze)
-		{
-			maxKind = planstate->navMaxOffsetKind;
-			maxOffset = planstate->navMaxOffset;
-			firstKind = planstate->navFirstOffsetKind;
-			firstOffset = planstate->navFirstOffset;
-		}
-
-		switch (maxKind)
-		{
-			case RPR_NAV_OFFSET_NEEDS_EVAL:
-				ExplainPropertyText("Nav Mark Lookback", "runtime", es);
-				break;
-			case RPR_NAV_OFFSET_RETAIN_ALL:
-				ExplainPropertyText("Nav Mark Lookback", "retain all", es);
-				break;
-			case RPR_NAV_OFFSET_FIXED:
-				ExplainPropertyInteger("Nav Mark Lookback", NULL,
-									   maxOffset, es);
-				break;
-			default:
-				elog(ERROR, "unrecognized RPR nav offset kind: %d",
-					 maxKind);
-				break;
-		}
+		if (planstate->navMaxOffset < 0)
+			ExplainPropertyText("Nav Mark Lookback", "retain all", es);
+		else
+			ExplainPropertyInteger("Nav Mark Lookback", NULL,
+								   planstate->navMaxOffset, es);
 
-		if (wagg->hasFirstNav)
+		if (planstate->hasFirstNav)
 		{
-			switch (firstKind)
-			{
-				case RPR_NAV_OFFSET_NEEDS_EVAL:
-					ExplainPropertyText("Nav Mark Lookahead", "runtime",
-										es);
-					break;
-				case RPR_NAV_OFFSET_FIXED:
-					if (firstOffset == PG_INT64_MAX)
-						ExplainPropertyText("Nav Mark Lookahead", "infinite",
-											es);
-					else
-						ExplainPropertyInteger("Nav Mark Lookahead", NULL,
-											   firstOffset, es);
-					break;
-				default:
-					/* RPR_NAV_OFFSET_RETAIN_ALL is lookback-only, never here */
-					elog(ERROR, "unrecognized RPR nav offset kind: %d",
-						 firstKind);
-					break;
-			}
+			if (planstate->navFirstOffset == PG_INT64_MAX)
+				ExplainPropertyText("Nav Mark Lookahead", "infinite", es);
+			else
+				ExplainPropertyInteger("Nav Mark Lookahead", NULL,
+									   planstate->navFirstOffset, es);
 		}
 	}
 }
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 3036782503..de13ac693f 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -1198,10 +1198,6 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				 * swaps ecxt_outertuple to the target row, the argument
 				 * expression is compiled normally (reads from the swapped
 				 * slot), and the RESTORE opcode restores the original slot.
-				 *
-				 * Default offset when offset_arg is NULL: PREV/NEXT: 1
-				 * (physical offset from currentpos) FIRST/LAST: 0 (logical
-				 * offset from match boundary)
 				 */
 				RPRNavExpr *nav = (RPRNavExpr *) node;
 				WindowAggState *winstate;
@@ -1213,56 +1209,8 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				scratch.opcode = EEOP_RPR_NAV_SET;
 				scratch.d.rpr_nav.winstate = winstate;
 				scratch.d.rpr_nav.kind = nav->kind;
-
-				if (nav->kind >= RPR_NAV_PREV_FIRST)
-				{
-					/*
-					 * Compound navigation: allocate array of 2 for inner [0]
-					 * and outer [1] offsets.
-					 */
-					Datum	   *offset_values = palloc_array(Datum, 2);
-					bool	   *offset_isnulls = palloc_array(bool, 2);
-
-					/* Inner offset (default 0 for FIRST/LAST) */
-					if (nav->offset_arg != NULL)
-						ExecInitExprRec(nav->offset_arg, state,
-										&offset_values[0], &offset_isnulls[0]);
-					else
-					{
-						offset_values[0] = Int64GetDatum(0);
-						offset_isnulls[0] = false;
-					}
-
-					/* Outer offset (default 1 for PREV/NEXT) */
-					if (nav->compound_offset_arg != NULL)
-						ExecInitExprRec(nav->compound_offset_arg, state,
-										&offset_values[1], &offset_isnulls[1]);
-					else
-					{
-						offset_values[1] = Int64GetDatum(1);
-						offset_isnulls[1] = false;
-					}
-
-					scratch.d.rpr_nav.offset_value = offset_values;
-					scratch.d.rpr_nav.offset_isnull = offset_isnulls;
-				}
-				else if (nav->offset_arg != NULL)
-				{
-					/* Simple navigation with explicit offset */
-					Datum	   *offset_value = palloc_object(Datum);
-					bool	   *offset_isnull = palloc_object(bool);
-
-					ExecInitExprRec(nav->offset_arg, state,
-									offset_value, offset_isnull);
-					scratch.d.rpr_nav.offset_value = offset_value;
-					scratch.d.rpr_nav.offset_isnull = offset_isnull;
-				}
-				else
-				{
-					/* Simple navigation with default offset */
-					scratch.d.rpr_nav.offset_value = NULL;
-					scratch.d.rpr_nav.offset_isnull = NULL;
-				}
+				scratch.d.rpr_nav.offset = nav->offset;
+				scratch.d.rpr_nav.compound_offset = nav->compound_offset;
 
 				ExprEvalPushStep(state, &scratch);
 
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 5b6281e9e3..33960b757d 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -6011,34 +6011,6 @@ ExecAggPlainTransByRef(AggState *aggstate, AggStatePerTrans pertrans,
 	MemoryContextSwitchTo(oldContext);
 }
 
-/*
- * Extract compound (outer) offset from step data.
- * For compound nav, offset_value is an array: [0]=inner, [1]=outer.
- * Returns the outer offset; errors on NULL or negative.
- * Default is 1 (like PREV/NEXT implicit offset).
- */
-static int64
-rpr_nav_get_compound_offset(ExprEvalStep *op)
-{
-	int64		val;
-
-	Assert(op->d.rpr_nav.offset_value != NULL);
-
-	if (op->d.rpr_nav.offset_isnull[1])
-		ereport(ERROR,
-				errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
-				errmsg("row pattern navigation offset must not be null"));
-
-	val = DatumGetInt64(op->d.rpr_nav.offset_value[1]);
-
-	if (val < 0)
-		ereport(ERROR,
-				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-				errmsg("row pattern navigation offset must not be negative"));
-
-	return val;
-}
-
 /*
  * Evaluate RPR navigation (PREV/NEXT/FIRST/LAST): swap slot to target row.
  *
@@ -6054,42 +6026,15 @@ ExecEvalRPRNavSet(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 {
 	WindowAggState *winstate = op->d.rpr_nav.winstate;
 	int64		offset;
+	int64		compound_offset;
 	int64		target_pos;
 	TupleTableSlot *target_slot;
 
 	/* Save current slot for later restore */
 	winstate->nav_saved_outertuple = econtext->ecxt_outertuple;
 
-	/*
-	 * Determine the inner offset.  NULL or negative offsets are errors per
-	 * the SQL standard.
-	 *
-	 * Default offset when offset_arg is NULL: PREV/NEXT: 1 (standard 5.6.2)
-	 * FIRST/LAST and compound: 0 for inner, 1 for outer
-	 */
-	if (op->d.rpr_nav.offset_value != NULL)
-	{
-		if (*op->d.rpr_nav.offset_isnull)
-			ereport(ERROR,
-					errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
-					errmsg("row pattern navigation offset must not be null"));
-
-		offset = DatumGetInt64(*op->d.rpr_nav.offset_value);
-
-		if (offset < 0)
-			ereport(ERROR,
-					errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-					errmsg("row pattern navigation offset must not be negative"));
-	}
-	else
-	{
-		/* Default offset: 1 for simple PREV/NEXT, 0 otherwise */
-		if (op->d.rpr_nav.kind == RPR_NAV_PREV ||
-			op->d.rpr_nav.kind == RPR_NAV_NEXT)
-			offset = 1;
-		else
-			offset = 0;
-	}
+	offset = op->d.rpr_nav.offset;
+	compound_offset = op->d.rpr_nav.compound_offset;
 
 	/*
 	 * Calculate target position based on navigation direction.  On overflow,
@@ -6129,7 +6074,6 @@ ExecEvalRPRNavSet(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 		case RPR_NAV_PREV_FIRST:
 		case RPR_NAV_NEXT_FIRST:
 			{
-				int64		compound_offset;
 				int64		inner_pos;
 
 				/* Inner: match_start + offset */
@@ -6144,9 +6088,6 @@ ExecEvalRPRNavSet(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 					break;
 				}
 
-				/* Outer offset */
-				compound_offset = rpr_nav_get_compound_offset(op);
-
 				/* Apply outer: PREV subtracts, NEXT adds */
 				if (op->d.rpr_nav.kind == RPR_NAV_PREV_FIRST)
 				{
@@ -6168,7 +6109,6 @@ ExecEvalRPRNavSet(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 		case RPR_NAV_PREV_LAST:
 		case RPR_NAV_NEXT_LAST:
 			{
-				int64		compound_offset;
 				int64		inner_pos;
 
 				/* Inner: currentpos - offset */
@@ -6183,9 +6123,6 @@ ExecEvalRPRNavSet(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 					break;
 				}
 
-				/* Outer offset */
-				compound_offset = rpr_nav_get_compound_offset(op);
-
 				/* Apply outer: PREV subtracts, NEXT adds */
 				if (op->d.rpr_nav.kind == RPR_NAV_PREV_LAST)
 				{
@@ -6206,7 +6143,7 @@ ExecEvalRPRNavSet(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 			break;
 		default:
 			elog(ERROR, "unrecognized RPR navigation kind: %d",
-				 op->d.rpr_nav.kind);
+				 (int) op->d.rpr_nav.kind);
 			break;
 	}
 
@@ -6238,8 +6175,6 @@ ExecEvalRPRNavSet(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
  * Evaluate RPR navigation: restore slot to original row.
  *
  * Restores econtext->ecxt_outertuple from the saved slot in winstate.
- * When slot swap was elided (target == currentpos), this is a harmless
- * no-op since saved and current slots are identical.
  * The caller is responsible for updating any local slot cache.
  *
  * For pass-by-reference result types, the result datum points into
@@ -6255,6 +6190,13 @@ ExecEvalRPRNavRestore(ExprState *state, ExprEvalStep *op,
 {
 	WindowAggState *winstate = op->d.rpr_nav.winstate;
 
+	/*
+	 * When slot swap was elided (target == currentpos), this is a harmless
+	 * no-op since saved and current slots are identical.
+	 */
+	if (econtext->ecxt_outertuple == winstate->nav_saved_outertuple)
+		return;
+
 	econtext->ecxt_outertuple = winstate->nav_saved_outertuple;
 
 	/* Stabilize pass-by-ref result against nav_slot re-fetch */
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index e4e97a6ed9..caa27ffe8e 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -1261,13 +1261,13 @@ prepare_tuplestore(WindowAggState *winstate)
 		/*
 		 * Allocate mark and read pointers for RPR navigation.
 		 *
-		 * If navMaxOffsetKind == RPR_NAV_OFFSET_FIXED, we advance the mark
-		 * based on (currentpos - navMaxOffset) and optionally
+		 * If navMaxOffset is non-negative, we advance the mark based on
+		 * (currentpos - navMaxOffset) and optionally
 		 * (nfaContext->matchStartRow + navFirstOffset), allowing
 		 * tuplestore_trim() to free rows that are no longer reachable.
 		 *
-		 * RPR_NAV_OFFSET_NEEDS_EVAL is resolved at executor init; by this
-		 * point it is either FIXED or RETAIN_ALL.
+		 * the offset is resolved at executor init; by this point it is either
+		 * FIXED or RETAIN_ALL.
 		 */
 		winstate->nav_winobj->markptr =
 			tuplestore_alloc_read_pointer(winstate->buffer, 0);
@@ -3037,12 +3037,7 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->rpSkipTo = node->rpSkipTo;
 	/* Set up row pattern recognition PATTERN clause (compiled NFA) */
 	winstate->rpPattern = node->rpPattern;
-	/* Set up nav offsets for tuplestore trim; resolve any NEEDS_EVAL kinds */
-	winstate->navMaxOffsetKind = node->navMaxOffsetKind;
-	winstate->navMaxOffset = node->navMaxOffset;
-	winstate->hasFirstNav = node->hasFirstNav;
-	winstate->navFirstOffsetKind = node->navFirstOffsetKind;
-	winstate->navFirstOffset = node->navFirstOffset;
+	/* Resolve nav offsets for tuplestore trim from the DEFINE clause */
 	eval_define_offsets(winstate, node->defineClause);
 
 	/* Copy match_start dependency bitmapset for per-context evaluation */
@@ -3983,20 +3978,16 @@ put_notnull_info(WindowObject winobj, int64 pos, int argno, bool isnull)
 }
 
 /*
- * eval_nav_offset_helper
- *		Pre-evaluate a navigation offset expression at executor init time, to
- *		bound how far navigation can reach (which sizes the frame trim).
- *		Returns the offset value, or 0 for a NULL or negative offset.
+ * eval_nav_offset
+ *		Evaluate a row pattern navigation offset expression
  *
- * The offset is not validated here.  A NULL or negative value is caught later,
- * per row, on the navigation path that consumes it (see EEOP_RPR_NAV_SET in
- * execExprInterp.c), which errors out before navigation produces any result;
- * the trim sizing computed from such an offset is therefore never used, and 0
- * is returned as a harmless placeholder.
+ * The resolved value bounds how far navigation can reach, which in turn
+ * sizes the tuplestore trim.  offset_expr may be either a simple offset or
+ * the outer (compound) offset of a compound navigation.  Returns the offset
+ * as an int64; a NULL or negative result is an error per the SQL standard.
  */
 static int64
-eval_nav_offset_helper(WindowAggState *winstate, Expr *offset_expr,
-					   int64 defaultOffset)
+eval_nav_offset(WindowAggState *winstate, Expr *offset_expr)
 {
 	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
 	ExprState  *estate;
@@ -4004,18 +3995,20 @@ eval_nav_offset_helper(WindowAggState *winstate, Expr *offset_expr,
 	bool		isnull;
 	int64		offset;
 
-	if (offset_expr == NULL)
-		return defaultOffset;
-
 	estate = ExecInitExpr(offset_expr, (PlanState *) winstate);
 	val = ExecEvalExprSwitchContext(estate, econtext, &isnull);
 
 	if (isnull)
-		return 0;
+		ereport(ERROR,
+				errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				errmsg("row pattern navigation offset must not be null"));
 
 	offset = DatumGetInt64(val);
+
 	if (offset < 0)
-		return 0;
+		ereport(ERROR,
+				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				errmsg("row pattern navigation offset must not be negative"));
 
 	return offset;
 }
@@ -4028,13 +4021,14 @@ typedef struct
 	bool		maxOverflow;	/* true if backward-reach overflow detected */
 	int64		minFirstOffset; /* min forward-from-match_start offset; may be
 								 * negative (PREV_FIRST: inner - outer < 0) */
+	bool		hasFirst;		/* any FIRST-based nav found */
 } EvalDefineOffsetsContext;
 
 /*
  * visit_nav_exec
  *		nav_traversal_walker callback (NavVisitFn) for the executor side.
  *		At each RPRNavExpr, evaluates the nav's offset expression(s) at
- *		runtime via eval_nav_offset_helper and accumulates:
+ *		runtime via eval_nav_offset and accumulates:
  *
  *		  - maxOffset (backward reach): PREV, LAST-with-offset, compound
  *			PREV_LAST (sets maxOverflow on int64 overflow), compound
@@ -4052,6 +4046,9 @@ typedef struct
 static void
 visit_nav_exec(NavTraversal *t, RPRNavExpr *nav)
 {
+	int64		inner;
+	int64		outer;
+
 	EvalDefineOffsetsContext *context = (EvalDefineOffsetsContext *) t->data;
 
 	/*
@@ -4064,69 +4061,61 @@ visit_nav_exec(NavTraversal *t, RPRNavExpr *nav)
 	Assert(nav->compound_offset_arg == NULL ||
 		   !IsA(nav->compound_offset_arg, RPRNavExpr));
 
+	if (nav->offset_arg != NULL)
+		inner = eval_nav_offset(context->winstate,
+								nav->offset_arg);
+	else if (nav->kind == RPR_NAV_PREV || nav->kind == RPR_NAV_NEXT)
+		inner = 1;
+	else
+		inner = 0;
+
+	if (nav->compound_offset_arg != NULL)
+		outer = eval_nav_offset(context->winstate,
+								nav->compound_offset_arg);
+	else
+		outer = 1;
+
+	nav->offset = inner;
+	nav->compound_offset = outer;
+
 	/* Backward reach: PREV, LAST-with-offset */
 	if (!context->maxOverflow)
 	{
-		int64		reach = 0;
-		bool		gotReach = false;
-
-		if (nav->kind == RPR_NAV_PREV)
+		if (nav->kind == RPR_NAV_PREV ||
+			nav->kind == RPR_NAV_LAST ||
+			nav->kind == RPR_NAV_PREV_LAST ||
+			nav->kind == RPR_NAV_NEXT_LAST)
 		{
-			reach = eval_nav_offset_helper(context->winstate,
-										   nav->offset_arg, 1);
-			gotReach = true;
-		}
-		else if (nav->kind == RPR_NAV_LAST && nav->offset_arg != NULL)
-		{
-			reach = eval_nav_offset_helper(context->winstate,
-										   nav->offset_arg, 0);
-			gotReach = true;
-		}
-		else if (nav->kind == RPR_NAV_PREV_LAST ||
-				 nav->kind == RPR_NAV_NEXT_LAST)
-		{
-			int64		inner = eval_nav_offset_helper(context->winstate,
-													   nav->offset_arg, 0);
-			int64		outer = eval_nav_offset_helper(context->winstate,
-													   nav->compound_offset_arg, 1);
+			int64		reach = 0;
 
-			if (nav->kind == RPR_NAV_PREV_LAST)
+			if (nav->kind == RPR_NAV_PREV || nav->kind == RPR_NAV_LAST)
+				reach = inner;
+			else if (nav->kind == RPR_NAV_PREV_LAST)
 			{
 				if (pg_add_s64_overflow(inner, outer, &reach))
 					context->maxOverflow = true;
-				else
-					gotReach = true;
 			}
 			else
-			{
 				reach = Max(inner - outer, 0);
-				gotReach = true;
-			}
-		}
 
-		if (gotReach)
-			context->maxOffset = Max(context->maxOffset, reach);
+			if (!context->maxOverflow)
+				context->maxOffset = Max(context->maxOffset, reach);
+		}
 	}
 
 	/* Forward reach from match_start: FIRST, compound PREV_FIRST/NEXT_FIRST */
-	if (nav->kind == RPR_NAV_FIRST)
+	if (nav->kind == RPR_NAV_FIRST ||
+		nav->kind == RPR_NAV_PREV_FIRST ||
+		nav->kind == RPR_NAV_NEXT_FIRST)
 	{
-		int64		reach;
+		int64		reach = 0;
 
-		reach = eval_nav_offset_helper(context->winstate,
-									   nav->offset_arg, 0);
-		context->minFirstOffset = Min(context->minFirstOffset, reach);
-	}
-	else if (nav->kind == RPR_NAV_PREV_FIRST ||
-			 nav->kind == RPR_NAV_NEXT_FIRST)
-	{
-		int64		inner = eval_nav_offset_helper(context->winstate,
-												   nav->offset_arg, 0);
-		int64		outer = eval_nav_offset_helper(context->winstate,
-												   nav->compound_offset_arg, 1);
-		int64		reach;
+		context->hasFirst = true;
 
-		if (nav->kind == RPR_NAV_PREV_FIRST)
+		if (nav->kind == RPR_NAV_FIRST)
+			context->minFirstOffset = Min(context->minFirstOffset,
+										  inner);
+		else if (nav->kind == RPR_NAV_PREV_FIRST)
 		{
 			/*
 			 * reach = inner - outer.  Both are non-negative, so the result >=
@@ -4150,33 +4139,38 @@ visit_nav_exec(NavTraversal *t, RPRNavExpr *nav)
 
 /*
  * eval_define_offsets
- *		Evaluate non-constant nav offsets at executor init time.
+ *		Compute navigation offsets for tuplestore trim from the DEFINE clause,
+ *		at executor init.
  *
- * Called when the planner set navMaxOffsetKind and/or navFirstOffsetKind
- * to RPR_NAV_OFFSET_NEEDS_EVAL because some offset contains a parameter
- * or non-foldable expression.  Updates only the fields whose kind was
- * NEEDS_EVAL; FIXED kinds are left unchanged.
+ * Evaluates every nav offset expression (constant or not) via visit_nav_exec
+ * and stores the results in the WindowAggState:
+ *	 - navMaxOffset: max backward (PREV/LAST) reach; set to -1 (the retain-all
+ *	   sentinel) on int64 overflow so advance_nav_mark() disables trim.
+ *	 - hasFirstNav / navFirstOffset: forward (FIRST) reach from match_start;
+ *	   PG_INT64_MAX means unbounded forward reach.
  *
- * On backward-reach overflow, sets navMaxOffsetKind to
- * RPR_NAV_OFFSET_RETAIN_ALL so that tuplestore trim is disabled for
- * backward navigation.
+ * Called unconditionally for windows; a non-RPR window has an empty DEFINE
+ * clause and falls through to the defaults.
  */
 static void
 eval_define_offsets(WindowAggState *winstate, List *defineClause)
 {
 	EvalDefineOffsetsContext ctx;
 	NavTraversal trav;
-	bool		needsMax = (winstate->navMaxOffsetKind == RPR_NAV_OFFSET_NEEDS_EVAL);
-	bool		needsFirst = (winstate->hasFirstNav &&
-							  winstate->navFirstOffsetKind == RPR_NAV_OFFSET_NEEDS_EVAL);
 
-	if (!needsMax && !needsFirst)
+	/* Defaults: no backward, no FIRST navigation */
+	winstate->navMaxOffset = 0;
+	winstate->hasFirstNav = false;
+	winstate->navFirstOffset = 0;
+
+	if (defineClause == NIL)
 		return;
 
 	ctx.winstate = winstate;
 	ctx.maxOffset = 0;
 	ctx.maxOverflow = false;
 	ctx.minFirstOffset = PG_INT64_MAX;
+	ctx.hasFirst = false;
 
 	trav.visit = visit_nav_exec;
 	trav.data = &ctx;
@@ -4186,23 +4180,21 @@ eval_define_offsets(WindowAggState *winstate, List *defineClause)
 		nav_traversal_walker((Node *) te->expr, &trav);
 	}
 
-	if (needsMax)
-	{
-		if (ctx.maxOverflow)
-		{
-			winstate->navMaxOffsetKind = RPR_NAV_OFFSET_RETAIN_ALL;
-			winstate->navMaxOffset = 0;
-		}
-		else
-		{
-			winstate->navMaxOffsetKind = RPR_NAV_OFFSET_FIXED;
-			winstate->navMaxOffset = ctx.maxOffset;
-		}
-	}
+	/*
+	 * Backward (PREV/LAST) reach.  On int64 overflow the lookback cannot be
+	 * bounded, so store the retain-all sentinel (-1); advance_nav_mark()
+	 * reads it to disable tuplestore trim.
+	 */
+	if (ctx.maxOverflow)
+		winstate->navMaxOffset = -1;
+	else
+		winstate->navMaxOffset = ctx.maxOffset;
 
-	if (needsFirst)
+	/* Forward (FIRST) reach; never needs a retain-all sentinel */
+	winstate->hasFirstNav = ctx.hasFirst;
+
+	if (ctx.hasFirst)
 	{
-		winstate->navFirstOffsetKind = RPR_NAV_OFFSET_FIXED;
 		if (ctx.minFirstOffset < PG_INT64_MAX)
 			winstate->navFirstOffset = ctx.minFirstOffset;
 		else
@@ -4384,13 +4376,13 @@ advance_nav_mark(WindowAggState *winstate, int64 currentPos)
 	if (winstate->nav_winobj == NULL)
 		return;
 
-	/* RETAIN_ALL disables trim for the backward (PREV/LAST) dimension */
-	if (winstate->navMaxOffsetKind == RPR_NAV_OFFSET_RETAIN_ALL)
+	/*
+	 * navMaxOffset < 0 is the retain-all sentinel (infinite offset): trim is
+	 * disabled
+	 */
+	if (winstate->navMaxOffset < 0)
 		return;
 
-	/* navMax is FIXED here: NEEDS_EVAL resolved, RETAIN_ALL returned */
-	Assert(winstate->navMaxOffsetKind == RPR_NAV_OFFSET_FIXED);
-
 	if (currentPos > winstate->navMaxOffset)
 		navmarkpos = currentPos - winstate->navMaxOffset;
 	else
@@ -4400,9 +4392,6 @@ advance_nav_mark(WindowAggState *winstate, int64 currentPos)
 	{
 		int64		firstreach;
 
-		/* navFirst is always FIXED; it never takes RETAIN_ALL */
-		Assert(winstate->navFirstOffsetKind == RPR_NAV_OFFSET_FIXED);
-
 		/*
 		 * Head context has the smallest matchStartRow (contexts appended in
 		 * nondecreasing order), so bounding by it covers every FIRST reach.
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index d860fb9711..94d9a45bdc 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -293,9 +293,6 @@ static WindowAgg *make_windowagg(List *tlist, WindowClause *wc,
 								 List *runCondition,
 								 RPRPattern *compiledPattern,
 								 Bitmapset *defineMatchStartDependent,
-								 RPRNavOffsetKind navMaxOffsetKind, int64 navMaxOffset,
-								 bool hasFirstNav,
-								 RPRNavOffsetKind navFirstOffsetKind, int64 navFirstOffset,
 								 List *qual, bool topWindow,
 								 Plan *lefttree);
 static Group *make_group(List *tlist, List *qual, int numGroupCols,
@@ -2478,78 +2475,28 @@ create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path)
  *
  * The driver sets curVarIdx to the index of the variable being walked
  * before each invocation; the walker uses it to populate matchStartDependent.
+ *
+ * XXX: TODO, the above comments need change.
  */
 typedef struct DefineMetadataContext
 {
-	int64		maxOffset;		/* max PREV/LAST backward offset (>= 0) */
-	bool		maxNeedsEval;	/* non-constant PREV/LAST offset found */
-	bool		maxOverflow;	/* constant offset overflow detected */
-	int64		firstOffset;	/* min FIRST offset (may be negative for
-								 * PREV_FIRST) */
-	bool		hasFirst;		/* any FIRST node found */
-	bool		firstNeedsEval; /* non-constant FIRST offset found */
 	int			curVarIdx;		/* DEFINE variable currently being walked */
 	Bitmapset  *matchStartDependent;	/* variables that depend on
 										 * match_start */
 } DefineMetadataContext;
 
-/*
- * Helper: extract constant offset from an expression, handling NULL/negative.
- * If expr is NULL, returns defaultOffset.
- * Returns true if constant, false if non-constant (Param, cast, etc.).
- */
-static bool
-extract_const_offset(Expr *expr, int64 defaultOffset, int64 *result)
-{
-	if (expr == NULL)
-	{
-		*result = defaultOffset;
-		return true;
-	}
-
-	if (IsA(expr, Const))
-	{
-		Const	   *c = (Const *) expr;
-
-		if (c->constisnull)
-			*result = 0;		/* runtime error; safe placeholder */
-		else
-		{
-			*result = DatumGetInt64(c->constvalue);
-			if (*result < 0)
-				*result = 0;	/* runtime error; safe placeholder */
-		}
-		return true;
-	}
-
-	return false;				/* non-constant */
-}
-
 /*
  * visit_nav_plan
  *		nav_traversal_walker callback (NavVisitFn) for the planner side.
  *		At each RPRNavExpr in a DEFINE expression, computes:
  *
- *		  1. backward reach (maxOffset) for tuplestore trim:
- *			 - PREV(v, N), LAST(v, N)              -> N (default 1)
- *			 - compound PREV_LAST(v, N, M)         -> N + M (overflow -> maxOverflow)
- *			 - compound NEXT_LAST(v, N, M)         -> max(N - M, 0)
- *
- *		  2. forward reach (firstOffset) for tuplestore trim:
- *			 - FIRST(v, N)                         -> N (default 0)
- *			 - compound PREV_FIRST(v, N, M)        -> N - M (may be negative)
- *			 - compound NEXT_FIRST(v, N, M)        -> N + M
+ * per-variable match_start dependency for absorption suppression: outer nav
+ * kinds that reach match_start (FIRST, LAST-with-offset, PREV_FIRST,
+ * NEXT_FIRST, PREV_LAST/NEXT_LAST-with-offset) add curVarIdx to
+ * matchStartDependent.
  *
- *		  3. per-variable match_start dependency for absorption suppression:
- *			 outer nav kinds that reach match_start (FIRST, LAST-with-offset,
- *			 PREV_FIRST, NEXT_FIRST, PREV_LAST/NEXT_LAST-with-offset) add
- *			 curVarIdx to matchStartDependent.
- *
- * Constant offsets are extracted via extract_const_offset; non-constant
- * offsets set maxNeedsEval / firstNeedsEval so the executor can resolve
- * them at init time (see visit_nav_exec).  Classification uses only the
- * outer nav kind: parser nesting restrictions prevent FIRST/LAST inside
- * a PREV/NEXT value subexpression.
+ * Classification uses only the outer nav kind: parser nesting restrictions
+ * prevent FIRST/LAST inside a PREV/NEXT value subexpression.
  */
 static void
 visit_nav_plan(NavTraversal *t, RPRNavExpr *nav)
@@ -2568,130 +2515,6 @@ visit_nav_plan(NavTraversal *t, RPRNavExpr *nav)
 	Assert(nav->compound_offset_arg == NULL ||
 		   !IsA(nav->compound_offset_arg, RPRNavExpr));
 
-	/*
-	 * Simple PREV(v, N) and LAST(v, N): backward reach from currentpos. LAST
-	 * without offset = currentpos, no backward reach. NEXT: forward only,
-	 * irrelevant for trim.
-	 */
-	if (nav->kind == RPR_NAV_PREV ||
-		(nav->kind == RPR_NAV_LAST && nav->offset_arg != NULL))
-	{
-		if (!context->maxNeedsEval)
-		{
-			int64		offset;
-
-			/*
-			 * default 1 is for PREV; the guarded LAST sub-case never uses it.
-			 */
-			if (extract_const_offset(nav->offset_arg, 1, &offset))
-				context->maxOffset = Max(context->maxOffset, offset);
-			else
-				context->maxNeedsEval = true;
-		}
-	}
-
-	/*
-	 * Simple FIRST(v, N): forward reach from match_start. Smaller N means
-	 * older rows needed.
-	 */
-	if (nav->kind == RPR_NAV_FIRST)
-	{
-		context->hasFirst = true;
-
-		if (!context->firstNeedsEval)
-		{
-			int64		offset;
-
-			if (extract_const_offset(nav->offset_arg, 0, &offset))
-				context->firstOffset = Min(context->firstOffset, offset);
-			else
-				context->firstNeedsEval = true;
-		}
-	}
-
-	/*
-	 * Compound PREV_LAST / NEXT_LAST: base = currentpos. PREV_LAST(v, N, M):
-	 * target = currentpos - N - M -> lookback = N + M NEXT_LAST(v, N, M):
-	 * target = currentpos - N + M -> lookback = max(N - M, 0)
-	 */
-	if (nav->kind == RPR_NAV_PREV_LAST ||
-		nav->kind == RPR_NAV_NEXT_LAST)
-	{
-		if (!context->maxNeedsEval)
-		{
-			int64		inner;
-			int64		outer;
-			int64		reach;
-
-			if (extract_const_offset(nav->offset_arg, 0, &inner) &&
-				extract_const_offset(nav->compound_offset_arg, 1, &outer))
-			{
-				if (nav->kind == RPR_NAV_PREV_LAST)
-				{
-					if (pg_add_s64_overflow(inner, outer, &reach))
-						context->maxOverflow = true;
-					else
-						context->maxOffset = Max(context->maxOffset, reach);
-				}
-				else
-				{
-					reach = Max(inner - outer, 0);
-					context->maxOffset = Max(context->maxOffset, reach);
-				}
-			}
-			else
-				context->maxNeedsEval = true;
-		}
-	}
-
-	/*
-	 * Compound PREV_FIRST / NEXT_FIRST: base = match_start. PREV_FIRST(v, N,
-	 * M): target = match_start + N - M NEXT_FIRST(v, N, M): target =
-	 * match_start + N + M The combined offset (N+/-M) from match_start can be
-	 * negative, meaning rows before match_start are needed.
-	 */
-	if (nav->kind == RPR_NAV_PREV_FIRST ||
-		nav->kind == RPR_NAV_NEXT_FIRST)
-	{
-		context->hasFirst = true;
-
-		if (!context->firstNeedsEval)
-		{
-			int64		inner;
-			int64		outer;
-			int64		reach;
-
-			if (extract_const_offset(nav->offset_arg, 0, &inner) &&
-				extract_const_offset(nav->compound_offset_arg, 1, &outer))
-			{
-				if (nav->kind == RPR_NAV_PREV_FIRST)
-				{
-					/*
-					 * reach = inner - outer.  Both are non-negative, so the
-					 * result >= -PG_INT64_MAX, which cannot underflow int64.
-					 * No overflow check needed.
-					 */
-					reach = inner - outer;
-				}
-				else
-				{
-					/*
-					 * NEXT_FIRST: reach = inner + outer.  This can overflow,
-					 * but the result is always >= 0, so it never updates
-					 * firstOffset (which tracks the minimum).  Clamp to
-					 * PG_INT64_MAX on overflow.
-					 */
-					if (pg_add_s64_overflow(inner, outer, &reach))
-						reach = PG_INT64_MAX;
-				}
-
-				context->firstOffset = Min(context->firstOffset, reach);
-			}
-			else
-				context->firstNeedsEval = true;
-		}
-	}
-
 	/*
 	 * Match-start dependency: classify the outer nav kind.  A constant
 	 * LAST(x, 0) is conservatively included (offset_arg is a non-NULL Const),
@@ -2712,36 +2535,24 @@ visit_nav_plan(NavTraversal *t, RPRNavExpr *nav)
 
 /*
  * compute_define_metadata
- *		Compute navigation offsets and match_start dependency for the
- *		DEFINE clause in a single pass per variable.
+ *		Classify which DEFINE variables depend on the match start.
+ *
+ * Walks each DEFINE variable expression once and returns the set of variable
+ * indices whose navigation reaches match_start (FIRST, LAST-with-offset, or
+ * compound PREV_FIRST/NEXT_FIRST/PREV_LAST/NEXT_LAST-with-offset).  Such
+ * variables require per-context re-evaluation during NFA processing, and
+ * their presence disqualifies the pattern from context absorption.
  *
- * Walks each DEFINE variable expression once, computing:
- *   - maxOffset: max backward reach from PREV, LAST-with-offset,
- *     compound PREV_LAST/NEXT_LAST
- *   - hasFirst/firstOffset: min forward-from-match-start reach from
- *     FIRST, compound PREV_FIRST/NEXT_FIRST
- *   - matchStartDependent: bitmapset of variable indices whose
- *     expressions contain navigation that depends on match_start
- *     (FIRST, LAST-with-offset, or compound PREV_FIRST/NEXT_FIRST/
- *     PREV_LAST/NEXT_LAST-with-offset).  Such variables require
- *     per-context re-evaluation during NFA processing.
+ * Navigation offsets for tuplestore trim are not computed here; they are
+ * resolved at executor init (eval_define_offsets), which can evaluate
+ * non-constant offsets that the planner cannot fold.
  */
 static void
-compute_define_metadata(List *defineClause,
-						RPRNavOffsetKind *maxKind, int64 *maxResult,
-						bool *hasFirst,
-						RPRNavOffsetKind *firstKind, int64 *firstResult,
-						Bitmapset **matchStartDependent)
+compute_define_metadata(List *defineClause, Bitmapset **matchStartDependent)
 {
 	DefineMetadataContext ctx;
 	NavTraversal trav;
 
-	ctx.maxOffset = 0;
-	ctx.maxNeedsEval = false;
-	ctx.maxOverflow = false;
-	ctx.firstOffset = PG_INT64_MAX; /* sentinel: no FIRST found yet */
-	ctx.hasFirst = false;
-	ctx.firstNeedsEval = false;
 	ctx.curVarIdx = 0;
 	ctx.matchStartDependent = NULL;
 
@@ -2755,45 +2566,6 @@ compute_define_metadata(List *defineClause,
 	}
 
 	*matchStartDependent = ctx.matchStartDependent;
-
-	/* Max backward offset */
-	if (ctx.maxOverflow)
-	{
-		*maxKind = RPR_NAV_OFFSET_RETAIN_ALL;
-		*maxResult = 0;
-	}
-	else if (ctx.maxNeedsEval)
-	{
-		*maxKind = RPR_NAV_OFFSET_NEEDS_EVAL;
-		*maxResult = 0;
-	}
-	else
-	{
-		*maxKind = RPR_NAV_OFFSET_FIXED;
-		*maxResult = ctx.maxOffset;
-	}
-
-	/* First offset (can be negative for compound PREV_FIRST) */
-	*hasFirst = ctx.hasFirst;
-	if (ctx.hasFirst)
-	{
-		if (ctx.firstNeedsEval)
-		{
-			*firstKind = RPR_NAV_OFFSET_NEEDS_EVAL;
-			*firstResult = 0;
-		}
-		else
-		{
-			*firstKind = RPR_NAV_OFFSET_FIXED;
-			*firstResult = ctx.firstOffset; /* may be negative; PG_INT64_MAX
-											 * if overflowed */
-		}
-	}
-	else
-	{
-		*firstKind = RPR_NAV_OFFSET_FIXED;
-		*firstResult = 0;
-	}
 }
 
 /*
@@ -2822,12 +2594,6 @@ create_windowagg_plan(PlannerInfo *root, WindowAggPath *best_path)
 	ListCell   *lc;
 	RPRPattern *compiledPattern = NULL;
 	Bitmapset  *matchStartDependent = NULL;
-	RPRNavOffsetKind navMaxOffsetKind = RPR_NAV_OFFSET_FIXED;
-	int64		navMaxOffset = 0;
-	bool		hasFirstNav = false;
-	RPRNavOffsetKind navFirstOffsetKind = RPR_NAV_OFFSET_FIXED;
-	int64		navFirstOffset = 0;
-
 
 	/*
 	 * Choice of tlist here is motivated by the fact that WindowAgg will be
@@ -2882,15 +2648,11 @@ create_windowagg_plan(PlannerInfo *root, WindowAggPath *best_path)
 	if (wc->rpPattern)
 	{
 		/*
-		 * Walk DEFINE once: collect nav offsets (for tuplestore trim) and the
-		 * bitmapset of match_start-dependent variables (for absorption
-		 * suppression in buildRPRPattern).
+		 * Classify which DEFINE variables depend on match_start (for
+		 * absorption suppression in buildRPRPattern).  Nav offsets for
+		 * tuplestore trim are resolved later, at executor init.
 		 */
-		compute_define_metadata(wc->defineClause,
-								&navMaxOffsetKind, &navMaxOffset,
-								&hasFirstNav,
-								&navFirstOffsetKind, &navFirstOffset,
-								&matchStartDependent);
+		compute_define_metadata(wc->defineClause, &matchStartDependent);
 
 		/* Compile and optimize RPR patterns */
 		compiledPattern = buildRPRPattern(wc, !bms_is_empty(matchStartDependent));
@@ -2910,11 +2672,6 @@ create_windowagg_plan(PlannerInfo *root, WindowAggPath *best_path)
 						  best_path->runCondition,
 						  compiledPattern,
 						  matchStartDependent,
-						  navMaxOffsetKind,
-						  navMaxOffset,
-						  hasFirstNav,
-						  navFirstOffsetKind,
-						  navFirstOffset,
 						  best_path->qual,
 						  best_path->topwindow,
 						  subplan);
@@ -6988,9 +6745,6 @@ make_windowagg(List *tlist, WindowClause *wc,
 			   List *runCondition,
 			   RPRPattern *compiledPattern,
 			   Bitmapset *defineMatchStartDependent,
-			   RPRNavOffsetKind navMaxOffsetKind, int64 navMaxOffset,
-			   bool hasFirstNav,
-			   RPRNavOffsetKind navFirstOffsetKind, int64 navFirstOffset,
 			   List *qual, bool topWindow, Plan *lefttree)
 {
 	WindowAgg  *node = makeNode(WindowAgg);
@@ -7028,13 +6782,6 @@ make_windowagg(List *tlist, WindowClause *wc,
 	/* Store pre-computed match_start dependency bitmapset */
 	node->defineMatchStartDependent = defineMatchStartDependent;
 
-	/* Store pre-computed nav offsets for tuplestore trim optimization */
-	node->navMaxOffsetKind = navMaxOffsetKind;
-	node->navMaxOffset = navMaxOffset;
-	node->hasFirstNav = hasFirstNav;
-	node->navFirstOffsetKind = navFirstOffsetKind;
-	node->navFirstOffset = navFirstOffset;
-
 	plan->targetlist = tlist;
 	plan->lefttree = lefttree;
 	plan->righttree = NULL;
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index db66ebe313..0561fd8f40 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -703,9 +703,9 @@ typedef struct ExprEvalStep
 		struct
 		{
 			WindowAggState *winstate;
-			RPRNavKind	kind;	/* navigation kind (simple or compound) */
-			Datum	   *offset_value;	/* offset value(s), or NULL */
-			bool	   *offset_isnull;	/* offset null flag(s) */
+			RPRNavKind	kind;
+			int64		offset;
+			int64		compound_offset;
 			/* For compound nav: offset_value[0] = inner, [1] = outer */
 			int16		resulttyplen;	/* RESTORE: result type length */
 			bool		resulttypbyval; /* RESTORE: result pass-by-value? */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index d6ac6b229c..ad1371862a 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2726,14 +2726,16 @@ typedef struct WindowAggState
 	TupleTableSlot *temp_slot_2;
 
 	/* RPR navigation */
-	RPRNavOffsetKind navMaxOffsetKind;	/* status of navMaxOffset */
-	int64		navMaxOffset;	/* max backward nav offset (when FIXED) */
+	int64		navMaxOffset;	/* max backward nav offset, -1 means infinite
+								 * offset, retain all */
 	bool		hasFirstNav;	/* FIRST() present in DEFINE */
-	RPRNavOffsetKind navFirstOffsetKind;	/* status of navFirstOffset */
 	int64		navFirstOffset; /* min FIRST() offset (when FIXED) */
 	struct WindowObjectData *nav_winobj;	/* winobj for RPR nav fetch */
 	int64		nav_slot_pos;	/* position cached in nav_slot, or -1 */
-	TupleTableSlot *nav_slot;	/* slot for PREV/NEXT/FIRST/LAST target row */
+	TupleTableSlot *nav_slot;	/* slot holding the resolved navigation target
+								 * row (simple or compound
+								 * PREV/NEXT/FIRST/LAST) */
+
 	TupleTableSlot *nav_saved_outertuple;	/* saved slot during nav swap */
 	TupleTableSlot *nav_null_slot;	/* all NULL slot */
 	int64		nav_match_start;	/* match_start for FIRST/LAST nav */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 131c8d4ce8..ab75964755 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -594,21 +594,6 @@ typedef enum RPSkipTo
 	ST_PAST_LAST_ROW,			/* SKIP TO PAST LAST ROW */
 } RPSkipTo;
 
-/*
- * RPRNavOffsetKind - status of navigation offset for tuplestore trim.
- *
- * The planner computes navMaxOffset/navFirstOffset for tuplestore mark
- * optimization.  This enum tracks whether the value is a resolved constant,
- * needs runtime evaluation, or cannot be determined (retain all rows).
- */
-typedef enum RPRNavOffsetKind
-{
-	RPR_NAV_OFFSET_FIXED,		/* resolved constant; use the offset value */
-	RPR_NAV_OFFSET_NEEDS_EVAL,	/* non-constant offset; evaluate at executor
-								 * init */
-	RPR_NAV_OFFSET_RETAIN_ALL,	/* cannot determine; retain all rows (no trim) */
-} RPRNavOffsetKind;
-
 /*
  * RPRPatternNodeType - Row Pattern Recognition pattern node types
  */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 2b37c8f226..b4a196e1f6 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -1410,27 +1410,6 @@ typedef struct WindowAgg
 	 */
 	Bitmapset  *defineMatchStartDependent;
 
-	/*
-	 * Navigation offset status and values for tuplestore mark optimization.
-	 * See RPRNavOffsetKind in nodes/parsenodes.h.
-	 *
-	 * navMaxOffset: maximum backward reach from currentpos (contributed by
-	 * PREV, LAST-with-offset, compound PREV_LAST/NEXT_LAST).  Only valid when
-	 * navMaxOffsetKind == RPR_NAV_OFFSET_FIXED.
-	 *
-	 * navFirstOffset: minimum forward offset from match_start (contributed by
-	 * FIRST, compound PREV_FIRST/NEXT_FIRST).  Can be negative for compound
-	 * PREV_FIRST.  Only valid when navFirstOffsetKind == RPR_NAV_OFFSET_FIXED
-	 * and hasFirstNav == true.
-	 */
-	RPRNavOffsetKind navMaxOffsetKind;
-	int64		navMaxOffset;
-
-	/* true if FIRST-based navigation (FIRST, PREV_FIRST, NEXT_FIRST) is used */
-	bool		hasFirstNav;
-	RPRNavOffsetKind navFirstOffsetKind;
-	int64		navFirstOffset;
-
 	/*
 	 * false for all apart from the WindowAgg that's closest to the root of
 	 * the plan
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 8576a3c9c5..902521ff83 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -668,25 +668,40 @@ typedef struct WindowFuncRunCondition
  */
 typedef enum RPRNavKind
 {
-	RPR_NAV_PREV,
-	RPR_NAV_NEXT,
-	RPR_NAV_FIRST,
-	RPR_NAV_LAST,
+	RPR_NAV_PREV,				/* offset default: 1 */
+	RPR_NAV_NEXT,				/* offset default: 1 */
+	RPR_NAV_FIRST,				/* offset default: 0 */
+	RPR_NAV_LAST,				/* offset default: 0 */
 	/* compound: outer(inner(arg)) */
-	RPR_NAV_PREV_FIRST,
-	RPR_NAV_PREV_LAST,
-	RPR_NAV_NEXT_FIRST,
-	RPR_NAV_NEXT_LAST,
+	RPR_NAV_PREV_FIRST,			/* (offset, compound_offset) default: (0, 1) */
+	RPR_NAV_PREV_LAST,			/* (offset, compound_offset) default: (0, 1) */
+	RPR_NAV_NEXT_FIRST,			/* (offset, compound_offset) default: (0, 1) */
+	RPR_NAV_NEXT_LAST,			/* (offset, compound_offset) default: (0, 1) */
 } RPRNavKind;
 
 typedef struct RPRNavExpr
 {
 	Expr		xpr;
-	RPRNavKind	kind;			/* navigation kind */
-	Expr	   *arg;			/* argument expression */
-	Expr	   *offset_arg;		/* offset expression, or NULL for default */
-	Expr	   *compound_offset_arg;	/* outer offset for compound nav, or
-										 * NULL if simple */
+	RPRNavKind	kind;
+	/* argument expression */
+	Expr	   *arg;
+	/* offset expression */
+	Expr	   *offset_arg;
+	/* outer offset for compound navigation */
+	Expr	   *compound_offset_arg;
+
+	/*
+	 * The computed offset value, for default value see RPRNavKind comments
+	 * above. The value will be set in visit_nav_exec.
+	 */
+	int64		offset;
+
+	/*
+	 * The computeed compound_offset offset value, for default value see
+	 * RPRNavKind comments above. The value will be set in visit_nav_exec.
+	 */
+	int64		compound_offset;
+
 	/* result type (same as arg's type) */
 	Oid			resulttype pg_node_attr(query_jumble_ignore);
 	/* OID of collation of result */
diff --git a/src/test/regress/expected/rpr_explain.out b/src/test/regress/expected/rpr_explain.out
index d8343c9acc..77afb5e1ed 100644
--- a/src/test/regress/expected/rpr_explain.out
+++ b/src/test/regress/expected/rpr_explain.out
@@ -5775,7 +5775,7 @@ EXPLAIN (COSTS OFF) EXECUTE rpr_nav_offset_prep(2);
  WindowAgg
    Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    Pattern: a+"
-   Nav Mark Lookback: runtime
+   Nav Mark Lookback: 2
    ->  Function Scan on generate_series s
 (5 rows)
 
@@ -5974,7 +5974,7 @@ EXPLAIN (COSTS OFF) EXECUTE test_overflow_lookback(4611686018427387904, 46116860
  WindowAgg
    Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    Pattern: a+
-   Nav Mark Lookback: runtime
+   Nav Mark Lookback: retain all
    ->  Function Scan on generate_series s
 (5 rows)
 
@@ -6042,7 +6042,7 @@ EXPLAIN (COSTS OFF) EXECUTE p_first_runtime(1, 1);
    Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    Pattern: a+
    Nav Mark Lookback: 0
-   Nav Mark Lookahead: runtime
+   Nav Mark Lookahead: 2
    ->  Function Scan on generate_series s
 (6 rows)
 
diff --git a/src/test/regress/expected/rpr_integration.out b/src/test/regress/expected/rpr_integration.out
index 541ad35de4..ec335fd733 100644
--- a/src/test/regress/expected/rpr_integration.out
+++ b/src/test/regress/expected/rpr_integration.out
@@ -1077,7 +1077,7 @@ EXPLAIN (COSTS OFF) EXECUTE rpr_prev(1);
  WindowAgg
    Window: w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
    Pattern: a b+
-   Nav Mark Lookback: runtime
+   Nav Mark Lookback: 1
    ->  Sort
          Sort Key: id
          ->  Seq Scan on rpr_integ
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index c7c456fa64..6adee8b018 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2539,7 +2539,6 @@ RPRNFAContext
 RPRNFAState
 RPRNavExpr
 RPRNavKind
-RPRNavOffsetKind
 RPRPattern
 RPRPatternElement
 RPRPatternNode
-- 
2.34.1