0003-Context-absorption.txt

text/plain

Filename: 0003-Context-absorption.txt
Type: text/plain
Part: 2
Message: Re: Row pattern recognition
From 0ef3fa76fa1aa340a56eb5841183d61bc8129f3e Mon Sep 17 00:00:00 2001
From: Henson Choi <assam258@gmail.com>
Date: Thu, 15 Jan 2026 22:19:07 +0900
Subject: [PATCH] Row pattern recognition: Enable context absorption only for
 SKIP PAST LAST ROW

---
 src/backend/executor/nodeWindowAgg.c | 10 +++--
 src/test/regress/expected/rpr.out    | 63 +++++++++++++++++++---------
 src/test/regress/sql/rpr.sql         | 18 ++++++++
 3 files changed, 68 insertions(+), 23 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 3fc73db4e59..4af9998aacf 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -4796,8 +4796,9 @@ update_reduced_frame(WindowObject winobj, int64 pos)
 				if (ctx->states != NULL)
 					nfa_step(winstate, ctx, NULL, currentPos - 1);
 			}
-			/* Absorb completed contexts at partition boundary */
-			nfa_absorb_contexts(winstate, NULL, currentPos - 1);
+			/* Absorb completed contexts at partition boundary (SKIP PAST LAST ROW only) */
+			if (winstate->rpSkipTo == ST_PAST_LAST_ROW)
+				nfa_absorb_contexts(winstate, NULL, currentPos - 1);
 			break;
 		}
 
@@ -4815,12 +4816,13 @@ update_reduced_frame(WindowObject winobj, int64 pos)
 		nfa_start_context(winstate, currentPos + 1);
 
 		/*
-		 * Absorb redundant contexts.
+		 * Absorb redundant contexts (SKIP PAST LAST ROW only).
 		 * At the same elementIndex, if newer context's count <= older context's count,
 		 * the newer context can be absorbed (for unbounded quantifiers).
 		 * Note: Never absorb targetCtx - it's the context we're trying to complete.
 		 */
-		nfa_absorb_contexts(winstate, targetCtx, currentPos);
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW)
+			nfa_absorb_contexts(winstate, targetCtx, currentPos);
 
 		/* Check if target context is now complete */
 		if (targetCtx->states == NULL)
diff --git a/src/test/regress/expected/rpr.out b/src/test/regress/expected/rpr.out
index 652b1eb811d..f22bba23bff 100644
--- a/src/test/regress/expected/rpr.out
+++ b/src/test/regress/expected/rpr.out
@@ -884,26 +884,51 @@ SELECT company, tdate, price, first_value(tdate) OVER w, last_value(tdate) OVER
 ----------+------------+-------+-------------+------------
  company1 | 07-01-2023 |   100 |             | 
  company1 | 07-02-2023 |   200 | 07-02-2023  | 07-05-2023
- company1 | 07-03-2023 |   150 |             | 
- company1 | 07-04-2023 |   140 |             | 
+ company1 | 07-03-2023 |   150 | 07-03-2023  | 07-05-2023
+ company1 | 07-04-2023 |   140 | 07-04-2023  | 07-05-2023
  company1 | 07-05-2023 |   150 |             | 
  company1 | 07-06-2023 |    90 |             | 
  company1 | 07-07-2023 |   110 | 07-07-2023  | 07-10-2023
- company1 | 07-08-2023 |   130 |             | 
- company1 | 07-09-2023 |   120 |             | 
+ company1 | 07-08-2023 |   130 | 07-08-2023  | 07-10-2023
+ company1 | 07-09-2023 |   120 | 07-09-2023  | 07-10-2023
  company1 | 07-10-2023 |   130 |             | 
  company2 | 07-01-2023 |    50 |             | 
  company2 | 07-02-2023 |  2000 | 07-02-2023  | 07-05-2023
- company2 | 07-03-2023 |  1500 |             | 
- company2 | 07-04-2023 |  1400 |             | 
+ company2 | 07-03-2023 |  1500 | 07-03-2023  | 07-05-2023
+ company2 | 07-04-2023 |  1400 | 07-04-2023  | 07-05-2023
  company2 | 07-05-2023 |  1500 |             | 
  company2 | 07-06-2023 |    60 |             | 
  company2 | 07-07-2023 |  1100 | 07-07-2023  | 07-10-2023
