nocfbot-0076-Clean-up-RPR-regression-test-comments-and-add-gro.txt

text/plain

Filename: nocfbot-0076-Clean-up-RPR-regression-test-comments-and-add-gro.txt
Type: text/plain
Part: 73
Message: Re: Row pattern recognition
From 18aaeff3851eddd785341d6d7d04ccc2a2e8069f Mon Sep 17 00:00:00 2001
From: Henson Choi <assam258@gmail.com>
Date: Thu, 11 Jun 2026 09:19:20 +0900
Subject: [PATCH 76/77] Clean up RPR regression test comments and add
 group-merge tests

The regression test comments referenced internal implementation details --
static helper functions (mergeConsecutiveVars/Groups/Alts,
mergeGroupPrefixSuffix, rprPatternChildrenEqual, tryUnwrapGroup,
nfa_advance_begin/end/var/alt, nfa_route_to_elem, nfa_state_clone), the
execRPR.c file name, the isAbsorbable/hasAbsorbableState flags, and a
verbatim source conditional -- which drift when those helpers are renamed
or refactored.  Replace each with a comment describing the pattern
transformation or NFA behavior exercised.

Also add two EXPLAIN tests for prefix/suffix group merging: a 5-way
PREFIX+SUFFIX merge (B A B (A B)+ A B A B -> b (a b){4,}) and a negative
case where a differing prefix prevents the merge.
---
 src/test/regress/expected/rpr_base.out    | 56 ++++++++++++++++-------
 src/test/regress/expected/rpr_explain.out |  4 +-
 src/test/regress/expected/rpr_nfa.out     | 39 ++++++++--------
 src/test/regress/sql/rpr_base.sql         | 36 ++++++++-------
 src/test/regress/sql/rpr_explain.sql      |  4 +-
 src/test/regress/sql/rpr_nfa.sql          | 39 ++++++++--------
 6 files changed, 100 insertions(+), 78 deletions(-)

diff --git a/src/test/regress/expected/rpr_base.out b/src/test/regress/expected/rpr_base.out
index 302caea1e5d..f73758ee22d 100644
--- a/src/test/regress/expected/rpr_base.out
+++ b/src/test/regress/expected/rpr_base.out
@@ -3739,7 +3739,6 @@ WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
 (7 rows)
 
 -- Consecutive VAR merge: A A+ -> a{2,}
--- Exercises the child->max == RPR_QUANTITY_INF branch in mergeConsecutiveVars,
 -- where a finite prev (A{1,1}) meets an infinite child (A+).
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_plan
@@ -3808,8 +3807,7 @@ WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
 (7 rows)
 
 -- Consecutive GROUP merge: (A B){2} (A B)+ -> (a b){3,}
--- Exercises the child->max == RPR_QUANTITY_INF branch in mergeConsecutiveGroups,
--- where a finite prev ((A B){2,2}) meets an infinite child ((A B)+).
+-- Where a finite prev ((A B){2,2}) meets an infinite child ((A B)+).
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_plan
 WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
@@ -4366,7 +4364,6 @@ WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
 (7 rows)
 
 -- Consecutive GROUP merge with unbounded: (A+) (A+) -> a{2,}
--- Tests mergeConsecutiveGroups with child->max == INF
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_plan
 WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
@@ -4383,7 +4380,6 @@ WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
 (7 rows)
 
 -- Consecutive GROUP merge finite: (A{10}){20} -> a{200}
--- Tests mergeConsecutiveGroups with both finite
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_plan
 WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
@@ -4400,7 +4396,6 @@ WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
 (7 rows)
 
 -- Different GROUP prevents merge: (A B){2} (C D){3}
--- Tests mergeConsecutiveGroups flush previous
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_plan
 WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
@@ -4419,7 +4414,6 @@ WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
 (7 rows)
 
 -- Different children count prevents merge: (A B)+ (A B C)+
