From 3cdc30c795f918b5925ffdce9dbc91a08fe92a5e Mon Sep 17 00:00:00 2001 From: Henson Choi 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)