- company2 | 07-08-2023 |  1300 |             | 
- company2 | 07-09-2023 |  1200 |             | 
+ company2 | 07-08-2023 |  1300 | 07-08-2023  | 07-10-2023
+ company2 | 07-09-2023 |  1200 | 07-09-2023  | 07-10-2023
  company2 | 07-10-2023 |  1300 |             | 
 (20 rows)
 
+-- SKIP TO NEXT ROW with limited frame (Ishii-san's test case)
+-- Each row should produce its own match within its frame
+WITH data AS (
+ SELECT * FROM (VALUES
+  ('A', 1), ('A', 2),
+  ('B', 3), ('B', 4)
+ ) AS t(gid, id)
+)
+SELECT gid, id, array_agg(id) OVER w
+FROM data
+WINDOW w AS (
+ PARTITION BY gid
+ ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING
+ AFTER MATCH SKIP TO NEXT ROW
+ PATTERN (A+)
+ DEFINE A AS id < 10
+);
+ gid | id | array_agg 
+-----+----+-----------
+ A   |  1 | {1,2}
+ A   |  2 | {2}
+ B   |  3 | {3,4}
+ B   |  4 | {4}
+(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
@@ -2340,8 +2365,8 @@ WINDOW w AS (
   5 | {B}   |             |          
   6 | {C}   |             |          
   7 | {B}   |           7 |        10
-  8 | {B}   |             |          
-  9 | {B}   |             |          
+  8 | {B}   |           8 |        10
+  9 | {B}   |           9 |        10
  10 | {D}   |             |          
 (10 rows)
 
@@ -2556,9 +2581,9 @@ WINDOW w AS (
  id | flags | match_start | match_end 
 ----+-------+-------------+-----------
   1 | {A}   |           1 |         4
-  2 | {A}   |             |          
-  3 | {A}   |             |          
-  4 | {A}   |             |          
+  2 | {A}   |           2 |         4
+  3 | {A}   |           3 |         4
+  4 | {A}   |           4 |         4
   5 | {B}   |             |          
 (5 rows)
 
@@ -2589,8 +2614,8 @@ WINDOW w AS (
  id | flags | match_start | match_end 
 ----+-------+-------------+-----------
   1 | {A}   |           1 |         4
-  2 | {A}   |             |          
-  3 | {A}   |             |          
+  2 | {A}   |           2 |         4
+  3 | {A}   |           3 |         4
   4 | {B}   |             |          
   5 | {X}   |             |          
 (5 rows)
@@ -2623,8 +2648,8 @@ WINDOW w AS (
  id | flags | match_start | match_end 
 ----+-------+-------------+-----------
   1 | {B}   |           1 |         4
-  2 | {B}   |             |          
-  3 | {B}   |             |          
+  2 | {B}   |           2 |         4
+  3 | {B}   |           3 |         4
   4 | {D}   |             |          
   5 | {X}   |             |          
 (5 rows)
@@ -2691,7 +2716,7 @@ WINDOW w AS (
 ----+-------+-------------+-----------
   1 | {A}   |           1 |         6
   2 | {B}   |             |          
-  3 | {A}   |             |          
+  3 | {A}   |           3 |         6
   4 | {B}   |             |          
   5 | {A}   |             |          
   6 | {B}   |             |          
@@ -2725,7 +2750,7 @@ WINDOW w AS (
  id | flags | match_start | match_end 
 ----+-------+-------------+-----------
   1 | {A}   |           1 |         4
-  2 | {A}   |             |          
+  2 | {A}   |           2 |         4
   3 | {B}   |             |          
   4 | {B}   |             |          
   5 | {X}   |             |          
diff --git a/src/test/regress/sql/rpr.sql b/src/test/regress/sql/rpr.sql
index 19146f7f03d..93bf1698fd9 100644
--- a/src/test/regress/sql/rpr.sql
+++ b/src/test/regress/sql/rpr.sql
@@ -355,6 +355,24 @@ SELECT company, tdate, price, first_value(tdate) OVER w, last_value(tdate) OVER
   B AS price > 100
 );
 
+-- SKIP TO NEXT ROW with limited frame (Ishii-san's test case)
+-- Each row should produce its own match within its frame
+WITH data AS (
+ SELECT * FROM (VALUES
+  ('A', 1), ('A', 2),
+  ('B', 3), ('B', 4)
+ ) AS t(gid, id)
+)
+SELECT gid, id, array_agg(id) OVER w
+FROM data
+WINDOW w AS (
+ PARTITION BY gid
+ ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING
+ AFTER MATCH SKIP TO NEXT ROW
+ PATTERN (A+)
+ DEFINE A AS id < 10
+);
+
 -- 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)