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