v50-0001-Refactor-visit_nav_plan.partial

application/octet-stream

Filename: v50-0001-Refactor-visit_nav_plan.partial
Type: application/octet-stream
Part: 1
Message: Re: Row pattern recognition
From cab28ed773c49a2aac53b0d3e9686b6a60b5cb84 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 2 Jul 2026 17:36:49 +0800
Subject: [PATCH v50 1/4] Refactor visit_nav_plan

Previously the RPR DEFINE clause was walked twice for navigation offsets:
visit_nav_plan() did something with constant offsets, while visit_nav_exec() did
something for non-constant offsets at ExecInitWindowAgg.

The only thing the planner really need produce is the match_start-dependency
classification, because buildRPRPattern() consumes it to decide whether context
absorption is safe and bakes the result into the compiled pattern.

EXPLAIN without ANALYZE also executes ExecInitWindowAgg, computing both
navigation and compound offsets there is sufficient.

The plan-time offset folding is not needed.  The tuplestore is built at executor
init, so the trim offsets only have to be known by then, and visit_nav_exec()
already evaluates every offset expression there (constants included).

So narrow visit_nav_plan() (and compute_define_metadata()) to that single
classification pass, and move all offset computation into eval_define_offsets()
at ExecInitWindowAgg.

remove
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;

remove below struct WindowAgg field
{
    int64           navMaxOffset;

    /* true if FIRST-based navigation (FIRST, PREV_FIRST, NEXT_FIRST) is used */
    bool            hasFirstNav;
    int64           navFirstOffset;
}
There fields exists in struct WindowAggState also.

Based on https://github.com/assam258-5892/postgres/commits/RPR
---
 src/backend/commands/explain.c                |  69 +----
 src/backend/executor/nodeWindowAgg.c          |  90 +++---
 src/backend/optimizer/plan/createplan.c       | 292 ++----------------
 src/include/nodes/execnodes.h                 |   5 +-
 src/include/nodes/parsenodes.h                |  15 -
 src/include/nodes/plannodes.h                 |  21 --
 src/test/regress/expected/rpr_explain.out     |   6 +-
 src/test/regress/expected/rpr_integration.out |   2 +-
 src/tools/pgindent/typedefs.list              |   1 -
 9 files changed, 87 insertions(+), 414 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/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index e4e97a6ed9..8995329949 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 */
@@ -4028,6 +4023,7 @@ 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;
 
 /*
@@ -4115,6 +4111,9 @@ visit_nav_exec(NavTraversal *t, RPRNavExpr *nav)
 
 		reach = eval_nav_offset_helper(context->winstate,
 									   nav->offset_arg, 0);
+
+		context->hasFirst = true;
+
 		context->minFirstOffset = Min(context->minFirstOffset, reach);
 	}
 	else if (nav->kind == RPR_NAV_PREV_FIRST ||
@@ -4144,39 +4143,45 @@ visit_nav_exec(NavTraversal *t, RPRNavExpr *nav)
 			if (pg_add_s64_overflow(inner, outer, &reach))
 				reach = PG_INT64_MAX;
 		}
+		context->hasFirst = true;
 		context->minFirstOffset = Min(context->minFirstOffset, reach);
 	}
 }
 
 /*
  * 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 +4191,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;
+
+	/* Forward (FIRST) reach; never needs a retain-all sentinel */
+	winstate->hasFirstNav = ctx.hasFirst;
 
-	if (needsFirst)
+	if (ctx.hasFirst)
 	{
-		winstate->navFirstOffsetKind = RPR_NAV_OFFSET_FIXED;
 		if (ctx.minFirstOffset < PG_INT64_MAX)
 			winstate->navFirstOffset = ctx.minFirstOffset;
 		else
@@ -4384,13 +4387,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 +4403,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..8146ad1070 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,72 +2475,25 @@ 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
- *
- *		  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.
+ * 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
@@ -2568,130 +2518,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 +2538,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 +2569,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 +2597,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 +2651,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 +2675,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 +6748,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 +6785,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/nodes/execnodes.h b/src/include/nodes/execnodes.h
index d6ac6b229c..1f9508649c 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2726,10 +2726,9 @@ 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 */
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/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