nocfbot-0051-Rework-row-pattern-EXPLAIN-deparser-to-fix-groupe.txt
text/plain
Filename: nocfbot-0051-Rework-row-pattern-EXPLAIN-deparser-to-fix-groupe.txt
Type: text/plain
Part: 49
Message:
Re: Row pattern recognition
From 65ef6502a267b310ca8a452fdecc39c6528d94d6 Mon Sep 17 00:00:00 2001
From: Henson Choi <assam258@gmail.com>
Date: Fri, 5 Jun 2026 16:37:34 +0900
Subject: [PATCH 51/68] Rework row pattern EXPLAIN deparser to fix grouped
alternation branches
The EXPLAIN "Pattern:" line reconstructs a pattern string from the
compiled RPRPattern bytecode. When an alternation branch was a
quantified group, the previous deparser dropped the branch separator
or the enclosing parenthesis, so the displayed pattern no longer
matched the one that was compiled. For example PATTERN (C | (A B)+ | D)
was shown as (c | (a b)+ d), collapsing a three-way alternation into a
different two-way pattern, and a leading group branch lost the opening
parenthesis of the alternation.
The cause was that branch separators and scope ends were rebuilt from
the jump field, which the compiler overloads as a group's skip target,
and from absolute next values, which it rewrites for branch tails and
nested alternations.
Rewrite the deparser as a recursive descent in which each construct is
deparsed within an inherited [start, limit) window. Scope ends come
from depth, branch boundaries from a branch-start jump confirmed by the
relative test elem[j-1].next != j, and parentheses from structure plus
a one-step lookahead for a group that wraps a lone alternation. Each
construct's extent is found by a short forward scan, so the walk is not
a single linear pass; as this runs only while EXPLAIN formats its
output and patterns are small, it favors clarity over a maximally
efficient deparse.
This is display-only; query execution and pg_get_viewdef were already
correct and are unchanged. Add regression coverage in rpr_explain for
grouped alternation branches and for deeply nested alternations, which
previously had none.
---
src/backend/commands/explain.c | 343 ++++----
src/test/regress/expected/rpr_explain.out | 950 ++++++++++++++++++++++
src/test/regress/sql/rpr_explain.sql | 523 ++++++++++++
3 files changed, 1623 insertions(+), 193 deletions(-)
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 1a754bcdac5..7ba0b6df849 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -122,18 +122,13 @@ static void show_window_keys(StringInfo buf, PlanState *planstate,
List *ancestors, ExplainState *es);
static void append_rpr_quantifier(StringInfo buf, RPRPatternElement *elem);
static char *deparse_rpr_pattern(RPRPattern *pattern);
-static void deparse_rpr_elements(RPRPattern *pattern, int *idx,
- StringInfoData *buf, RPRDepth groupDepth,
- RPRDepth *prevDepth, bool *needSpace);
-static void deparse_rpr_group(RPRPattern *pattern, int *idx,
- StringInfoData *buf, RPRDepth *prevDepth,
- bool *needSpace);
-static void deparse_rpr_alt(RPRPattern *pattern, int *idx,
- StringInfoData *buf, RPRDepth *prevDepth,
- bool *needSpace, List **altSeps);
-static void deparse_rpr_var(RPRPattern *pattern, int *idx,
- StringInfoData *buf, RPRDepth *prevDepth,
- bool *needSpace, List **altSeps);
+static int deparse_rpr_seq(RPRPattern *pattern, int start, int limit,
+ StringInfo buf);
+static int deparse_rpr_node(RPRPattern *pattern, int i, int limit,
+ StringInfo buf);
+static int rpr_match_end(RPRPattern *pattern, int beginIdx);
+static int rpr_alt_scope_end(RPRPattern *pattern, int i);
+static int rpr_next_branch(RPRPattern *pattern, int b, int altEnd);
static void show_storage_info(char *maxStorageType, int64 maxSpaceUsed,
ExplainState *es);
static void show_tablesample(TableSampleClause *tsc, PlanState *planstate,
@@ -2952,244 +2947,206 @@ append_rpr_quantifier(StringInfo buf, RPRPatternElement *elem)
}
/*
- * Deparse a compiled RPRPattern (bytecode) back to pattern string.
+ * Deparse a compiled RPRPattern (bytecode) back to a pattern string.
*
- * Walks the flat bytecode array using mutual recursion: deparse_rpr_elements
- * processes sequential elements, and deparse_rpr_group handles BEGIN...END
- * groups by recursing back into deparse_rpr_elements for the group content.
+ * The flat RPRPatternElement[] array is walked by recursive descent. Each
+ * construct is deparsed within an inherited [start, limit) window: the parent
+ * passes the boundary down, so each construct's extent is fixed by its caller.
+ * Three signals drive the walk:
+ *
+ * - scope ends (where an ALT or GROUP body finishes) come from depth, via
+ * rpr_alt_scope_end() and rpr_match_end().
+ * - branch boundaries (where a "|" goes) come from a branch-start jump,
+ * confirmed by the relative test elem[j-1].next != j, via
+ * rpr_next_branch().
+ * - parentheses come from structure (a BEGIN group, an ALT) plus a one-step
+ * lookahead for a group that wraps a lone ALT.
+ *
+ * depth and the relative next test are stable across the next/jump values the
+ * compiler assigns to branch tails and nested alternations, which is what makes
+ * them suitable to anchor scope and branch boundaries.
+ *
+ * EXPLAIN parenthesizes every ALT on its own, so a top-level "A | B" deparses
+ * as "(a | b)". This self-consistent EXPLAIN form is the correctness oracle
+ * here; pg_get_viewdef differs, as its parens come only from an enclosing
+ * GROUP. Absorption markers (' ") are orthogonal and handled by
+ * append_rpr_quantifier().
+ *
+ * Two compiler invariants hold throughout: {1,1} groups are unwrapped before
+ * bytecode generation (so every BEGIN/END group carries a non-trivial
+ * quantifier, and a lone ALT inside a group always spans to the group's END),
+ * and a group's quantifier is read from its END element (the BEGIN copy is
+ * ignored).
*/
static char *
deparse_rpr_pattern(RPRPattern *pattern)
{
StringInfoData buf;
- int idx = 0;
- RPRDepth prevDepth = 0;
- bool needSpace = false;
Assert(pattern != NULL && pattern->numElements >= 2);
initStringInfo(&buf);
-
- deparse_rpr_elements(pattern, &idx, &buf, RPR_DEPTH_NONE,
- &prevDepth, &needSpace);
-
- /* Close remaining open parens */
- while (prevDepth > 0)
- {
- appendStringInfoChar(&buf, ')');
- prevDepth--;
- }
-
+ deparse_rpr_seq(pattern, 0, pattern->numElements, &buf);
return buf.data;
}
/*
- * Process pattern elements sequentially until FIN or END at groupDepth.
+ * Deparse a run of sibling elements in [start, limit), separated by spaces.
*
- * When groupDepth >= 0, stops at the matching END element (leaving idx
- * pointing to it) so the caller (deparse_rpr_group) can consume it.
- * When groupDepth < 0, processes until FIN (top-level call).
+ * Stops at limit or at the FIN terminator (top-level call passes limit =
+ * numElements, where the last element is FIN). Returns the index reached.
*/
-static void
-deparse_rpr_elements(RPRPattern *pattern, int *idx, StringInfoData *buf,
- RPRDepth groupDepth, RPRDepth *prevDepth,
- bool *needSpace)
+static int
+deparse_rpr_seq(RPRPattern *pattern, int start, int limit, StringInfo buf)
{
- List *altSeps = NIL; /* pending alternation separator indices */
+ int i = start;
+ bool first = true;
- while (*idx < pattern->numElements)
+ while (i < limit && !RPRElemIsFin(&pattern->elements[i]))
{
- RPRPatternElement *elem = &pattern->elements[*idx];
-
- if (RPRElemIsFin(elem))
- break;
-
- /* Stop at END matching our group depth; caller handles it */
- if (RPRElemIsEnd(elem) && elem->depth == groupDepth)
- break;
-
- /* Alternation separator */
- if (list_member_int(altSeps, *idx))
- {
- /* Close parens to match separator depth first */
- while (*prevDepth > elem->depth)
- {
- appendStringInfoChar(buf, ')');
- (*prevDepth)--;
- }
- appendStringInfoString(buf, " | ");
- *needSpace = false;
- altSeps = list_delete_int(altSeps, *idx);
- }
-
- /* Dispatch to element-type handlers */
- if (RPRElemIsAlt(elem))
- deparse_rpr_alt(pattern, idx, buf, prevDepth,
- needSpace, &altSeps);
- else if (RPRElemIsBegin(elem))
- deparse_rpr_group(pattern, idx, buf, prevDepth,
- needSpace);
- else if (RPRElemIsVar(elem))
- deparse_rpr_var(pattern, idx, buf, prevDepth,
- needSpace, &altSeps);
- }
- list_free(altSeps);
+ if (!first)
+ appendStringInfoChar(buf, ' ');
+ first = false;
+ i = deparse_rpr_node(pattern, i, limit, buf);
+ }
+ return i;
}
/*
- * Process a BEGIN...END group.
+ * Deparse the single construct starting at index i, bounded by the inherited
+ * limit. Returns the index just past the construct.
*
- * Consumes BEGIN, recurses into deparse_rpr_elements for group content,
- * then consumes END and outputs the group quantifier.
- *
- * When the group wraps a single ALT with no siblings, the group-level
- * parenthesis is suppressed since the ALT-to-children depth transition
- * already provides it (avoids double parens like "((a | b))+").
+ * A VAR is its name plus quantifier. A BEGIN opens a group spanning to its
+ * matching END (rpr_match_end); when the group's sole child is an ALT that
+ * runs to the END, the ALT supplies the parentheses and the group only adds
+ * the quantifier, otherwise the group body is wrapped in its own "( )". An
+ * ALT runs to its depth-determined scope end (capped by the inherited limit)
+ * and emits "( b1 | b2 | ... )", each branch deparsed within the boundary
+ * handed down by rpr_next_branch.
*/
-static void
-deparse_rpr_group(RPRPattern *pattern, int *idx, StringInfoData *buf,
- RPRDepth *prevDepth, bool *needSpace)
+static int
+deparse_rpr_node(RPRPattern *pattern, int i, int limit, StringInfo buf)
{
- RPRPatternElement *begin = &pattern->elements[*idx];
- RPRDepth childDepth = begin->depth + 1;
- bool singleAlt = false;
- RPRPatternElement *end;
+ RPRPatternElement *elem = &pattern->elements[i];
- /*
- * Check if this group wraps a single ALT with no siblings. Scan from
- * after ALT to END: if no element at childDepth exists, the ALT is the
- * sole child.
- */
- if (*idx + 1 < pattern->numElements &&
- RPRElemIsAlt(&pattern->elements[*idx + 1]))
+ if (RPRElemIsVar(elem))
{
- int j;
+ Assert(elem->varId < pattern->numVars);
+ appendStringInfoString(buf,
+ quote_identifier(pattern->varNames[elem->varId]));
+ append_rpr_quantifier(buf, elem);
+ return i + 1;
+ }
- singleAlt = true;
- for (j = *idx + 2; j < pattern->numElements; j++)
- {
- RPRPatternElement *e = &pattern->elements[j];
+ if (RPRElemIsBegin(elem))
+ {
+ int end = rpr_match_end(pattern, i);
+ bool loneAlt;
- if (RPRElemIsEnd(e) && e->depth == begin->depth)
- break;
- if (e->depth <= childDepth)
- {
- singleAlt = false;
- break;
- }
+ loneAlt = (i + 1 < end &&
+ RPRElemIsAlt(&pattern->elements[i + 1]) &&
+ rpr_alt_scope_end(pattern, i + 1) == end);
+
+ if (loneAlt)
+ {
+ /* The ALT child already parenthesizes the whole group body. */
+ (void) deparse_rpr_node(pattern, i + 1, end, buf);
}
+ else
+ {
+ appendStringInfoChar(buf, '(');
+ (void) deparse_rpr_seq(pattern, i + 1, end, buf);
+ appendStringInfoChar(buf, ')');
+ }
+ append_rpr_quantifier(buf, &pattern->elements[end]);
+ return end + 1;
}
- /* Open group paren (unless single ALT provides it) */
- if (!singleAlt)
+ Assert(RPRElemIsAlt(elem));
{
- if (*needSpace)
- appendStringInfoChar(buf, ' ');
- appendStringInfoChar(buf, '(');
- *needSpace = false;
- }
- *prevDepth = childDepth;
- (*idx)++; /* consume BEGIN */
+ int altEnd = rpr_alt_scope_end(pattern, i);
+ int b;
+ bool first = true;
- /* Process group children; stops at matching END */
- deparse_rpr_elements(pattern, idx, buf, begin->depth,
- prevDepth, needSpace);
+ if (altEnd > limit)
+ altEnd = limit;
- /* Consume END and output quantifier */
- Assert(*idx < pattern->numElements);
- end = &pattern->elements[*idx];
- Assert(RPRElemIsEnd(end) && end->depth == begin->depth);
+ appendStringInfoChar(buf, '(');
+ b = i + 1;
+ while (b < altEnd)
+ {
+ int nb = rpr_next_branch(pattern, b, altEnd);
- while (*prevDepth > end->depth + 1)
- {
+ if (!first)
+ appendStringInfoString(buf, " | ");
+ first = false;
+ (void) deparse_rpr_seq(pattern, b, nb, buf);
+ b = nb;
+ }
appendStringInfoChar(buf, ')');
- (*prevDepth)--;
+ return altEnd;
}
- if (!singleAlt)
- appendStringInfoChar(buf, ')');
- append_rpr_quantifier(buf, end);
- *prevDepth = end->depth;
- *needSpace = true;
- (*idx)++; /* consume END */
}
/*
- * Process an ALT element: adjust depth parens and register separator positions.
+ * Find the END that closes the group opened by the BEGIN at beginIdx: the
+ * first END at the same depth scanning forward.
*/
-static void
-deparse_rpr_alt(RPRPattern *pattern, int *idx, StringInfoData *buf,
- RPRDepth *prevDepth, bool *needSpace, List **altSeps)
+static int
+rpr_match_end(RPRPattern *pattern, int beginIdx)
{
- RPRPatternElement *elem = &pattern->elements[*idx];
-
- /* Close parens for depth decrease */
- while (*prevDepth > elem->depth)
- {
- appendStringInfoChar(buf, ')');
- (*prevDepth)--;
- *needSpace = true;
- }
-
- /* Open parens up to ALT's depth */
- while (*prevDepth < elem->depth)
- {
- if (*needSpace)
- appendStringInfoChar(buf, ' ');
- appendStringInfoChar(buf, '(');
- (*prevDepth)++;
- *needSpace = false;
- }
+ RPRDepth d = pattern->elements[beginIdx].depth;
+ int j;
- /* Register next alternation separator position */
- if (elem->next != RPR_ELEMIDX_INVALID)
+ for (j = beginIdx + 1; j < pattern->numElements; j++)
{
- RPRPatternElement *firstElem = &pattern->elements[elem->next];
+ RPRPatternElement *e = &pattern->elements[j];
- if (firstElem->jump != RPR_ELEMIDX_INVALID)
- *altSeps = lappend_int(*altSeps, firstElem->jump);
+ if (RPRElemIsEnd(e) && e->depth == d)
+ return j;
}
- if (elem->jump != RPR_ELEMIDX_INVALID)
- *altSeps = lappend_int(*altSeps, elem->jump);
- (*idx)++;
+ pg_unreachable(); /* a BEGIN always has a matching END */
}
/*
- * Process a VAR element: adjust depth parens and output variable name.
+ * Scope end of the construct at index i: the first following element whose
+ * depth is no greater than i's own. For an ALT marker this is the index just
+ * past its last branch, since depth stays constant across branch boundaries.
+ * FIN sits at depth 0, so a top-level ALT stops there.
*/
-static void
-deparse_rpr_var(RPRPattern *pattern, int *idx, StringInfoData *buf,
- RPRDepth *prevDepth, bool *needSpace, List **altSeps)
+static int
+rpr_alt_scope_end(RPRPattern *pattern, int i)
{
- RPRPatternElement *elem = &pattern->elements[*idx];
-
- /* Open parens for depth increase */
- while (*prevDepth < elem->depth)
- {
- if (*needSpace)
- appendStringInfoChar(buf, ' ');
- appendStringInfoChar(buf, '(');
- (*prevDepth)++;
- *needSpace = false;
- }
+ RPRDepth d = pattern->elements[i].depth;
+ int k;
- /* Close parens for depth decrease */
- while (*prevDepth > elem->depth)
+ for (k = i + 1; k < pattern->numElements; k++)
{
- appendStringInfoChar(buf, ')');
- (*prevDepth)--;
+ if (pattern->elements[k].depth <= d)
+ return k;
}
+ return pattern->numElements;
+}
- if (*needSpace)
- appendStringInfoChar(buf, ' ');
-
- Assert(elem->varId < pattern->numVars);
- appendStringInfoString(buf, quote_identifier(pattern->varNames[elem->varId]));
- append_rpr_quantifier(buf, elem);
- *needSpace = true;
+/*
+ * Boundary of the alternation branch starting at b (i.e. the start of the next
+ * branch, or altEnd if b is the last branch).
+ *
+ * The branch-start element's jump points at the next branch when this is not
+ * the last branch. jump is overloaded (a group BEGIN also uses it for its
+ * skip path), so confirm a real branch boundary with the relative test
+ * elem[j-1].next != j: at a true boundary the preceding branch's tail has its
+ * next redirected past the alternation, so it does not point at j.
+ */
+static int
+rpr_next_branch(RPRPattern *pattern, int b, int altEnd)
+{
+ int j = pattern->elements[b].jump;
- if (elem->jump != RPR_ELEMIDX_INVALID)
- *altSeps = lappend_int(*altSeps, elem->jump);
- (*idx)++;
+ if (j != RPR_ELEMIDX_INVALID && j < altEnd &&
+ pattern->elements[j - 1].next != j)
+ return j;
+ return altEnd;
}
/*
diff --git a/src/test/regress/expected/rpr_explain.out b/src/test/regress/expected/rpr_explain.out
index 5cddd1a56df..bcbf4f941ba 100644
--- a/src/test/regress/expected/rpr_explain.out
+++ b/src/test/regress/expected/rpr_explain.out
@@ -3761,6 +3761,956 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=20.00 loops=1)
(9 rows)
+-- Quantified group as the first alternation branch
+-- Pattern: ((A B)+ | C) - leading group branch must open the enclosing paren
+CREATE VIEW rpr_ev_alt_grp_first AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((A B)+ | C)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_alt_grp_first'), E'\n')) AS line WHERE line ~ 'PATTERN';
+ line
+-------------------------
+ PATTERN ((a b)+ | c)
+(1 row)
+
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((A B)+ | C)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2
+);');
+ rpr_explain_filter
+----------------------------------------------------------------------
+ WindowAgg (actual rows=20.00 loops=1)
+ Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+ Pattern: ((a' b')+" | c)
+ Nav Mark Lookback: 0
+ Storage: Memory Maximum Storage: NkB
+ NFA States: 5 peak, 67 total, 0 merged
+ NFA Contexts: 3 peak, 21 total, 7 pruned
+ NFA: 9 matched (len 1/2/1.4), 0 mismatched
+ NFA: 0 absorbed, 4 skipped (len 1/1/1.0)
+ -> Function Scan on generate_series s (actual rows=20.00 loops=1)
+(10 rows)
+
+-- Quantified group as the last alternation branch
+-- Pattern: (C | (A B)+) - trailing group branch, no separator follows
+CREATE VIEW rpr_ev_alt_grp_last AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (C | (A B)+)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_alt_grp_last'), E'\n')) AS line WHERE line ~ 'PATTERN';
+ line
+-------------------------
+ PATTERN (c | (a b)+)
+(1 row)
+
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (C | (A B)+)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2
+);');
+ rpr_explain_filter
+----------------------------------------------------------------------
+ WindowAgg (actual rows=20.00 loops=1)
+ Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+ Pattern: (c | (a' b')+")
+ Nav Mark Lookback: 0
+ Storage: Memory Maximum Storage: NkB
+ NFA States: 5 peak, 67 total, 0 merged
+ NFA Contexts: 3 peak, 21 total, 7 pruned
+ NFA: 9 matched (len 1/2/1.4), 0 mismatched
+ NFA: 0 absorbed, 4 skipped (len 1/1/1.0)
+ -> Function Scan on generate_series s (actual rows=20.00 loops=1)
+(10 rows)
+
+-- Quantified group as the middle branch of a three-way alternation
+-- Pattern: (C | (A B)+ | D) - separator before D must survive the group branch
+CREATE VIEW rpr_ev_alt_grp_mid AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (C | (A B)+ | D)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2, D AS v % 4 = 3
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_alt_grp_mid'), E'\n')) AS line WHERE line ~ 'PATTERN';
+ line
+-----------------------------
+ PATTERN (c | (a b)+ | d)
+(1 row)
+
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (C | (A B)+ | D)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2, D AS v % 4 = 3
+);');
+ rpr_explain_filter
+----------------------------------------------------------------------
+ WindowAgg (actual rows=20.00 loops=1)
+ Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+ Pattern: (c | (a' b')+" | d)
+ Nav Mark Lookback: 0
+ Storage: Memory Maximum Storage: NkB
+ NFA States: 6 peak, 88 total, 0 merged
+ NFA Contexts: 3 peak, 21 total, 2 pruned
+ NFA: 14 matched (len 1/2/1.3), 0 mismatched
+ NFA: 0 absorbed, 4 skipped (len 1/1/1.0)
+ -> Function Scan on generate_series s (actual rows=20.00 loops=1)
+(10 rows)
+
+-- Quantified group as the first branch of a three-way alternation
+-- Pattern: ((A B)+ | C | D) - leading group branch with two following branches
+CREATE VIEW rpr_ev_alt_grp_first3 AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((A B)+ | C | D)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2, D AS v % 4 = 3
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_alt_grp_first3'), E'\n')) AS line WHERE line ~ 'PATTERN';
+ line
+-----------------------------
+ PATTERN ((a b)+ | c | d)
+(1 row)
+
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((A B)+ | C | D)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2, D AS v % 4 = 3
+);');
+ rpr_explain_filter
+----------------------------------------------------------------------
+ WindowAgg (actual rows=20.00 loops=1)
+ Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+ Pattern: ((a' b')+" | c | d)
+ Nav Mark Lookback: 0
+ Storage: Memory Maximum Storage: NkB
+ NFA States: 6 peak, 88 total, 0 merged
+ NFA Contexts: 3 peak, 21 total, 2 pruned
+ NFA: 14 matched (len 1/2/1.3), 0 mismatched
+ NFA: 0 absorbed, 4 skipped (len 1/1/1.0)
+ -> Function Scan on generate_series s (actual rows=20.00 loops=1)
+(10 rows)
+
+-- Bounded-quantifier group as the first alternation branch
+-- Pattern: ((A B){2} | C) - leading group branch with a range quantifier
+CREATE VIEW rpr_ev_alt_grp_bounded AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((A B){2} | C)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_alt_grp_bounded'), E'\n')) AS line WHERE line ~ 'PATTERN';
+ line
+---------------------------
+ PATTERN ((a b){2} | c)
+(1 row)
+
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((A B){2} | C)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2
+);');
+ rpr_explain_filter
+----------------------------------------------------------------------
+ WindowAgg (actual rows=20.00 loops=1)
+ Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+ Pattern: ((a b){2} | c)
+ Nav Mark Lookback: 0
+ Storage: Memory Maximum Storage: NkB
+ NFA States: 4 peak, 63 total, 0 merged
+ NFA Contexts: 3 peak, 21 total, 11 pruned
+ NFA: 5 matched (len 1/1/1.0), 4 mismatched (len 3/3/3.0)
+ -> Function Scan on generate_series s (actual rows=20.00 loops=1)
+(9 rows)
+
+-- Two quantified groups in one alternation
+-- Pattern: ((A B)+ | (C D)+) - both branches are groups
+CREATE VIEW rpr_ev_alt_grp_both AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((A B)+ | (C D)+)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2, D AS v % 4 = 3
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_alt_grp_both'), E'\n')) AS line WHERE line ~ 'PATTERN';
+ line
+------------------------------
+ PATTERN ((a b)+ | (c d)+)
+(1 row)
+
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((A B)+ | (C D)+)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2, D AS v % 4 = 3
+);');
+ rpr_explain_filter
+----------------------------------------------------------------------
+ WindowAgg (actual rows=20.00 loops=1)
+ Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+ Pattern: ((a' b')+" | (c' d')+")
+ Nav Mark Lookback: 0
+ Storage: Memory Maximum Storage: NkB
+ NFA States: 5 peak, 72 total, 0 merged
+ NFA Contexts: 3 peak, 21 total, 2 pruned
+ NFA: 9 matched (len 2/2/2.0), 0 mismatched
+ NFA: 0 absorbed, 9 skipped (len 1/1/1.0)
+ -> Function Scan on generate_series s (actual rows=20.00 loops=1)
+(10 rows)
+
+-- Leading group branch in an alternation nested in a sequence
+-- Pattern: (((A B)+ | C) D) - inner alternation opens with a group branch
+CREATE VIEW rpr_ev_alt_grp_seq_head AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (((A B)+ | C) D)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2, D AS v % 4 = 3
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_alt_grp_seq_head'), E'\n')) AS line WHERE line ~ 'PATTERN';
+ line
+-----------------------------
+ PATTERN (((a b)+ | c) d)
+(1 row)
+
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (((A B)+ | C) D)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2, D AS v % 4 = 3
+);');
+ rpr_explain_filter
+----------------------------------------------------------------------
+ WindowAgg (actual rows=20.00 loops=1)
+ Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+ Pattern: ((a' b')+" | c) d
+ Nav Mark Lookback: 0
+ Storage: Memory Maximum Storage: NkB
+ NFA States: 5 peak, 67 total, 0 merged
+ NFA Contexts: 3 peak, 21 total, 6 pruned
+ NFA: 5 matched (len 2/2/2.0), 4 mismatched (len 3/3/3.0)
+ NFA: 0 absorbed, 5 skipped (len 1/1/1.0)
+ -> Function Scan on generate_series s (actual rows=20.00 loops=1)
+(10 rows)
+
+-- Trailing group branch in an alternation nested in a sequence
+-- Pattern: ((C | (A B)+) D) - group as last branch, then a sequence element
+CREATE VIEW rpr_ev_alt_grp_seq_tail AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((C | (A B)+) D)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2, D AS v % 4 = 3
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_alt_grp_seq_tail'), E'\n')) AS line WHERE line ~ 'PATTERN';
+ line
+-----------------------------
+ PATTERN ((c | (a b)+) d)
+(1 row)
+
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((C | (A B)+) D)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2, D AS v % 4 = 3
+);');
+ rpr_explain_filter
+----------------------------------------------------------------------
+ WindowAgg (actual rows=20.00 loops=1)
+ Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+ Pattern: (c | (a' b')+") d
+ Nav Mark Lookback: 0
+ Storage: Memory Maximum Storage: NkB
+ NFA States: 5 peak, 67 total, 0 merged
+ NFA Contexts: 3 peak, 21 total, 6 pruned
+ NFA: 5 matched (len 2/2/2.0), 4 mismatched (len 3/3/3.0)
+ NFA: 0 absorbed, 5 skipped (len 1/1/1.0)
+ -> Function Scan on generate_series s (actual rows=20.00 loops=1)
+(10 rows)
+
+-- Quantified alternation whose first branch is a quantified group
+-- Pattern: (((A B){2} | C)+) - single-ALT group wraps a leading group branch
+CREATE VIEW rpr_ev_alt_grp_quant AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (((A B){2} | C)+)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_alt_grp_quant'), E'\n')) AS line WHERE line ~ 'PATTERN';
+ line
+------------------------------
+ PATTERN (((a b){2} | c)+)
+(1 row)
+
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (((A B){2} | C)+)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2
+);');
+ rpr_explain_filter
+----------------------------------------------------------------------
+ WindowAgg (actual rows=20.00 loops=1)
+ Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+ Pattern: ((a b){2} | c)+
+ Nav Mark Lookback: 0
+ Storage: Memory Maximum Storage: NkB
+ NFA States: 6 peak, 78 total, 0 merged
+ NFA Contexts: 3 peak, 21 total, 11 pruned
+ NFA: 5 matched (len 1/1/1.0), 4 mismatched (len 3/3/3.0)
+ -> Function Scan on generate_series s (actual rows=20.00 loops=1)
+(9 rows)
+
+-- Unit (1,1) group as an alternation branch (emits no BEGIN/END)
+-- Pattern: ((A B) | C) - control: takes the variable path, not deparse_rpr_group
+CREATE VIEW rpr_ev_alt_grp_unit AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((A B) | C)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_alt_grp_unit'), E'\n')) AS line WHERE line ~ 'PATTERN';
+ line
+------------------------
+ PATTERN ((a b) | c)
+(1 row)
+
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((A B) | C)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2
+);');
+ rpr_explain_filter
+----------------------------------------------------------------------
+ WindowAgg (actual rows=20.00 loops=1)
+ Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+ Pattern: (a b | c)
+ Nav Mark Lookback: 0
+ Storage: Memory Maximum Storage: NkB
+ NFA States: 4 peak, 63 total, 0 merged
+ NFA Contexts: 2 peak, 21 total, 7 pruned
+ NFA: 9 matched (len 1/2/1.4), 0 mismatched
+ NFA: 0 absorbed, 4 skipped (len 1/1/1.0)
+ -> Function Scan on generate_series s (actual rows=20.00 loops=1)
+(10 rows)
+
+-- Quantified variable as the first alternation branch
+-- Pattern: (A+ | C) - control: deparse_rpr_var already opens the leading paren
+CREATE VIEW rpr_ev_alt_var_first AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A+ | C)
+ DEFINE A AS v % 4 = 0, C AS v % 4 = 2
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_alt_var_first'), E'\n')) AS line WHERE line ~ 'PATTERN';
+ line
+---------------------
+ PATTERN (a+ | c)
+(1 row)
+
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A+ | C)
+ DEFINE A AS v % 4 = 0, C AS v % 4 = 2
+);');
+ rpr_explain_filter
+----------------------------------------------------------------------
+ WindowAgg (actual rows=20.00 loops=1)
+ Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+ Pattern: (a+" | c)
+ Nav Mark Lookback: 0
+ Storage: Memory Maximum Storage: NkB
+ NFA States: 5 peak, 68 total, 0 merged
+ NFA Contexts: 3 peak, 21 total, 10 pruned
+ NFA: 10 matched (len 1/1/1.0), 0 mismatched
+ -> Function Scan on generate_series s (actual rows=20.00 loops=1)
+(9 rows)
+
+-- Quantified group as the last branch of a three-way alternation
+-- Pattern: (C | D | (A B)+) - control: trailing group needs no separator
+CREATE VIEW rpr_ev_alt_grp_last3 AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (C | D | (A B)+)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2, D AS v % 4 = 3
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_alt_grp_last3'), E'\n')) AS line WHERE line ~ 'PATTERN';
+ line
+-----------------------------
+ PATTERN (c | d | (a b)+)
+(1 row)
+
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (C | D | (A B)+)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2, D AS v % 4 = 3
+);');
+ rpr_explain_filter
+----------------------------------------------------------------------
+ WindowAgg (actual rows=20.00 loops=1)
+ Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+ Pattern: (c | d | (a' b')+")
+ Nav Mark Lookback: 0
+ Storage: Memory Maximum Storage: NkB
+ NFA States: 6 peak, 88 total, 0 merged
+ NFA Contexts: 3 peak, 21 total, 2 pruned
+ NFA: 14 matched (len 1/2/1.3), 0 mismatched
+ NFA: 0 absorbed, 4 skipped (len 1/1/1.0)
+ -> Function Scan on generate_series s (actual rows=20.00 loops=1)
+(10 rows)
+
+-- Alternation nested in a leading branch must not swallow the trailing branch
+-- Pattern: (D (A | B) | E) - inherited limit bounds the inner alternation
+CREATE VIEW rpr_ev_alt_inner_bounded AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (D (A | B) | E)
+ DEFINE A AS v % 5 = 0, B AS v % 5 = 1, D AS v % 5 = 2, E AS v % 5 = 3
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_alt_inner_bounded'), E'\n')) AS line WHERE line ~ 'PATTERN';
+ line
+----------------------------
+ PATTERN (d (a | b) | e)
+(1 row)
+
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (D (A | B) | E)
+ DEFINE A AS v % 5 = 0, B AS v % 5 = 1, D AS v % 5 = 2, E AS v % 5 = 3
+);');
+ rpr_explain_filter
+----------------------------------------------------------------------
+ WindowAgg (actual rows=20.00 loops=1)
+ Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+ Pattern: (d (a | b) | e)
+ Nav Mark Lookback: 0
+ Storage: Memory Maximum Storage: NkB
+ NFA States: 5 peak, 71 total, 0 merged
+ NFA Contexts: 3 peak, 21 total, 12 pruned
+ NFA: 4 matched (len 1/1/1.0), 4 mismatched (len 2/2/2.0)
+ -> Function Scan on generate_series s (actual rows=20.00 loops=1)
+(9 rows)
+
+-- Group mid-branch followed by a sequence element needs no separator before it
+-- Pattern: (C | (A B)+ D) - relative-next blocks a spurious separator at D
+CREATE VIEW rpr_ev_alt_grp_then_seq AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (C | (A B)+ D)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2, D AS v % 4 = 3
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_alt_grp_then_seq'), E'\n')) AS line WHERE line ~ 'PATTERN';
+ line
+---------------------------
+ PATTERN (c | (a b)+ d)
+(1 row)
+
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (C | (A B)+ D)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2, D AS v % 4 = 3
+);');
+ rpr_explain_filter
+----------------------------------------------------------------------
+ WindowAgg (actual rows=20.00 loops=1)
+ Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+ Pattern: (c | (a' b')+" d)
+ Nav Mark Lookback: 0
+ Storage: Memory Maximum Storage: NkB
+ NFA States: 6 peak, 88 total, 0 merged
+ NFA Contexts: 3 peak, 21 total, 6 pruned
+ NFA: 10 matched (len 1/1/1.0), 4 mismatched (len 3/3/3.0)
+ -> Function Scan on generate_series s (actual rows=20.00 loops=1)
+(9 rows)
+
+-- Quantified group wrapping a lone alternation: the ALT supplies the parens
+-- Pattern: ((A | B)+) - loneAlt path, single pair of parens
+CREATE VIEW rpr_ev_grp_lone_alt AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((A | B)+)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_grp_lone_alt'), E'\n')) AS line WHERE line ~ 'PATTERN';
+ line
+-----------------------
+ PATTERN ((a | b)+)
+(1 row)
+
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((A | B)+)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1
+);');
+ rpr_explain_filter
+----------------------------------------------------------------------
+ WindowAgg (actual rows=20.00 loops=1)
+ Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+ Pattern: (a | b)+
+ Nav Mark Lookback: 0
+ Storage: Memory Maximum Storage: NkB
+ NFA States: 6 peak, 93 total, 0 merged
+ NFA Contexts: 3 peak, 21 total, 10 pruned
+ NFA: 6 matched (len 1/2/1.7), 0 mismatched
+ NFA: 0 absorbed, 4 skipped (len 1/1/1.0)
+ -> Function Scan on generate_series s (actual rows=20.00 loops=1)
+(10 rows)
+
+-- Quantified group wrapping a sequence whose last element is an alternation
+-- Pattern: ((A (B | C))+) - group paren plus a nested alternation paren
+CREATE VIEW rpr_ev_grp_seq_alt AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((A (B | C))+)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_grp_seq_alt'), E'\n')) AS line WHERE line ~ 'PATTERN';
+ line
+---------------------------
+ PATTERN ((a (b | c))+)
+(1 row)
+
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((A (B | C))+)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2
+);');
+ rpr_explain_filter
+----------------------------------------------------------------------
+ WindowAgg (actual rows=20.00 loops=1)
+ Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+ Pattern: (a (b | c))+
+ Nav Mark Lookback: 0
+ Storage: Memory Maximum Storage: NkB
+ NFA States: 3 peak, 35 total, 0 merged
+ NFA Contexts: 3 peak, 21 total, 12 pruned
+ NFA: 4 matched (len 2/2/2.0), 0 mismatched
+ NFA: 0 absorbed, 4 skipped (len 1/1/1.0)
+ -> Function Scan on generate_series s (actual rows=20.00 loops=1)
+(10 rows)
+
+-- Quantified group wrapping a sequence whose first element is an alternation
+-- Pattern: (((A | B) C)+) - leading nested alternation inside a group sequence
+CREATE VIEW rpr_ev_grp_alt_seq AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (((A | B) C)+)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_grp_alt_seq'), E'\n')) AS line WHERE line ~ 'PATTERN';
+ line
+---------------------------
+ PATTERN (((a | b) c)+)
+(1 row)
+
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (((A | B) C)+)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2
+);');
+ rpr_explain_filter
+----------------------------------------------------------------------
+ WindowAgg (actual rows=20.00 loops=1)
+ Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+ Pattern: ((a | b) c)+
+ Nav Mark Lookback: 0
+ Storage: Memory Maximum Storage: NkB
+ NFA States: 6 peak, 78 total, 0 merged
+ NFA Contexts: 3 peak, 21 total, 6 pruned
+ NFA: 5 matched (len 2/2/2.0), 4 mismatched (len 2/2/2.0)
+ NFA: 0 absorbed, 5 skipped (len 1/1/1.0)
+ -> Function Scan on generate_series s (actual rows=20.00 loops=1)
+(10 rows)
+
+-- Alternation non-last in a non-last branch, stacked three deep: each level's
+-- inherited limit must bound the inner alternation against the next branch
+-- Pattern: (((A | B) C | D) E | F) - three nested inherited-limit boundaries
+CREATE VIEW rpr_ev_alt_stack3 AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (((A | B) C | D) E | F)
+ DEFINE A AS v % 6 = 0, B AS v % 6 = 1, C AS v % 6 = 2,
+ D AS v % 6 = 3, E AS v % 6 = 4, F AS v % 6 = 5
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_alt_stack3'), E'\n')) AS line WHERE line ~ 'PATTERN';
+ line
+------------------------------------
+ PATTERN (((a | b) c | d) e | f)
+(1 row)
+
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (((A | B) C | D) E | F)
+ DEFINE A AS v % 6 = 0, B AS v % 6 = 1, C AS v % 6 = 2,
+ D AS v % 6 = 3, E AS v % 6 = 4, F AS v % 6 = 5
+);');
+ rpr_explain_filter
+----------------------------------------------------------------------
+ WindowAgg (actual rows=20.00 loops=1)
+ Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+ Pattern: (((a | b) c | d) e | f)
+ Nav Mark Lookback: 0
+ Storage: Memory Maximum Storage: NkB
+ NFA States: 6 peak, 147 total, 0 merged
+ NFA Contexts: 3 peak, 21 total, 4 pruned
+ NFA: 6 matched (len 1/2/1.5), 7 mismatched (len 2/3/2.4)
+ NFA: 0 absorbed, 3 skipped (len 1/1/1.0)
+ -> Function Scan on generate_series s (actual rows=20.00 loops=1)
+(10 rows)
+
+-- Same interaction stacked four deep, to exercise the induction one step further
+-- Pattern: ((((A | B) C | D) E | F) G | H) - four nested inherited-limit boundaries
+CREATE VIEW rpr_ev_alt_stack4 AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((((A | B) C | D) E | F) G | H)
+ DEFINE A AS v % 8 = 0, B AS v % 8 = 1, C AS v % 8 = 2, D AS v % 8 = 3,
+ E AS v % 8 = 4, F AS v % 8 = 5, G AS v % 8 = 6, H AS v % 8 = 7
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_alt_stack4'), E'\n')) AS line WHERE line ~ 'PATTERN';
+ line
+--------------------------------------------
+ PATTERN ((((a | b) c | d) e | f) g | h)
+(1 row)
+
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((((A | B) C | D) E | F) G | H)
+ DEFINE A AS v % 8 = 0, B AS v % 8 = 1, C AS v % 8 = 2, D AS v % 8 = 3,
+ E AS v % 8 = 4, F AS v % 8 = 5, G AS v % 8 = 6, H AS v % 8 = 7
+);');
+ rpr_explain_filter
+----------------------------------------------------------------------
+ WindowAgg (actual rows=20.00 loops=1)
+ Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+ Pattern: ((((a | b) c | d) e | f) g | h)
+ Nav Mark Lookback: 0
+ Storage: Memory Maximum Storage: NkB
+ NFA States: 7 peak, 189 total, 0 merged
+ NFA Contexts: 3 peak, 21 total, 6 pruned
+ NFA: 4 matched (len 1/2/1.5), 8 mismatched (len 2/3/2.6)
+ NFA: 0 absorbed, 2 skipped (len 1/1/1.0)
+ -> Function Scan on generate_series s (actual rows=20.00 loops=1)
+(10 rows)
+
+-- Three-deep stack whose innermost branch is a quantified group: the group's
+-- skip-target jump must not be mistaken for a branch separator at any depth
+-- Pattern: (((A | B)+ C | D) E | F) - inherited limit plus loneAlt at the base
+CREATE VIEW rpr_ev_alt_stack3_grp AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (((A | B)+ C | D) E | F)
+ DEFINE A AS v % 6 = 0, B AS v % 6 = 1, C AS v % 6 = 2,
+ D AS v % 6 = 3, E AS v % 6 = 4, F AS v % 6 = 5
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_alt_stack3_grp'), E'\n')) AS line WHERE line ~ 'PATTERN';
+ line
+-------------------------------------
+ PATTERN (((a | b)+ c | d) e | f)
+(1 row)
+
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (((A | B)+ C | D) E | F)
+ DEFINE A AS v % 6 = 0, B AS v % 6 = 1, C AS v % 6 = 2,
+ D AS v % 6 = 3, E AS v % 6 = 4, F AS v % 6 = 5
+);');
+ rpr_explain_filter
+----------------------------------------------------------------------
+ WindowAgg (actual rows=20.00 loops=1)
+ Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+ Pattern: (((a | b)+ c | d) e | f)
+ Nav Mark Lookback: 0
+ Storage: Memory Maximum Storage: NkB
+ NFA States: 11 peak, 177 total, 0 merged
+ NFA Contexts: 4 peak, 21 total, 4 pruned
+ NFA: 6 matched (len 1/2/1.5), 7 mismatched (len 2/4/3.1)
+ NFA: 0 absorbed, 3 skipped (len 1/1/1.0)
+ -> Function Scan on generate_series s (actual rows=20.00 loops=1)
+(10 rows)
+
+-- Alternation trailing a paren-less sequence (last element of a non-last
+-- branch, no same-depth sibling to bound it), nested three deep
+-- Pattern: (A (B (C | D) | E) | F) - each inner alternation is branch-tail
+CREATE VIEW rpr_ev_alt_tail3 AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A (B (C | D) | E) | F)
+ DEFINE A AS v % 6 = 0, B AS v % 6 = 1, C AS v % 6 = 2,
+ D AS v % 6 = 3, E AS v % 6 = 4, F AS v % 6 = 5
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_alt_tail3'), E'\n')) AS line WHERE line ~ 'PATTERN';
+ line
+------------------------------------
+ PATTERN (a (b (c | d) | e) | f)
+(1 row)
+
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A (B (C | D) | E) | F)
+ DEFINE A AS v % 6 = 0, B AS v % 6 = 1, C AS v % 6 = 2,
+ D AS v % 6 = 3, E AS v % 6 = 4, F AS v % 6 = 5
+);');
+ rpr_explain_filter
+----------------------------------------------------------------------
+ WindowAgg (actual rows=20.00 loops=1)
+ Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+ Pattern: (a (b (c | d) | e) | f)
+ Nav Mark Lookback: 0
+ Storage: Memory Maximum Storage: NkB
+ NFA States: 5 peak, 75 total, 0 merged
+ NFA Contexts: 3 peak, 21 total, 11 pruned
+ NFA: 6 matched (len 1/3/2.0), 0 mismatched
+ NFA: 0 absorbed, 3 skipped (len 1/1/1.0)
+ -> Function Scan on generate_series s (actual rows=20.00 loops=1)
+(10 rows)
+
+-- Same branch-tail alternation nested four deep
+-- Pattern: (A (B (C (D | E) | F) | G) | H) - branch-tail alternation x4
+CREATE VIEW rpr_ev_alt_tail4 AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A (B (C (D | E) | F) | G) | H)
+ DEFINE A AS v % 8 = 0, B AS v % 8 = 1, C AS v % 8 = 2, D AS v % 8 = 3,
+ E AS v % 8 = 4, F AS v % 8 = 5, G AS v % 8 = 6, H AS v % 8 = 7
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_alt_tail4'), E'\n')) AS line WHERE line ~ 'PATTERN';
+ line
+--------------------------------------------
+ PATTERN (a (b (c (d | e) | f) | g) | h)
+(1 row)
+
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A (B (C (D | E) | F) | G) | H)
+ DEFINE A AS v % 8 = 0, B AS v % 8 = 1, C AS v % 8 = 2, D AS v % 8 = 3,
+ E AS v % 8 = 4, F AS v % 8 = 5, G AS v % 8 = 6, H AS v % 8 = 7
+);');
+ rpr_explain_filter
+----------------------------------------------------------------------
+ WindowAgg (actual rows=20.00 loops=1)
+ Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+ Pattern: (a (b (c (d | e) | f) | g) | h)
+ Nav Mark Lookback: 0
+ Storage: Memory Maximum Storage: NkB
+ NFA States: 5 peak, 75 total, 0 merged
+ NFA Contexts: 3 peak, 21 total, 14 pruned
+ NFA: 4 matched (len 1/4/2.5), 0 mismatched
+ NFA: 0 absorbed, 2 skipped (len 1/1/1.0)
+ -> Function Scan on generate_series s (actual rows=20.00 loops=1)
+(10 rows)
+
+-- A nested alternation tail neighbouring a multi-element sequence branch: the
+-- branch boundary must split "...branch-tail ALT" from a plain "G A" sequence
+-- Pattern: (A (B (C (D | E) | F) | G A) | H) - seq branch beside an ALT tail
+CREATE VIEW rpr_ev_alt_tail_seqbranch AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A (B (C (D | E) | F) | G A) | H)
+ DEFINE A AS v % 8 = 0, B AS v % 8 = 1, C AS v % 8 = 2, D AS v % 8 = 3,
+ E AS v % 8 = 4, F AS v % 8 = 5, G AS v % 8 = 6, H AS v % 8 = 7
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_alt_tail_seqbranch'), E'\n')) AS line WHERE line ~ 'PATTERN';
+ line
+----------------------------------------------
+ PATTERN (a (b (c (d | e) | f) | g a) | h)
+(1 row)
+
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A (B (C (D | E) | F) | G A) | H)
+ DEFINE A AS v % 8 = 0, B AS v % 8 = 1, C AS v % 8 = 2, D AS v % 8 = 3,
+ E AS v % 8 = 4, F AS v % 8 = 5, G AS v % 8 = 6, H AS v % 8 = 7
+);');
+ rpr_explain_filter
+----------------------------------------------------------------------
+ WindowAgg (actual rows=20.00 loops=1)
+ Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+ Pattern: (a (b (c (d | e) | f) | g a) | h)
+ Nav Mark Lookback: 0
+ Storage: Memory Maximum Storage: NkB
+ NFA States: 5 peak, 75 total, 0 merged
+ NFA Contexts: 3 peak, 21 total, 14 pruned
+ NFA: 4 matched (len 1/4/2.5), 0 mismatched
+ NFA: 0 absorbed, 2 skipped (len 1/1/1.0)
+ -> Function Scan on generate_series s (actual rows=20.00 loops=1)
+(10 rows)
+
+-- A nested alternation that is sibling-bounded by a trailing sequence element
+-- at the outer level (the ALT is not the branch tail; G follows it in-branch)
+-- Pattern: ((A (B (C | D) | E) | F) G | H) - ALT bounded by a following element
+CREATE VIEW rpr_ev_alt_mid_seqtail AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((A (B (C | D) | E) | F) G | H)
+ DEFINE A AS v % 8 = 0, B AS v % 8 = 1, C AS v % 8 = 2, D AS v % 8 = 3,
+ E AS v % 8 = 4, F AS v % 8 = 5, G AS v % 8 = 6, H AS v % 8 = 7
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_alt_mid_seqtail'), E'\n')) AS line WHERE line ~ 'PATTERN';
+ line
+--------------------------------------------
+ PATTERN ((a (b (c | d) | e) | f) g | h)
+(1 row)
+
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((A (B (C | D) | E) | F) G | H)
+ DEFINE A AS v % 8 = 0, B AS v % 8 = 1, C AS v % 8 = 2, D AS v % 8 = 3,
+ E AS v % 8 = 4, F AS v % 8 = 5, G AS v % 8 = 6, H AS v % 8 = 7
+);');
+ rpr_explain_filter
+----------------------------------------------------------------------
+ WindowAgg (actual rows=20.00 loops=1)
+ Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+ Pattern: ((a (b (c | d) | e) | f) g | h)
+ Nav Mark Lookback: 0
+ Storage: Memory Maximum Storage: NkB
+ NFA States: 6 peak, 113 total, 0 merged
+ NFA Contexts: 3 peak, 21 total, 12 pruned
+ NFA: 4 matched (len 1/2/1.5), 2 mismatched (len 4/4/4.0)
+ NFA: 0 absorbed, 2 skipped (len 1/1/1.0)
+ -> Function Scan on generate_series s (actual rows=20.00 loops=1)
+(10 rows)
+
-- ============================================================
-- Group Pattern Tests
-- ============================================================
diff --git a/src/test/regress/sql/rpr_explain.sql b/src/test/regress/sql/rpr_explain.sql
index c8b159e30e6..aa78ffed260 100644
--- a/src/test/regress/sql/rpr_explain.sql
+++ b/src/test/regress/sql/rpr_explain.sql
@@ -2118,6 +2118,529 @@ WINDOW w AS (
DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2, D AS v % 4 = 3
);');
+-- Quantified group as the first alternation branch
+-- Pattern: ((A B)+ | C) - leading group branch must open the enclosing paren
+CREATE VIEW rpr_ev_alt_grp_first AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((A B)+ | C)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_alt_grp_first'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((A B)+ | C)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2
+);');
+
+-- Quantified group as the last alternation branch
+-- Pattern: (C | (A B)+) - trailing group branch, no separator follows
+CREATE VIEW rpr_ev_alt_grp_last AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (C | (A B)+)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_alt_grp_last'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (C | (A B)+)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2
+);');
+
+-- Quantified group as the middle branch of a three-way alternation
+-- Pattern: (C | (A B)+ | D) - separator before D must survive the group branch
+CREATE VIEW rpr_ev_alt_grp_mid AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (C | (A B)+ | D)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2, D AS v % 4 = 3
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_alt_grp_mid'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (C | (A B)+ | D)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2, D AS v % 4 = 3
+);');
+
+-- Quantified group as the first branch of a three-way alternation
+-- Pattern: ((A B)+ | C | D) - leading group branch with two following branches
+CREATE VIEW rpr_ev_alt_grp_first3 AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((A B)+ | C | D)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2, D AS v % 4 = 3
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_alt_grp_first3'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((A B)+ | C | D)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2, D AS v % 4 = 3
+);');
+
+-- Bounded-quantifier group as the first alternation branch
+-- Pattern: ((A B){2} | C) - leading group branch with a range quantifier
+CREATE VIEW rpr_ev_alt_grp_bounded AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((A B){2} | C)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_alt_grp_bounded'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((A B){2} | C)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2
+);');
+
+-- Two quantified groups in one alternation
+-- Pattern: ((A B)+ | (C D)+) - both branches are groups
+CREATE VIEW rpr_ev_alt_grp_both AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((A B)+ | (C D)+)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2, D AS v % 4 = 3
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_alt_grp_both'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((A B)+ | (C D)+)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2, D AS v % 4 = 3
+);');
+
+-- Leading group branch in an alternation nested in a sequence
+-- Pattern: (((A B)+ | C) D) - inner alternation opens with a group branch
+CREATE VIEW rpr_ev_alt_grp_seq_head AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (((A B)+ | C) D)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2, D AS v % 4 = 3
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_alt_grp_seq_head'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (((A B)+ | C) D)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2, D AS v % 4 = 3
+);');
+
+-- Trailing group branch in an alternation nested in a sequence
+-- Pattern: ((C | (A B)+) D) - group as last branch, then a sequence element
+CREATE VIEW rpr_ev_alt_grp_seq_tail AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((C | (A B)+) D)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2, D AS v % 4 = 3
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_alt_grp_seq_tail'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((C | (A B)+) D)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2, D AS v % 4 = 3
+);');
+
+-- Quantified alternation whose first branch is a quantified group
+-- Pattern: (((A B){2} | C)+) - single-ALT group wraps a leading group branch
+CREATE VIEW rpr_ev_alt_grp_quant AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (((A B){2} | C)+)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_alt_grp_quant'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (((A B){2} | C)+)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2
+);');
+
+-- Unit (1,1) group as an alternation branch (emits no BEGIN/END)
+-- Pattern: ((A B) | C) - control: takes the variable path, not deparse_rpr_group
+CREATE VIEW rpr_ev_alt_grp_unit AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((A B) | C)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_alt_grp_unit'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((A B) | C)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2
+);');
+
+-- Quantified variable as the first alternation branch
+-- Pattern: (A+ | C) - control: deparse_rpr_var already opens the leading paren
+CREATE VIEW rpr_ev_alt_var_first AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A+ | C)
+ DEFINE A AS v % 4 = 0, C AS v % 4 = 2
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_alt_var_first'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A+ | C)
+ DEFINE A AS v % 4 = 0, C AS v % 4 = 2
+);');
+
+-- Quantified group as the last branch of a three-way alternation
+-- Pattern: (C | D | (A B)+) - control: trailing group needs no separator
+CREATE VIEW rpr_ev_alt_grp_last3 AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (C | D | (A B)+)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2, D AS v % 4 = 3
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_alt_grp_last3'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (C | D | (A B)+)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2, D AS v % 4 = 3
+);');
+
+-- Alternation nested in a leading branch must not swallow the trailing branch
+-- Pattern: (D (A | B) | E) - inherited limit bounds the inner alternation
+CREATE VIEW rpr_ev_alt_inner_bounded AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (D (A | B) | E)
+ DEFINE A AS v % 5 = 0, B AS v % 5 = 1, D AS v % 5 = 2, E AS v % 5 = 3
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_alt_inner_bounded'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (D (A | B) | E)
+ DEFINE A AS v % 5 = 0, B AS v % 5 = 1, D AS v % 5 = 2, E AS v % 5 = 3
+);');
+
+-- Group mid-branch followed by a sequence element needs no separator before it
+-- Pattern: (C | (A B)+ D) - relative-next blocks a spurious separator at D
+CREATE VIEW rpr_ev_alt_grp_then_seq AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (C | (A B)+ D)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2, D AS v % 4 = 3
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_alt_grp_then_seq'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (C | (A B)+ D)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2, D AS v % 4 = 3
+);');
+
+-- Quantified group wrapping a lone alternation: the ALT supplies the parens
+-- Pattern: ((A | B)+) - loneAlt path, single pair of parens
+CREATE VIEW rpr_ev_grp_lone_alt AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((A | B)+)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_grp_lone_alt'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((A | B)+)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1
+);');
+
+-- Quantified group wrapping a sequence whose last element is an alternation
+-- Pattern: ((A (B | C))+) - group paren plus a nested alternation paren
+CREATE VIEW rpr_ev_grp_seq_alt AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((A (B | C))+)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_grp_seq_alt'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((A (B | C))+)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2
+);');
+
+-- Quantified group wrapping a sequence whose first element is an alternation
+-- Pattern: (((A | B) C)+) - leading nested alternation inside a group sequence
+CREATE VIEW rpr_ev_grp_alt_seq AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (((A | B) C)+)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_grp_alt_seq'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (((A | B) C)+)
+ DEFINE A AS v % 4 = 0, B AS v % 4 = 1, C AS v % 4 = 2
+);');
+
+-- Alternation non-last in a non-last branch, stacked three deep: each level's
+-- inherited limit must bound the inner alternation against the next branch
+-- Pattern: (((A | B) C | D) E | F) - three nested inherited-limit boundaries
+CREATE VIEW rpr_ev_alt_stack3 AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (((A | B) C | D) E | F)
+ DEFINE A AS v % 6 = 0, B AS v % 6 = 1, C AS v % 6 = 2,
+ D AS v % 6 = 3, E AS v % 6 = 4, F AS v % 6 = 5
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_alt_stack3'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (((A | B) C | D) E | F)
+ DEFINE A AS v % 6 = 0, B AS v % 6 = 1, C AS v % 6 = 2,
+ D AS v % 6 = 3, E AS v % 6 = 4, F AS v % 6 = 5
+);');
+
+-- Same interaction stacked four deep, to exercise the induction one step further
+-- Pattern: ((((A | B) C | D) E | F) G | H) - four nested inherited-limit boundaries
+CREATE VIEW rpr_ev_alt_stack4 AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((((A | B) C | D) E | F) G | H)
+ DEFINE A AS v % 8 = 0, B AS v % 8 = 1, C AS v % 8 = 2, D AS v % 8 = 3,
+ E AS v % 8 = 4, F AS v % 8 = 5, G AS v % 8 = 6, H AS v % 8 = 7
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_alt_stack4'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((((A | B) C | D) E | F) G | H)
+ DEFINE A AS v % 8 = 0, B AS v % 8 = 1, C AS v % 8 = 2, D AS v % 8 = 3,
+ E AS v % 8 = 4, F AS v % 8 = 5, G AS v % 8 = 6, H AS v % 8 = 7
+);');
+
+-- Three-deep stack whose innermost branch is a quantified group: the group's
+-- skip-target jump must not be mistaken for a branch separator at any depth
+-- Pattern: (((A | B)+ C | D) E | F) - inherited limit plus loneAlt at the base
+CREATE VIEW rpr_ev_alt_stack3_grp AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (((A | B)+ C | D) E | F)
+ DEFINE A AS v % 6 = 0, B AS v % 6 = 1, C AS v % 6 = 2,
+ D AS v % 6 = 3, E AS v % 6 = 4, F AS v % 6 = 5
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_alt_stack3_grp'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (((A | B)+ C | D) E | F)
+ DEFINE A AS v % 6 = 0, B AS v % 6 = 1, C AS v % 6 = 2,
+ D AS v % 6 = 3, E AS v % 6 = 4, F AS v % 6 = 5
+);');
+
+-- Alternation trailing a paren-less sequence (last element of a non-last
+-- branch, no same-depth sibling to bound it), nested three deep
+-- Pattern: (A (B (C | D) | E) | F) - each inner alternation is branch-tail
+CREATE VIEW rpr_ev_alt_tail3 AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A (B (C | D) | E) | F)
+ DEFINE A AS v % 6 = 0, B AS v % 6 = 1, C AS v % 6 = 2,
+ D AS v % 6 = 3, E AS v % 6 = 4, F AS v % 6 = 5
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_alt_tail3'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A (B (C | D) | E) | F)
+ DEFINE A AS v % 6 = 0, B AS v % 6 = 1, C AS v % 6 = 2,
+ D AS v % 6 = 3, E AS v % 6 = 4, F AS v % 6 = 5
+);');
+
+-- Same branch-tail alternation nested four deep
+-- Pattern: (A (B (C (D | E) | F) | G) | H) - branch-tail alternation x4
+CREATE VIEW rpr_ev_alt_tail4 AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A (B (C (D | E) | F) | G) | H)
+ DEFINE A AS v % 8 = 0, B AS v % 8 = 1, C AS v % 8 = 2, D AS v % 8 = 3,
+ E AS v % 8 = 4, F AS v % 8 = 5, G AS v % 8 = 6, H AS v % 8 = 7
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_alt_tail4'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A (B (C (D | E) | F) | G) | H)
+ DEFINE A AS v % 8 = 0, B AS v % 8 = 1, C AS v % 8 = 2, D AS v % 8 = 3,
+ E AS v % 8 = 4, F AS v % 8 = 5, G AS v % 8 = 6, H AS v % 8 = 7
+);');
+
+-- A nested alternation tail neighbouring a multi-element sequence branch: the
+-- branch boundary must split "...branch-tail ALT" from a plain "G A" sequence
+-- Pattern: (A (B (C (D | E) | F) | G A) | H) - seq branch beside an ALT tail
+CREATE VIEW rpr_ev_alt_tail_seqbranch AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A (B (C (D | E) | F) | G A) | H)
+ DEFINE A AS v % 8 = 0, B AS v % 8 = 1, C AS v % 8 = 2, D AS v % 8 = 3,
+ E AS v % 8 = 4, F AS v % 8 = 5, G AS v % 8 = 6, H AS v % 8 = 7
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_alt_tail_seqbranch'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A (B (C (D | E) | F) | G A) | H)
+ DEFINE A AS v % 8 = 0, B AS v % 8 = 1, C AS v % 8 = 2, D AS v % 8 = 3,
+ E AS v % 8 = 4, F AS v % 8 = 5, G AS v % 8 = 6, H AS v % 8 = 7
+);');
+
+-- A nested alternation that is sibling-bounded by a trailing sequence element
+-- at the outer level (the ALT is not the branch tail; G follows it in-branch)
+-- Pattern: ((A (B (C | D) | E) | F) G | H) - ALT bounded by a following element
+CREATE VIEW rpr_ev_alt_mid_seqtail AS
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((A (B (C | D) | E) | F) G | H)
+ DEFINE A AS v % 8 = 0, B AS v % 8 = 1, C AS v % 8 = 2, D AS v % 8 = 3,
+ E AS v % 8 = 4, F AS v % 8 = 5, G AS v % 8 = 6, H AS v % 8 = 7
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev_alt_mid_seqtail'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
+SELECT count(*) OVER w
+FROM generate_series(1, 20) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((A (B (C | D) | E) | F) G | H)
+ DEFINE A AS v % 8 = 0, B AS v % 8 = 1, C AS v % 8 = 2, D AS v % 8 = 3,
+ E AS v % 8 = 4, F AS v % 8 = 5, G AS v % 8 = 6, H AS v % 8 = 7
+);');
+
-- ============================================================
-- Group Pattern Tests
-- ============================================================
--
2.50.1 (Apple Git-155)