From 3cdc30c795f918b5925ffdce9dbc91a08fe92a5e Mon Sep 17 00:00:00 2001
From: Henson Choi <assam258@gmail.com>
Date: Fri, 16 Jan 2026 08:22:08 +0900
Subject: [PATCH 5/5] Row pattern recognition: Disable context absorption for
 limited frame

---
 src/backend/executor/nodeWindowAgg.c |  8 +++++---
 src/test/regress/expected/rpr.out    | 27 +++++++++++++++++++++++++++
 src/test/regress/sql/rpr.sql         | 20 ++++++++++++++++++++
 3 files changed, 52 insertions(+), 3 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index f02bff4251a..5dea161c928 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -4793,7 +4793,7 @@ update_reduced_frame(WindowObject winobj, int64 pos)
 		{
 			nfa_finalize_all_contexts(winstate, currentPos - 1);
 			/* Absorb completed contexts at partition boundary (SKIP PAST LAST ROW only) */
-			if (winstate->rpSkipTo == ST_PAST_LAST_ROW)
+			if (winstate->rpSkipTo == ST_PAST_LAST_ROW && !hasLimitedFrame)
 				nfa_absorb_contexts(winstate, NULL, currentPos - 1);
 			break;
 		}
@@ -4812,12 +4812,14 @@ update_reduced_frame(WindowObject winobj, int64 pos)
 		nfa_start_context(winstate, currentPos + 1);
 
 		/*
-		 * Absorb redundant contexts (SKIP PAST LAST ROW only).
+		 * Absorb redundant contexts (SKIP PAST LAST ROW only, unbounded frame).
 		 * At the same elementIndex, if newer context's count <= older context's count,
 		 * the newer context can be absorbed (for unbounded quantifiers).
+		 * With limited frame, different contexts have different frame boundaries,
+		 * so absorption is not safe.
 		 * Note: Never absorb targetCtx - it's the context we're trying to complete.
 		 */
-		if (winstate->rpSkipTo == ST_PAST_LAST_ROW)
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW && !hasLimitedFrame)
 			nfa_absorb_contexts(winstate, targetCtx, currentPos);
 
 		/* Check if target context is now complete */
diff --git a/src/test/regress/expected/rpr.out b/src/test/regress/expected/rpr.out
index f22bba23bff..1567fec953d 100644
--- a/src/test/regress/expected/rpr.out
+++ b/src/test/regress/expected/rpr.out
@@ -929,6 +929,33 @@ WINDOW w AS (
  B   |  4 | {4}
 (4 rows)
 
+-- Limited frame with absorption test
+-- Row 0: frame [0,2], can't see B at row 3 -> no match
+-- Row 1: frame [1,3], can see A A B -> should match rows 1-3
+WITH frame_absorb_test AS (
+ SELECT * FROM (VALUES
+  (0, 'A'), (1, 'A'), (2, 'A'), (3, 'B')
+ ) AS t(id, flag)
+)
+SELECT id, flag, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
+FROM frame_absorb_test
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A+ B)
+ DEFINE
+  A AS flag = 'A',
+  B AS flag = 'B'
+);
+ id | flag | match_start | match_end 
+----+------+-------------+-----------
+  0 | A    |             |          
+  1 | A    |           1 |         3
+  2 | A    |             |          
+  3 | B    |             |          
+(4 rows)
+
 -- ROWS BETWEEN CURRENT ROW AND offset FOLLOWING
 SELECT company, tdate, price, first_value(tdate) OVER w, last_value(tdate) OVER w,
  count(*) OVER w
diff --git a/src/test/regress/sql/rpr.sql b/src/test/regress/sql/rpr.sql
index 93bf1698fd9..6b70c82b2c7 100644
--- a/src/test/regress/sql/rpr.sql
+++ b/src/test/regress/sql/rpr.sql
@@ -373,6 +373,26 @@ WINDOW w AS (
  DEFINE A AS id < 10
 );
 
+-- Limited frame with absorption test
+-- Row 0: frame [0,2], can't see B at row 3 -> no match
+-- Row 1: frame [1,3], can see A A B -> should match rows 1-3
+WITH frame_absorb_test AS (
+ SELECT * FROM (VALUES
+  (0, 'A'), (1, 'A'), (2, 'A'), (3, 'B')
+ ) AS t(id, flag)
+)
+SELECT id, flag, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
+FROM frame_absorb_test
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A+ B)
+ DEFINE
+  A AS flag = 'A',
+  B AS flag = 'B'
+);
+
 -- ROWS BETWEEN CURRENT ROW AND offset FOLLOWING
 SELECT company, tdate, price, first_value(tdate) OVER w, last_value(tdate) OVER w,
  count(*) OVER w
-- 
2.50.1 (Apple Git-155)