--- Tests rprPatternChildrenEqual length check
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_plan
 WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
@@ -4437,7 +4431,6 @@ WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
 (7 rows)
 
 -- PREFIX only merge: A B (A B)+ -> (a b){2,}
--- Tests mergeGroupPrefixSuffix: absorb preceding elements into GROUP min
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_plan
 WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
@@ -4454,7 +4447,6 @@ WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
 (7 rows)
 
 -- SUFFIX only merge: (A B)+ A B -> (a b){2,}
--- Tests mergeGroupPrefixSuffix: absorb following elements into GROUP min
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_plan
 WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
@@ -4471,7 +4463,6 @@ WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
 (7 rows)
 
 -- Multiple SUFFIX absorption with skipUntil: (A B)+ A B A B C
--- Tests mergeGroupPrefixSuffix: skip absorbed suffix elements
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_plan
 WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
@@ -4488,8 +4479,7 @@ WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
          ->  Seq Scan on rpr_plan
 (7 rows)
 
--- PREFIX merge with remaining prefix: A B C D (C D)+
--- Tests mergeGroupPrefixSuffix: trimmed list reconstruction
+-- PREFIX merge with remaining prefix: A B C D (C D)+  -> A B (C D) {2,}
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_plan
 WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
@@ -4507,8 +4497,26 @@ WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
          ->  Seq Scan on rpr_plan
 (7 rows)
 
+-- cannot merge, prefix is different
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) OVER w
+FROM rpr_plan
+WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+PATTERN (A B C D C (C D) +)
+DEFINE A AS val <= 25, B AS val > 25,
+       C AS val > 50, D AS val > 75);
+                                  QUERY PLAN                                   
+-------------------------------------------------------------------------------
+ WindowAgg
+   Window: w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   Pattern: a b c d c (c d)+
+   Nav Mark Lookback: 0
+   ->  Sort
+         Sort Key: id
+         ->  Seq Scan on rpr_plan
+(7 rows)
+
 -- PREFIX merge with quantifiers: A B* (A B*)+ -> (a b*){2,}
--- Tests mergeGroupPrefixSuffix: quantifier comparison in rprPatternEqual
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_plan
 WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
@@ -4526,7 +4534,6 @@ WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
 (7 rows)
 
 -- PREFIX merge with multiple quantifiers: A+ B* C? (A+ B* C?)+ -> (a+ b* c?){2,}
--- Tests mergeGroupPrefixSuffix: complex quantifier patterns
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_plan
 WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
@@ -4544,7 +4551,6 @@ WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
 (7 rows)
 
 -- SUFFIX merge with quantifiers: (A B*)+ A B* -> (a b*){2,}
--- Tests mergeGroupPrefixSuffix: suffix with quantifiers
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_plan
 WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
@@ -4562,7 +4568,6 @@ WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
 (7 rows)
 
 -- Unwrap GROUP{1,1}: ((A | B | C)) -> (a | b | c)
--- Tests tryUnwrapGroup removing redundant outer GROUP
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_plan
 WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
@@ -4816,6 +4821,23 @@ WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
          ->  Seq Scan on rpr_plan
 (7 rows)
 
+-- PREFIX+SUFFIX merge (5-way): B A B (A B)+ A B A B -> b (a b){4,}
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) OVER w FROM rpr_plan
+WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+             PATTERN (B A B (A B)+ A B A B)
+             DEFINE A AS val <= 50, B AS val > 50);
+                                  QUERY PLAN                                   
+-------------------------------------------------------------------------------
+ WindowAgg
+   Window: w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+   Pattern: b (a b){4,}
+   Nav Mark Lookback: 0
+   ->  Sort
+         Sort Key: id
+         ->  Seq Scan on rpr_plan
+(7 rows)
+
 -- Unwrap single-item ALT after dedup: (A | A)+ -> a+
 -- ALT dedup reduces to single-item, then GROUP unwrap
 EXPLAIN (COSTS OFF)
@@ -7069,7 +7091,7 @@ WINDOW w AS (
 (10 rows)
 
 -- Test: (A+ | (A | B)+)* - nested alternation inside quantified group
--- Previously caused infinite recursion in nfa_advance_alt when the inner
+-- Previously caused infinite recursion in alternation handling when the inner
 -- BEGIN(+)'s skip jump was followed as an ALT branch pointer.
 SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
 FROM (VALUES
diff --git a/src/test/regress/expected/rpr_explain.out b/src/test/regress/expected/rpr_explain.out
index bcbf4f941ba..cc86d0aae30 100644
--- a/src/test/regress/expected/rpr_explain.out
+++ b/src/test/regress/expected/rpr_explain.out
@@ -541,7 +541,7 @@ WINDOW w AS (
 (10 rows)
 
 -- Consecutive ALT merge followed by different ALT
--- Tests mergeConsecutiveAlts flush on ALT change: (A|B){2} (C|D)
+-- ((A | B) (A | B) (C | D)) -> (A|B){2} (C|D)
 CREATE VIEW rpr_ev_state_alt_merge_alt AS
 SELECT count(*) OVER w
 FROM generate_series(1, 40) AS s(v)
@@ -582,7 +582,7 @@ WINDOW w AS (
 (10 rows)
 
 -- Consecutive ALT merge followed by non-ALT element
--- Tests mergeConsecutiveAlts flush on non-ALT: (A|B){2} c
+-- ((A | B) (A | B) C) -> (A|B){2} C
 CREATE VIEW rpr_ev_state_alt_merge_nonalt AS
 SELECT count(*) OVER w
 FROM generate_series(1, 40) AS s(v)
diff --git a/src/test/regress/expected/rpr_nfa.out b/src/test/regress/expected/rpr_nfa.out
index d7146168f2b..1fc85a365f6 100644
--- a/src/test/regress/expected/rpr_nfa.out
+++ b/src/test/regress/expected/rpr_nfa.out
@@ -534,7 +534,6 @@ WINDOW w AS (
 (11 rows)
 
 -- Consecutive vars merged to fixed-length: (A B B)+ -> (A B{2})+
--- mergeConsecutiveVars produces B{2}; now absorbable with fixed-length check
 WITH test_absorb_consecutive AS (
     SELECT * FROM (VALUES
         (1,  ARRAY['A']),
@@ -1192,7 +1191,7 @@ WINDOW w AS (
 (5 rows)
 
 -- Optional reluctant group: (A B)?? C
--- nfa_advance_begin: reluctant tries skip first, but skip path needs C
+-- Reluctant group entry tries skip first, but the skip path needs C
 -- at row 1 which is A -> skip fails. Enter path succeeds: A(1) B(2) C(3).
 WITH test_optional_reluctant AS (
     SELECT * FROM (VALUES
@@ -1225,8 +1224,8 @@ WINDOW w AS (
 -- Non-leading reluctant optional VAR: (B A?? C)
 -- Reluctant A?? should prefer to skip, matching B(1) C(2) with A left
 -- unmatched (match_end 2).  The leading/group reluctant cases above go through
--- the begin path; this exercises the non-leading skip path in
--- nfa_route_to_elem, which must honor reluctant ordering too.
+-- the begin path; this exercises the non-leading skip path,
+-- which must honor reluctant ordering too.
 WITH test_nonleading_reluctant AS (
     SELECT * FROM (VALUES
         (1, ARRAY['B']),
@@ -1256,8 +1255,8 @@ WINDOW w AS (
 (3 rows)
 
 -- Reluctant outer quantifier over a nullable reluctant body: SQL/RPR
--- semantics call for the shortest (empty) match.  The count<min branch of
--- nfa_advance_end must prefer the fast-forward (exit) path for reluctant
+-- semantics call for the shortest (empty) match.  In the count<min case
+-- the engine must prefer the fast-forward (exit) path for reluctant
 -- groups and suppress longer matches once exit reaches FIN, mirroring the
 -- sibling min<=count<max branch.  The 2-level greedy/reluctant matrix plus a
 -- min>=2 boundary and single-quantifier controls localize the behaviour: only
@@ -1290,7 +1289,7 @@ ORDER BY id;
 
 -- Non-leading reluctant optional GROUP with a follower: (B (A X)?? C)
 -- Like the VAR case above but a multi-element group; it goes through the
--- begin path (nfa_advance_begin), which already honors reluctant ordering.
+-- begin path, which already honors reluctant ordering.
 -- Reluctant (A X)?? should skip, matching B(1) C(2), with the group skipped
 -- to the following C (not to FIN).
 WITH test_nonleading_reluctant_group AS (
@@ -1357,7 +1356,7 @@ WINDOW w AS (
 
 -- Reluctant optional group skip-to-FIN
 -- When a reluctant optional group's skip path reaches FIN, the group
--- entry path is abandoned (execRPR.c nfa_advance_begin).
+-- entry path is abandoned.
 -- Pattern: C (A B)?? -- after C matches, the reluctant group (A B)??
 -- prefers to skip.  Skip goes to FIN (group is last element), so
 -- the match completes with just C.
@@ -2381,11 +2380,11 @@ WINDOW w AS (
   4 | {B,_} |             |          
 (4 rows)
 
--- A{3,5}? B (reluctant bounded mid-band): the VAR-level count in
--- nfa_advance_var cycles through 3, 4, 5 within a single match
--- attempt.  Exercises the count > 2 && reluctant && !isAbsorbable
--- branch (absorbability analysis excludes reluctant quantifiers, so
--- isAbsorbable stays false for A).
+-- A{3,5}? B (reluctant bounded mid-band): the VAR-level count
+-- cycles through 3, 4, 5 within a single match attempt.  Exercises
+-- a reluctant bounded quantifier that absorbability analysis excludes
+-- (reluctant quantifiers are never absorbable, so A stays
+-- non-absorbable).
 WITH test_reluctant_mid_band AS (
     SELECT * FROM (VALUES
         (1, ARRAY['A']),
@@ -3364,7 +3363,7 @@ WINDOW w AS (
 -- Nested END->END fast-forward
 -- When an inner group has a nullable body and count < min, the
 -- fast-forward path exits through the outer END, incrementing
--- the outer group's count (execRPR.c nfa_advance_end).
+-- the outer group's count.
 -- Pattern: ((A?){2,3}){2,3} -- nested groups, neither collapses
 -- because the optimizer cannot safely multiply non-exact quantifiers.
 -- Data has no A rows, forcing all-empty iterations via fast-forward.
@@ -4067,7 +4066,7 @@ WINDOW w AS (
 
 -- Non-absorbable context during absorption
 -- Pattern (A B)+ C: A,B in absorbable group, C is not.
--- When END exits to C via nfa_state_clone, isAbsorbable becomes false.
+-- When END exits to C, the cloned context becomes non-absorbable.
 WITH test_non_absorbable AS (
     SELECT * FROM (VALUES
         (1, ARRAY['A']),
@@ -4102,9 +4101,9 @@ WINDOW w AS (
   6 | {_}   |             |          
 (6 rows)
 
--- Absorption flags early return (!hasAbsorbableState)
+-- Absorption skipped when no absorbable state remains
 -- Pattern (A B)+ C D with SKIP PAST LAST ROW
--- After reaching C (non-absorbable), hasAbsorbableState becomes false.
+-- After reaching C (non-absorbable), no absorbable state remains.
 -- On next row (D), the early return fires.
 WITH test_absorption_early_return AS (
     SELECT * FROM (VALUES
@@ -4212,11 +4211,11 @@ WINDOW w AS (
   4 | {_}   |             |          
 (4 rows)
 
--- Absorb skips non-absorbable context (!hasAbsorbableState)
+-- Absorb skips a context with no absorbable state
 -- Pattern A+ | B C with SKIP PAST LAST ROW (only A+ branch absorbable).
 -- Row 1: B only -> Ctx1 takes B branch (non-absorbable), advances to C.
--- Row 2: C,A -> Ctx1 C matches (hasAbsorbableState=false). Ctx2 takes A (absorbable).
--- Absorption: Ctx1 !hasAbsorbableState -> skip.
+-- Row 2: C,A -> Ctx1 C matches (no absorbable state). Ctx2 takes A (absorbable).
+-- Absorption: Ctx1 has no absorbable state -> skip.
 WITH test_older_non_absorbable AS (
     SELECT * FROM (VALUES
         (1, ARRAY['B', '_']),
diff --git a/src/test/regress/sql/rpr_base.sql b/src/test/regress/sql/rpr_base.sql
index 5e95859f758..7bb45b6bcf4 100644
--- a/src/test/regress/sql/rpr_base.sql
+++ b/src/test/regress/sql/rpr_base.sql
@@ -2471,7 +2471,6 @@ WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
              PATTERN (A+ A*) DEFINE A AS val > 0);
 
 -- Consecutive VAR merge: A A+ -> a{2,}
--- Exercises the child->max == RPR_QUANTITY_INF branch in mergeConsecutiveVars,
 -- where a finite prev (A{1,1}) meets an infinite child (A+).
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_plan
@@ -2500,8 +2499,7 @@ WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
              PATTERN ((A B)+ (A B)+) DEFINE A AS val <= 50, B AS val > 50);
 
 -- Consecutive GROUP merge: (A B){2} (A B)+ -> (a b){3,}
--- Exercises the child->max == RPR_QUANTITY_INF branch in mergeConsecutiveGroups,
--- where a finite prev ((A B){2,2}) meets an infinite child ((A B)+).
+-- Where a finite prev ((A B){2,2}) meets an infinite child ((A B)+).
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_plan
 WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
@@ -2722,21 +2720,18 @@ WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
              DEFINE A AS val <= 20, B AS val > 20 AND val <= 70, C AS val > 70);
 
 -- Consecutive GROUP merge with unbounded: (A+) (A+) -> a{2,}
--- Tests mergeConsecutiveGroups with child->max == INF
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_plan
 WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
              PATTERN ((A+) (A+)) DEFINE A AS val > 0);
 
 -- Consecutive GROUP merge finite: (A{10}){20} -> a{200}
--- Tests mergeConsecutiveGroups with both finite
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_plan
 WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
              PATTERN ((A{10}){20}) DEFINE A AS val > 0);
 
 -- Different GROUP prevents merge: (A B){2} (C D){3}
--- Tests mergeConsecutiveGroups flush previous
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_plan
 WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
@@ -2745,7 +2740,6 @@ WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
                     C AS val > 50 AND val <= 75, D AS val > 75);
 
 -- Different children count prevents merge: (A B)+ (A B C)+
--- Tests rprPatternChildrenEqual length check
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_plan
 WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
@@ -2753,29 +2747,25 @@ WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
              DEFINE A AS val <= 33, B AS val > 33 AND val <= 66, C AS val > 66);
 
 -- PREFIX only merge: A B (A B)+ -> (a b){2,}
--- Tests mergeGroupPrefixSuffix: absorb preceding elements into GROUP min
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_plan
 WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
              PATTERN (A B (A B)+) DEFINE A AS val <= 50, B AS val > 50);
 
 -- SUFFIX only merge: (A B)+ A B -> (a b){2,}
--- Tests mergeGroupPrefixSuffix: absorb following elements into GROUP min
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_plan
 WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
              PATTERN ((A B)+ A B) DEFINE A AS val <= 50, B AS val > 50);
 
 -- Multiple SUFFIX absorption with skipUntil: (A B)+ A B A B C
--- Tests mergeGroupPrefixSuffix: skip absorbed suffix elements
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_plan
 WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
              PATTERN ((A B)+ A B A B C)
              DEFINE A AS val <= 50, B AS val > 50 AND val <= 75, C AS val > 75);
 
--- PREFIX merge with remaining prefix: A B C D (C D)+
--- Tests mergeGroupPrefixSuffix: trimmed list reconstruction
+-- PREFIX merge with remaining prefix: A B C D (C D)+  -> A B (C D) {2,}
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_plan
 WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
@@ -2783,8 +2773,16 @@ WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
              DEFINE A AS val <= 25, B AS val > 25 AND val <= 50,
                     C AS val > 50 AND val <= 75, D AS val > 75);
 
+-- cannot merge, prefix is different
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) OVER w
+FROM rpr_plan
+WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+PATTERN (A B C D C (C D) +)
+DEFINE A AS val <= 25, B AS val > 25,
+       C AS val > 50, D AS val > 75);
+
 -- PREFIX merge with quantifiers: A B* (A B*)+ -> (a b*){2,}
--- Tests mergeGroupPrefixSuffix: quantifier comparison in rprPatternEqual
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_plan
 WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
@@ -2792,7 +2790,6 @@ WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
              DEFINE A AS val <= 50, B AS val > 50);
 
 -- PREFIX merge with multiple quantifiers: A+ B* C? (A+ B* C?)+ -> (a+ b* c?){2,}
--- Tests mergeGroupPrefixSuffix: complex quantifier patterns
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_plan
 WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
@@ -2800,7 +2797,6 @@ WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
              DEFINE A AS val <= 30, B AS val > 30 AND val <= 60, C AS val > 60);
 
 -- SUFFIX merge with quantifiers: (A B*)+ A B* -> (a b*){2,}
--- Tests mergeGroupPrefixSuffix: suffix with quantifiers
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_plan
 WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
@@ -2808,7 +2804,6 @@ WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
              DEFINE A AS val <= 50, B AS val > 50);
 
 -- Unwrap GROUP{1,1}: ((A | B | C)) -> (a | b | c)
--- Tests tryUnwrapGroup removing redundant outer GROUP
 EXPLAIN (COSTS OFF)
 SELECT COUNT(*) OVER w FROM rpr_plan
 WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
@@ -2909,6 +2904,13 @@ WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
              PATTERN (A B A B (A B)+ A B A B)
              DEFINE A AS val <= 50, B AS val > 50);
 
+-- PREFIX+SUFFIX merge (5-way): B A B (A B)+ A B A B -> b (a b){4,}
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) OVER w FROM rpr_plan
+WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+             PATTERN (B A B (A B)+ A B A B)
+             DEFINE A AS val <= 50, B AS val > 50);
+
 -- Unwrap single-item ALT after dedup: (A | A)+ -> a+
 -- ALT dedup reduces to single-item, then GROUP unwrap
 EXPLAIN (COSTS OFF)
@@ -4303,7 +4305,7 @@ WINDOW w AS (
 );
 
 -- Test: (A+ | (A | B)+)* - nested alternation inside quantified group
--- Previously caused infinite recursion in nfa_advance_alt when the inner
+-- Previously caused infinite recursion in alternation handling when the inner
 -- BEGIN(+)'s skip jump was followed as an ALT branch pointer.
 SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
 FROM (VALUES
diff --git a/src/test/regress/sql/rpr_explain.sql b/src/test/regress/sql/rpr_explain.sql
index aa78ffed260..297ca78d54b 100644
--- a/src/test/regress/sql/rpr_explain.sql
+++ b/src/test/regress/sql/rpr_explain.sql
@@ -365,7 +365,7 @@ WINDOW w AS (
 );');
 
 -- Consecutive ALT merge followed by different ALT
--- Tests mergeConsecutiveAlts flush on ALT change: (A|B){2} (C|D)
+-- ((A | B) (A | B) (C | D)) -> (A|B){2} (C|D)
 CREATE VIEW rpr_ev_state_alt_merge_alt AS
 SELECT count(*) OVER w
 FROM generate_series(1, 40) AS s(v)
@@ -388,7 +388,7 @@ WINDOW w AS (
 );');
 
 -- Consecutive ALT merge followed by non-ALT element
--- Tests mergeConsecutiveAlts flush on non-ALT: (A|B){2} c
+-- ((A | B) (A | B) C) -> (A|B){2} C
 CREATE VIEW rpr_ev_state_alt_merge_nonalt AS
 SELECT count(*) OVER w
 FROM generate_series(1, 40) AS s(v)
diff --git a/src/test/regress/sql/rpr_nfa.sql b/src/test/regress/sql/rpr_nfa.sql
index 8daa0a73725..c82b8340977 100644
--- a/src/test/regress/sql/rpr_nfa.sql
+++ b/src/test/regress/sql/rpr_nfa.sql
@@ -408,7 +408,6 @@ WINDOW w AS (
 );
 
 -- Consecutive vars merged to fixed-length: (A B B)+ -> (A B{2})+
--- mergeConsecutiveVars produces B{2}; now absorbable with fixed-length check
 WITH test_absorb_consecutive AS (
     SELECT * FROM (VALUES
         (1,  ARRAY['A']),
@@ -836,7 +835,7 @@ WINDOW w AS (
 );
 
 -- Optional reluctant group: (A B)?? C
--- nfa_advance_begin: reluctant tries skip first, but skip path needs C
+-- Reluctant group entry tries skip first, but the skip path needs C
 -- at row 1 which is A -> skip fails. Enter path succeeds: A(1) B(2) C(3).
 WITH test_optional_reluctant AS (
     SELECT * FROM (VALUES
@@ -863,8 +862,8 @@ WINDOW w AS (
 -- Non-leading reluctant optional VAR: (B A?? C)
 -- Reluctant A?? should prefer to skip, matching B(1) C(2) with A left
 -- unmatched (match_end 2).  The leading/group reluctant cases above go through
--- the begin path; this exercises the non-leading skip path in
--- nfa_route_to_elem, which must honor reluctant ordering too.
+-- the begin path; this exercises the non-leading skip path,
+-- which must honor reluctant ordering too.
 WITH test_nonleading_reluctant AS (
     SELECT * FROM (VALUES
         (1, ARRAY['B']),
@@ -888,8 +887,8 @@ WINDOW w AS (
 );
 
 -- Reluctant outer quantifier over a nullable reluctant body: SQL/RPR
--- semantics call for the shortest (empty) match.  The count<min branch of
--- nfa_advance_end must prefer the fast-forward (exit) path for reluctant
+-- semantics call for the shortest (empty) match.  In the count<min case
+-- the engine must prefer the fast-forward (exit) path for reluctant
 -- groups and suppress longer matches once exit reaches FIN, mirroring the
 -- sibling min<=count<max branch.  The 2-level greedy/reluctant matrix plus a
 -- min>=2 boundary and single-quantifier controls localize the behaviour: only
@@ -915,7 +914,7 @@ ORDER BY id;
 
 -- Non-leading reluctant optional GROUP with a follower: (B (A X)?? C)
 -- Like the VAR case above but a multi-element group; it goes through the
--- begin path (nfa_advance_begin), which already honors reluctant ordering.
+-- begin path, which already honors reluctant ordering.
 -- Reluctant (A X)?? should skip, matching B(1) C(2), with the group skipped
 -- to the following C (not to FIN).
 WITH test_nonleading_reluctant_group AS (
@@ -968,7 +967,7 @@ WINDOW w AS (
 
 -- Reluctant optional group skip-to-FIN
 -- When a reluctant optional group's skip path reaches FIN, the group
--- entry path is abandoned (execRPR.c nfa_advance_begin).
+-- entry path is abandoned.
 -- Pattern: C (A B)?? -- after C matches, the reluctant group (A B)??
 -- prefers to skip.  Skip goes to FIN (group is last element), so
 -- the match completes with just C.
@@ -1667,11 +1666,11 @@ WINDOW w AS (
         B AS 'B' = ANY(flags)
 );
 
--- A{3,5}? B (reluctant bounded mid-band): the VAR-level count in
--- nfa_advance_var cycles through 3, 4, 5 within a single match
--- attempt.  Exercises the count > 2 && reluctant && !isAbsorbable
--- branch (absorbability analysis excludes reluctant quantifiers, so
--- isAbsorbable stays false for A).
+-- A{3,5}? B (reluctant bounded mid-band): the VAR-level count
+-- cycles through 3, 4, 5 within a single match attempt.  Exercises
+-- a reluctant bounded quantifier that absorbability analysis excludes
+-- (reluctant quantifiers are never absorbable, so A stays
+-- non-absorbable).
 WITH test_reluctant_mid_band AS (
     SELECT * FROM (VALUES
         (1, ARRAY['A']),
@@ -2406,7 +2405,7 @@ WINDOW w AS (
 -- Nested END->END fast-forward
 -- When an inner group has a nullable body and count < min, the
 -- fast-forward path exits through the outer END, incrementing
--- the outer group's count (execRPR.c nfa_advance_end).
+-- the outer group's count.
 -- Pattern: ((A?){2,3}){2,3} -- nested groups, neither collapses
 -- because the optimizer cannot safely multiply non-exact quantifiers.
 -- Data has no A rows, forcing all-empty iterations via fast-forward.
@@ -2954,7 +2953,7 @@ WINDOW w AS (
 
 -- Non-absorbable context during absorption
 -- Pattern (A B)+ C: A,B in absorbable group, C is not.
--- When END exits to C via nfa_state_clone, isAbsorbable becomes false.
+-- When END exits to C, the cloned context becomes non-absorbable.
 WITH test_non_absorbable AS (
     SELECT * FROM (VALUES
         (1, ARRAY['A']),
@@ -2980,9 +2979,9 @@ WINDOW w AS (
         C AS 'C' = ANY(flags)
 );
 
--- Absorption flags early return (!hasAbsorbableState)
+-- Absorption skipped when no absorbable state remains
 -- Pattern (A B)+ C D with SKIP PAST LAST ROW
--- After reaching C (non-absorbable), hasAbsorbableState becomes false.
+-- After reaching C (non-absorbable), no absorbable state remains.
 -- On next row (D), the early return fires.
 WITH test_absorption_early_return AS (
     SELECT * FROM (VALUES
@@ -3065,11 +3064,11 @@ WINDOW w AS (
         B AS 'B' = ANY(flags)
 );
 
--- Absorb skips non-absorbable context (!hasAbsorbableState)
+-- Absorb skips a context with no absorbable state
 -- Pattern A+ | B C with SKIP PAST LAST ROW (only A+ branch absorbable).
 -- Row 1: B only -> Ctx1 takes B branch (non-absorbable), advances to C.
--- Row 2: C,A -> Ctx1 C matches (hasAbsorbableState=false). Ctx2 takes A (absorbable).
--- Absorption: Ctx1 !hasAbsorbableState -> skip.
+-- Row 2: C,A -> Ctx1 C matches (no absorbable state). Ctx2 takes A (absorbable).
+-- Absorption: Ctx1 has no absorbable state -> skip.
 WITH test_older_non_absorbable AS (
     SELECT * FROM (VALUES
         (1, ARRAY['B', '_']),
-- 
2.50.1 (Apple Git-155)