nocfbot-0059-Allow-a-row-pattern-quantifier-with-no-space-befo.txt
text/plain
Filename: nocfbot-0059-Allow-a-row-pattern-quantifier-with-no-space-befo.txt
Type: text/plain
Part: 57
Message:
Re: Row pattern recognition
From 657e8c72bca567387d073a629cf5813a7871ae2d Mon Sep 17 00:00:00 2001
From: Henson Choi <assam258@gmail.com>
Date: Mon, 8 Jun 2026 11:23:03 +0900
Subject: [PATCH 59/68] Allow a row pattern quantifier with no space before the
alternation operator
The scanner lexes an operator such as "*|" as a single token, so a PATTERN
like (A*|B) previously failed with "unsupported quantifier". Recognize these
glued forms in the quantifier rules and, once the surrounding sequence is
built, re-split it at the affected term so "|" keeps its lowest precedence;
(A*|B) now parses identically to the spaced (A* | B). This covers the op-char
quantifiers (*| +| ?| *?| +?| ??|), the reluctant range forms ({n}?| and the
like), and mixed spacing such as "A* ?|B". A dangling "|" with no right-hand
pattern is still rejected.
---
src/backend/parser/gram.y | 195 +++++++++++++++++++--
src/backend/parser/parse_rpr.c | 6 +
src/include/nodes/parsenodes.h | 8 +
src/test/regress/expected/rpr_base.out | 228 +++++++++++++++++++++++++
src/test/regress/sql/rpr_base.sql | 112 ++++++++++++
5 files changed, 535 insertions(+), 14 deletions(-)
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a2fafb717cd..147b5f37293 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -210,6 +210,8 @@ static void preprocess_pub_all_objtype_list(List *all_objects_list,
static void preprocess_pubobj_list(List *pubobjspec_list,
core_yyscan_t yyscanner);
static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
+static RPRPatternNode *makeRPRSeqOrSingle(List *children, int location);
+static RPRPatternNode *splitRPRTrailingAlt(RPRPatternNode *node, core_yyscan_t yyscanner);
static RPRPatternNode *makeRPRQuantifier(int min, int max, ParseLoc reluctant, int location,
core_yyscan_t yyscanner);
@@ -17624,23 +17626,30 @@ row_pattern:
;
row_pattern_alt:
- row_pattern_seq { $$ = $1; }
+ row_pattern_seq
+ {
+ $$ = (Node *) splitRPRTrailingAlt((RPRPatternNode *) $1,
+ yyscanner);
+ }
| row_pattern_alt '|' row_pattern_seq
{
RPRPatternNode *n;
+ RPRPatternNode *rhs = splitRPRTrailingAlt((RPRPatternNode *) $3,
+ yyscanner);
+
/* If left side is already ALT, append to it */
if (IsA($1, RPRPatternNode) &&
((RPRPatternNode *) $1)->nodeType == RPR_PATTERN_ALT)
{
n = (RPRPatternNode *) $1;
- n->children = lappend(n->children, $3);
+ n->children = lappend(n->children, rhs);
$$ = (Node *) n;
}
else
{
n = makeNode(RPRPatternNode);
n->nodeType = RPR_PATTERN_ALT;
- n->children = list_make2($1, $3);
+ n->children = list_make2($1, rhs);
n->min = 1;
n->max = 1;
n->reluctant = false;
@@ -17656,7 +17665,12 @@ row_pattern_seq:
| row_pattern_seq row_pattern_term
{
RPRPatternNode *n;
- /* If left side is already SEQ, append to it */
+
+ /*
+ * If left side is already SEQ, append to it. A glued
+ * quantifier's trailing_alt stays on the child term;
+ * row_pattern_alt splits on it once the seq is complete.
+ */
if (IsA($1, RPRPatternNode) &&
((RPRPatternNode *) $1)->nodeType == RPR_PATTERN_SEQ)
{
@@ -17689,6 +17703,7 @@ row_pattern_term:
n->max = q->max;
n->reluctant = q->reluctant;
n->reluctant_location = q->reluctant_location;
+ n->trailing_alt = q->trailing_alt;
$$ = (Node *) n;
}
;
@@ -17739,6 +17754,36 @@ row_pattern_quantifier_opt:
$$ = (Node *) makeRPRQuantifier(1, INT_MAX, @1 + 1, @1, yyscanner);
else if (strcmp($1, "??") == 0)
$$ = (Node *) makeRPRQuantifier(0, 1, @1 + 1, @1, yyscanner);
+ else if (strcmp($1, "*|") == 0)
+ {
+ $$ = (Node *) makeRPRQuantifier(0, INT_MAX, -1, @1, yyscanner);
+ ((RPRPatternNode *) $$)->trailing_alt = true;
+ }
+ else if (strcmp($1, "+|") == 0)
+ {
+ $$ = (Node *) makeRPRQuantifier(1, INT_MAX, -1, @1, yyscanner);
+ ((RPRPatternNode *) $$)->trailing_alt = true;
+ }
+ else if (strcmp($1, "?|") == 0)
+ {
+ $$ = (Node *) makeRPRQuantifier(0, 1, -1, @1, yyscanner);
+ ((RPRPatternNode *) $$)->trailing_alt = true;
+ }
+ else if (strcmp($1, "*?|") == 0)
+ {
+ $$ = (Node *) makeRPRQuantifier(0, INT_MAX, @1 + 1, @1, yyscanner);
+ ((RPRPatternNode *) $$)->trailing_alt = true;
+ }
+ else if (strcmp($1, "+?|") == 0)
+ {
+ $$ = (Node *) makeRPRQuantifier(1, INT_MAX, @1 + 1, @1, yyscanner);
+ ((RPRPatternNode *) $$)->trailing_alt = true;
+ }
+ else if (strcmp($1, "??|") == 0)
+ {
+ $$ = (Node *) makeRPRQuantifier(0, 1, @1 + 1, @1, yyscanner);
+ ((RPRPatternNode *) $$)->trailing_alt = true;
+ }
else
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
@@ -17749,33 +17794,60 @@ row_pattern_quantifier_opt:
/* RELUCTANT quantifiers (when lexer separates tokens) */
| '*' Op
{
- if (strcmp($2, "?") != 0)
+ if (strcmp($2, "?") == 0)
+ $$ = (Node *) makeRPRQuantifier(0, INT_MAX, @2, @1, yyscanner);
+ else if (strcmp($2, "?|") == 0)
+ {
+ /* "A* ?|B" = reluctant "A*?" plus alternation */
+ $$ = (Node *) makeRPRQuantifier(0, INT_MAX, @2, @1, yyscanner);
+ ((RPRPatternNode *) $$)->trailing_alt = true;
+ }
+ else
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid token after \"*\" quantifier"),
errhint("Did you mean \"*?\" for reluctant quantifier?"),
parser_errposition(@2));
- $$ = (Node *) makeRPRQuantifier(0, INT_MAX, @2, @1, yyscanner);
}
| '+' Op
{
- if (strcmp($2, "?") != 0)
+ if (strcmp($2, "?") == 0)
+ $$ = (Node *) makeRPRQuantifier(1, INT_MAX, @2, @1, yyscanner);
+ else if (strcmp($2, "?|") == 0)
+ {
+ /* "A+ ?|B" = reluctant "A+?" plus alternation */
+ $$ = (Node *) makeRPRQuantifier(1, INT_MAX, @2, @1, yyscanner);
+ ((RPRPatternNode *) $$)->trailing_alt = true;
+ }
+ else
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid token after \"+\" quantifier"),
errhint("Did you mean \"+?\" for reluctant quantifier?"),
parser_errposition(@2));
- $$ = (Node *) makeRPRQuantifier(1, INT_MAX, @2, @1, yyscanner);
}
| Op Op
{
- if (strcmp($1, "?") != 0 || strcmp($2, "?") != 0)
+ if (strcmp($1, "?") != 0)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid quantifier combination"),
+ errhint("Did you mean \"??\" for reluctant quantifier?"),
+ parser_errposition(@1));
+ if (strcmp($2, "?") == 0)
+ $$ = (Node *) makeRPRQuantifier(0, 1, @2, @1, yyscanner);
+ else if (strcmp($2, "?|") == 0)
+ {
+ /* "A? ?|B" = reluctant "A??" plus alternation */
+ $$ = (Node *) makeRPRQuantifier(0, 1, @2, @1, yyscanner);
+ ((RPRPatternNode *) $$)->trailing_alt = true;
+ }
+ else
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid quantifier combination"),
errhint("Did you mean \"??\" for reluctant quantifier?"),
parser_errposition(@1));
- $$ = (Node *) makeRPRQuantifier(0, 1, @2, @1, yyscanner);
}
/* {n}, {n,}, {,m}, {n,m} quantifiers */
| '{' Iconst '}'
@@ -17822,7 +17894,7 @@ row_pattern_quantifier_opt:
/* Reluctant versions: {n}?, {n,}?, {,m}?, {n,m}? */
| '{' Iconst '}' Op
{
- if (strcmp($4, "?") != 0)
+ if (strcmp($4, "?") != 0 && strcmp($4, "?|") != 0)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid token after range quantifier"),
@@ -17834,10 +17906,12 @@ row_pattern_quantifier_opt:
errmsg("quantifier bound must be between 1 and %d", INT_MAX - 1),
parser_errposition(@2));
$$ = (Node *) makeRPRQuantifier($2, $2, @4, @1, yyscanner);
+ if (strcmp($4, "?|") == 0)
+ ((RPRPatternNode *) $$)->trailing_alt = true;
}
| '{' Iconst ',' '}' Op
{
- if (strcmp($5, "?") != 0)
+ if (strcmp($5, "?") != 0 && strcmp($5, "?|") != 0)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid token after range quantifier"),
@@ -17849,10 +17923,12 @@ row_pattern_quantifier_opt:
errmsg("quantifier bound must be between 0 and %d", INT_MAX - 1),
parser_errposition(@2));
$$ = (Node *) makeRPRQuantifier($2, INT_MAX, @5, @1, yyscanner);
+ if (strcmp($5, "?|") == 0)
+ ((RPRPatternNode *) $$)->trailing_alt = true;
}
| '{' ',' Iconst '}' Op
{
- if (strcmp($5, "?") != 0)
+ if (strcmp($5, "?") != 0 && strcmp($5, "?|") != 0)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid token after range quantifier"),
@@ -17864,10 +17940,12 @@ row_pattern_quantifier_opt:
errmsg("quantifier bound must be between 1 and %d", INT_MAX - 1),
parser_errposition(@3));
$$ = (Node *) makeRPRQuantifier(0, $3, @5, @1, yyscanner);
+ if (strcmp($5, "?|") == 0)
+ ((RPRPatternNode *) $$)->trailing_alt = true;
}
| '{' Iconst ',' Iconst '}' Op
{
- if (strcmp($6, "?") != 0)
+ if (strcmp($6, "?") != 0 && strcmp($6, "?|") != 0)
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid token after range quantifier"),
@@ -17884,6 +17962,8 @@ row_pattern_quantifier_opt:
errmsg("quantifier minimum bound must not exceed maximum"),
parser_errposition(@2));
$$ = (Node *) makeRPRQuantifier($2, $4, @6, @1, yyscanner);
+ if (strcmp($6, "?|") == 0)
+ ((RPRPatternNode *) $$)->trailing_alt = true;
}
;
@@ -21325,6 +21405,93 @@ makeRPRQuantifier(int min, int max, ParseLoc reluctant_location, int location,
return n;
}
+/*
+ * Build a SEQ node from children, or return the lone child unchanged.
+ */
+static RPRPatternNode *
+makeRPRSeqOrSingle(List *children, int location)
+{
+ RPRPatternNode *n;
+
+ if (list_length(children) == 1)
+ return (RPRPatternNode *) linitial(children);
+
+ n = makeNode(RPRPatternNode);
+ n->nodeType = RPR_PATTERN_SEQ;
+ n->children = children;
+ n->min = 1;
+ n->max = 1;
+ n->reluctant = false;
+ n->reluctant_location = -1;
+ n->location = location;
+ return n;
+}
+
+/*
+ * A glued quantifier such as "A*|" leaves trailing_alt set on its term while
+ * the enclosing sequence is built. Once the sequence is complete, split it at
+ * the flagged term into alt(left, right), where the right operand is the whole
+ * remaining sequence -- this keeps "|" as the lowest-precedence operator, so
+ * "A*|B C" parses as "A* | (B C)", identical to the spaced form. A flag with
+ * nothing to its right is a dangling "|" and is rejected.
+ */
+static RPRPatternNode *
+splitRPRTrailingAlt(RPRPatternNode *node, core_yyscan_t yyscanner)
+{
+ ListCell *lc;
+ int i = 0;
+
+ if (node->nodeType != RPR_PATTERN_SEQ)
+ {
+ if (node->trailing_alt)
+ {
+ node->trailing_alt = false;
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("alternation operator \"|\" requires a pattern on both sides"),
+ parser_errposition(node->location));
+ }
+ return node;
+ }
+
+ foreach(lc, node->children)
+ {
+ RPRPatternNode *child = (RPRPatternNode *) lfirst(lc);
+
+ if (child->trailing_alt)
+ {
+ List *lefthalf = list_copy_head(node->children, i + 1);
+ List *righthalf = list_copy_tail(node->children, i + 1);
+ RPRPatternNode *altn;
+ RPRPatternNode *rightnode;
+
+ child->trailing_alt = false;
+ if (righthalf == NIL)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("alternation operator \"|\" requires a pattern on both sides"),
+ parser_errposition(node->location));
+
+ /* the right branch starts at its own first element, not the seq start */
+ rightnode = splitRPRTrailingAlt(makeRPRSeqOrSingle(righthalf,
+ ((RPRPatternNode *) linitial(righthalf))->location),
+ yyscanner);
+ altn = makeNode(RPRPatternNode);
+ altn->nodeType = RPR_PATTERN_ALT;
+ altn->children = list_make2(makeRPRSeqOrSingle(lefthalf, node->location),
+ rightnode);
+ altn->min = 1;
+ altn->max = 1;
+ altn->reluctant = false;
+ altn->reluctant_location = -1;
+ altn->location = node->location;
+ return altn;
+ }
+ i++;
+ }
+ return node;
+}
+
/* parser_init()
* Initialize to parse one query string
*/
diff --git a/src/backend/parser/parse_rpr.c b/src/backend/parser/parse_rpr.c
index c9469b56b7b..4e1d2650cf6 100644
--- a/src/backend/parser/parse_rpr.c
+++ b/src/backend/parser/parse_rpr.c
@@ -213,6 +213,12 @@ validateRPRPatternVarCount(ParseState *pstate, RPRPatternNode *node,
/* Pattern node must exist - parser always provides non-NULL root */
Assert(node != NULL);
+ /*
+ * trailing_alt is a transient grammar flag; splitRPRTrailingAlt must have
+ * cleared it on every node before the pattern reaches parse analysis.
+ */
+ Assert(!node->trailing_alt);
+
check_stack_depth();
switch (node->nodeType)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 5200182aa46..e371f04a403 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -632,6 +632,14 @@ typedef struct RPRPatternNode
ParseLoc location; /* token location, or -1 */
char *varName; /* VAR: variable name */
List *children; /* SEQ, ALT, GROUP: child nodes */
+
+ /*
+ * Transient parse flag, cleared by splitRPRTrailingAlt before the tree is
+ * finalized: a glued quantifier such as "*|" is immediately followed by
+ * the alternation operator '|'. It is always false in a finalized tree,
+ * so query_jumble_ignore keeps it off the pg_stat_statements queryid.
+ */
+ bool trailing_alt pg_node_attr(query_jumble_ignore);
} RPRPatternNode;
/*
diff --git a/src/test/regress/expected/rpr_base.out b/src/test/regress/expected/rpr_base.out
index c50c7a5f6a8..1fcb2ce22f0 100644
--- a/src/test/regress/expected/rpr_base.out
+++ b/src/test/regress/expected/rpr_base.out
@@ -13,6 +13,7 @@
-- Navigation Functions Tests
-- SKIP TO / INITIAL Tests
-- Serialization/Deserialization Tests (objects kept for pg_upgrade/pg_dump)
+-- Glued Quantifier / Alternation Tests
-- Error Cases Tests
-- Window Deduplication Tests
--
@@ -3083,6 +3084,233 @@ SELECT pg_get_viewdef('rpr_quant_n_plus_v'::regclass);
a AS (val > 0) );
(1 row)
+-- ============================================================
+-- Glued Quantifier / Alternation Tests
+-- ============================================================
+CREATE TABLE rpr_glue (id INT, val INT);
+INSERT INTO rpr_glue VALUES (1, 5), (2, 8), (3, 9), (4, -1), (5, 6), (6, -2);
+-- Quantifier glued to the alternation operator '|' without a space (0059).
+-- The lexer glues the trailing '|' into one Op token; the grammar reattaches it
+-- as the lowest-precedence alternation once the surrounding sequence is built.
+-- Deparse is canonical, so the glued, spaced, and mixed-spacing forms all
+-- reduce to the same PATTERN -- one deparse per shape proves the parse tree.
+-- Op-char quantifiers (*, +, ?, *?, +?, ??) glued to '|'.
+CREATE VIEW rpr_dp_op AS SELECT
+ count(*) OVER w1 AS w1, count(*) OVER w2 AS w2, count(*) OVER w3 AS w3,
+ count(*) OVER w4 AS w4, count(*) OVER w5 AS w5, count(*) OVER w6 AS w6
+FROM rpr_glue
+WINDOW w1 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A*|B) DEFINE A AS val > 0, B AS val <= 0),
+ w2 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A+|B) DEFINE A AS val > 0, B AS val <= 0),
+ w3 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A?|B) DEFINE A AS val > 0, B AS val <= 0),
+ w4 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A*?|B) DEFINE A AS val > 0, B AS val <= 0),
+ w5 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A+?|B) DEFINE A AS val > 0, B AS val <= 0),
+ w6 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A??|B) DEFINE A AS val > 0, B AS val <= 0);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_dp_op'), E'\n')) AS line WHERE line ~ 'PATTERN';
+ line
+----------------------
+ PATTERN (a* | b)
+ PATTERN (a+ | b)
+ PATTERN (a? | b)
+ PATTERN (a*? | b)
+ PATTERN (a+? | b)
+ PATTERN (a?? | b)
+(6 rows)
+
+DROP VIEW rpr_dp_op;
+-- Spaced reference: the fully-spaced canonical forms. Identical deparse to the
+-- glued rpr_dp_op w1/w4 above completes the glued = spaced = mixed equivalence.
+CREATE VIEW rpr_dp_spc AS SELECT count(*) OVER w1 AS w1, count(*) OVER w2 AS w2
+FROM rpr_glue
+WINDOW w1 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A* | B) DEFINE A AS val > 0, B AS val <= 0),
+ w2 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A*? | B) DEFINE A AS val > 0, B AS val <= 0);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_dp_spc'), E'\n')) AS line WHERE line ~ 'PATTERN';
+ line
+----------------------
+ PATTERN (a* | b)
+ PATTERN (a*? | b)
+(2 rows)
+
+DROP VIEW rpr_dp_spc;
+-- Range quantifiers glued to '|': non-reluctant {n}| (} + char '|') and
+-- reluctant {n}?| (} + Op "?|").
+CREATE VIEW rpr_dp_rng AS SELECT
+ count(*) OVER w1 AS w1, count(*) OVER w2 AS w2, count(*) OVER w3 AS w3, count(*) OVER w4 AS w4,
+ count(*) OVER w5 AS w5, count(*) OVER w6 AS w6, count(*) OVER w7 AS w7, count(*) OVER w8 AS w8
+FROM rpr_glue
+WINDOW w1 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A{2}|B) DEFINE A AS val > 0, B AS val <= 0),
+ w2 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A{2,}|B) DEFINE A AS val > 0, B AS val <= 0),
+ w3 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A{,3}|B) DEFINE A AS val > 0, B AS val <= 0),
+ w4 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A{2,3}|B) DEFINE A AS val > 0, B AS val <= 0),
+ w5 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A{2}?|B) DEFINE A AS val > 0, B AS val <= 0),
+ w6 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A{2,}?|B) DEFINE A AS val > 0, B AS val <= 0),
+ w7 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A{,3}?|B) DEFINE A AS val > 0, B AS val <= 0),
+ w8 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A{2,3}?|B) DEFINE A AS val > 0, B AS val <= 0);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_dp_rng'), E'\n')) AS line WHERE line ~ 'PATTERN';
+ line
+--------------------------
+ PATTERN (a{2} | b)
+ PATTERN (a{2,} | b)
+ PATTERN (a{0,3} | b)
+ PATTERN (a{2,3} | b)
+ PATTERN (a{2}? | b)
+ PATTERN (a{2,}? | b)
+ PATTERN (a{0,3}? | b)
+ PATTERN (a{2,3}? | b)
+(8 rows)
+
+DROP VIEW rpr_dp_rng;
+-- Mixed spacing: a space inside the quantifier with '|' still glued.
+-- "A* ?|B" = '*' + Op"?|" = reluctant "A*?" plus alternation.
+CREATE VIEW rpr_dp_mix AS SELECT count(*) OVER w1 AS w1, count(*) OVER w2 AS w2, count(*) OVER w3 AS w3
+FROM rpr_glue
+WINDOW w1 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A* ?|B) DEFINE A AS val > 0, B AS val <= 0),
+ w2 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A+ ?|B) DEFINE A AS val > 0, B AS val <= 0),
+ w3 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A? ?|B) DEFINE A AS val > 0, B AS val <= 0);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_dp_mix'), E'\n')) AS line WHERE line ~ 'PATTERN';
+ line
+----------------------
+ PATTERN (a*? | b)
+ PATTERN (a+? | b)
+ PATTERN (a?? | b)
+(3 rows)
+
+DROP VIEW rpr_dp_mix;
+-- Structure: precedence (| is lowest, so its right operand is the whole
+-- following sequence), chaining, concatenation, and grouping.
+CREATE VIEW rpr_dp_struct AS SELECT
+ count(*) OVER w1 AS w1, count(*) OVER w2 AS w2, count(*) OVER w3 AS w3,
+ count(*) OVER w4 AS w4, count(*) OVER w5 AS w5, count(*) OVER w6 AS w6
+FROM rpr_glue
+WINDOW w1 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A*|B C) DEFINE A AS val > 0, B AS val <= 0, C AS val < 100),
+ w2 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A*|B*|C) DEFINE A AS val > 0, B AS val <= 0, C AS val < 100),
+ w3 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A B*|C D) DEFINE A AS val > 0, B AS val <= 0, C AS val < 100, D AS val > 5),
+ w4 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN ((A*|B)) DEFINE A AS val > 0, B AS val <= 0),
+ w5 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A*|(B|C)) DEFINE A AS val > 0, B AS val <= 0, C AS val < 100),
+ w6 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN ((A*|B)+) DEFINE A AS val > 0, B AS val <= 0);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_dp_struct'), E'\n')) AS line WHERE line ~ 'PATTERN';
+ line
+---------------------------
+ PATTERN (a* | b c)
+ PATTERN (a* | b* | c)
+ PATTERN (a b* | c d)
+ PATTERN ((a* | b))
+ PATTERN (a* | (b | c))
+ PATTERN ((a* | b)+)
+(6 rows)
+
+DROP VIEW rpr_dp_struct;
+-- Execution semantics (deparse cannot show reluctant shortest-match). The
+-- rpr_glue rows -- an A-run followed by B rows -- make the '|B' alternative
+-- reachable: with "*" the greedy form matches the whole run while the
+-- reluctant form matches empty; with "+" the greedy form matches the run and
+-- the reluctant form matches one row, and on a B row (where "A+" fails) the B
+-- alternative fires.
+SELECT id, val,
+ count(*) OVER gs AS gstar, count(*) OVER rs AS rstar,
+ count(*) OVER gp AS gplus, count(*) OVER rp AS rplus
+FROM rpr_glue
+WINDOW gs AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A*|B) DEFINE A AS val > 0, B AS val <= 0),
+ rs AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A*?|B) DEFINE A AS val > 0, B AS val <= 0),
+ gp AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A+|B) DEFINE A AS val > 0, B AS val <= 0),
+ rp AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A+?|B) DEFINE A AS val > 0, B AS val <= 0)
+ORDER BY id;
+ id | val | gstar | rstar | gplus | rplus
+----+-----+-------+-------+-------+-------
+ 1 | 5 | 3 | 0 | 3 | 1
+ 2 | 8 | 0 | 0 | 0 | 1
+ 3 | 9 | 0 | 0 | 0 | 1
+ 4 | -1 | 1 | 1 | 1 | 1
+ 5 | 6 | 1 | 0 | 1 | 1
+ 6 | -2 | 1 | 1 | 1 | 1
+(6 rows)
+
+-- Patterns that must stay rejected. "&" is an invalid op; a '|' with an empty
+-- side (leading, trailing, doubled, or alone in a group) has no operand; "||"
+-- and "*||" are doubled pipes; "A* *|B"/"A* *?|B"/"A{2}*?|B" are doubled
+-- quantifiers.
+SELECT count(*) OVER w FROM rpr_glue WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A&B) DEFINE A AS val > 0);
+ERROR: unsupported quantifier "&"
+LINE 1: ...EEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A&B) DEFINE...
+ ^
+HINT: Valid quantifiers are: *, +, ?, *?, +?, ??, {n}, {n,}, {,m}, {n,m} and their reluctant versions.
+SELECT count(*) OVER w FROM rpr_glue WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A*|) DEFINE A AS val > 0);
+ERROR: alternation operator "|" requires a pattern on both sides
+LINE 1: ...WEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A*|) DEFIN...
+ ^
+SELECT count(*) OVER w FROM rpr_glue WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A*| |B) DEFINE A AS val > 0, B AS val <= 0);
+ERROR: alternation operator "|" requires a pattern on both sides
+LINE 1: ...WEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A*| |B) DE...
+ ^
+SELECT count(*) OVER w FROM rpr_glue WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A*||B) DEFINE A AS val > 0, B AS val <= 0);
+ERROR: unsupported quantifier "*||"
+LINE 1: ...EEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A*||B) DEFI...
+ ^
+HINT: Valid quantifiers are: *, +, ?, *?, +?, ??, {n}, {n,}, {,m}, {n,m} and their reluctant versions.
+SELECT count(*) OVER w FROM rpr_glue WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A||B) DEFINE A AS val > 0, B AS val <= 0);
+ERROR: unsupported quantifier "||"
+LINE 1: ...EEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A||B) DEFIN...
+ ^
+HINT: Valid quantifiers are: *, +, ?, *?, +?, ??, {n}, {n,}, {,m}, {n,m} and their reluctant versions.
+SELECT count(*) OVER w FROM rpr_glue WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A*|B|) DEFINE A AS val > 0, B AS val <= 0);
+ERROR: syntax error at or near ")"
+LINE 1: ...CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A*|B|) DEFINE A...
+ ^
+SELECT count(*) OVER w FROM rpr_glue WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (|A) DEFINE A AS val > 0);
+ERROR: syntax error at or near "|"
+LINE 1: ...WEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (|A) DEFINE...
+ ^
+SELECT count(*) OVER w FROM rpr_glue WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN ((A*|)) DEFINE A AS val > 0);
+ERROR: alternation operator "|" requires a pattern on both sides
+LINE 1: ...EEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN ((A*|)) DEFI...
+ ^
+SELECT count(*) OVER w FROM rpr_glue WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A* *|B) DEFINE A AS val > 0, B AS val <= 0);
+ERROR: invalid token after "*" quantifier
+LINE 1: ...N CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A* *|B) DEFIN...
+ ^
+HINT: Did you mean "*?" for reluctant quantifier?
+SELECT count(*) OVER w FROM rpr_glue WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A* *?|B) DEFINE A AS val > 0, B AS val <= 0);
+ERROR: invalid token after "*" quantifier
+LINE 1: ...N CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A* *?|B) DEFI...
+ ^
+HINT: Did you mean "*?" for reluctant quantifier?
+SELECT count(*) OVER w FROM rpr_glue WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A? *?|B) DEFINE A AS val > 0, B AS val <= 0);
+ERROR: invalid quantifier combination
+LINE 1: ...EEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A? *?|B) DE...
+ ^
+HINT: Did you mean "??" for reluctant quantifier?
+SELECT count(*) OVER w FROM rpr_glue WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A{2}*?|B) DEFINE A AS val > 0, B AS val <= 0);
+ERROR: invalid token after range quantifier
+LINE 1: ... CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A{2}*?|B) DEFI...
+ ^
+HINT: Only "?" is allowed after {n} to make it reluctant.
+SELECT count(*) OVER w FROM rpr_glue WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A{2} *?|B) DEFINE A AS val > 0, B AS val <= 0);
+ERROR: invalid token after range quantifier
+LINE 1: ...CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A{2} *?|B) DEFI...
+ ^
+HINT: Only "?" is allowed after {n} to make it reluctant.
+-- Doubled op-char quantifiers lex as one Op token and are unsupported, whether
+-- glued to '|' ("**|", "*+|", "???|") or on their own ("**").
+SELECT count(*) OVER w FROM rpr_glue WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A**|B) DEFINE A AS val > 0, B AS val <= 0);
+ERROR: unsupported quantifier "**|"
+LINE 1: ...EEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A**|B) DEFI...
+ ^
+HINT: Valid quantifiers are: *, +, ?, *?, +?, ??, {n}, {n,}, {,m}, {n,m} and their reluctant versions.
+SELECT count(*) OVER w FROM rpr_glue WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A*+|B) DEFINE A AS val > 0, B AS val <= 0);
+ERROR: unsupported quantifier "*+|"
+LINE 1: ...EEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A*+|B) DEFI...
+ ^
+HINT: Valid quantifiers are: *, +, ?, *?, +?, ??, {n}, {n,}, {,m}, {n,m} and their reluctant versions.
+SELECT count(*) OVER w FROM rpr_glue WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A???|B) DEFINE A AS val > 0, B AS val <= 0);
+ERROR: unsupported quantifier "???|"
+LINE 1: ...EEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A???|B) DEF...
+ ^
+HINT: Valid quantifiers are: *, +, ?, *?, +?, ??, {n}, {n,}, {,m}, {n,m} and their reluctant versions.
+SELECT count(*) OVER w FROM rpr_glue WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A**B) DEFINE A AS val > 0);
+ERROR: unsupported quantifier "**"
+LINE 1: ...EEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A**B) DEFIN...
+ ^
+HINT: Valid quantifiers are: *, +, ?, *?, +?, ??, {n}, {n,}, {,m}, {n,m} and their reluctant versions.
+DROP TABLE rpr_glue;
-- ============================================================
-- Error Cases Tests
-- ============================================================
diff --git a/src/test/regress/sql/rpr_base.sql b/src/test/regress/sql/rpr_base.sql
index 7dfb72f6bfd..cc79843aeb7 100644
--- a/src/test/regress/sql/rpr_base.sql
+++ b/src/test/regress/sql/rpr_base.sql
@@ -13,6 +13,7 @@
-- Navigation Functions Tests
-- SKIP TO / INITIAL Tests
-- Serialization/Deserialization Tests (objects kept for pg_upgrade/pg_dump)
+-- Glued Quantifier / Alternation Tests
-- Error Cases Tests
-- Window Deduplication Tests
--
@@ -2074,6 +2075,117 @@ WINDOW w AS (ORDER BY id
DEFINE A AS val > 0);
SELECT pg_get_viewdef('rpr_quant_n_plus_v'::regclass);
+-- ============================================================
+-- Glued Quantifier / Alternation Tests
+-- ============================================================
+CREATE TABLE rpr_glue (id INT, val INT);
+INSERT INTO rpr_glue VALUES (1, 5), (2, 8), (3, 9), (4, -1), (5, 6), (6, -2);
+-- Quantifier glued to the alternation operator '|' without a space (0059).
+-- The lexer glues the trailing '|' into one Op token; the grammar reattaches it
+-- as the lowest-precedence alternation once the surrounding sequence is built.
+-- Deparse is canonical, so the glued, spaced, and mixed-spacing forms all
+-- reduce to the same PATTERN -- one deparse per shape proves the parse tree.
+
+-- Op-char quantifiers (*, +, ?, *?, +?, ??) glued to '|'.
+CREATE VIEW rpr_dp_op AS SELECT
+ count(*) OVER w1 AS w1, count(*) OVER w2 AS w2, count(*) OVER w3 AS w3,
+ count(*) OVER w4 AS w4, count(*) OVER w5 AS w5, count(*) OVER w6 AS w6
+FROM rpr_glue
+WINDOW w1 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A*|B) DEFINE A AS val > 0, B AS val <= 0),
+ w2 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A+|B) DEFINE A AS val > 0, B AS val <= 0),
+ w3 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A?|B) DEFINE A AS val > 0, B AS val <= 0),
+ w4 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A*?|B) DEFINE A AS val > 0, B AS val <= 0),
+ w5 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A+?|B) DEFINE A AS val > 0, B AS val <= 0),
+ w6 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A??|B) DEFINE A AS val > 0, B AS val <= 0);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_dp_op'), E'\n')) AS line WHERE line ~ 'PATTERN';
+DROP VIEW rpr_dp_op;
+-- Spaced reference: the fully-spaced canonical forms. Identical deparse to the
+-- glued rpr_dp_op w1/w4 above completes the glued = spaced = mixed equivalence.
+CREATE VIEW rpr_dp_spc AS SELECT count(*) OVER w1 AS w1, count(*) OVER w2 AS w2
+FROM rpr_glue
+WINDOW w1 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A* | B) DEFINE A AS val > 0, B AS val <= 0),
+ w2 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A*? | B) DEFINE A AS val > 0, B AS val <= 0);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_dp_spc'), E'\n')) AS line WHERE line ~ 'PATTERN';
+DROP VIEW rpr_dp_spc;
+-- Range quantifiers glued to '|': non-reluctant {n}| (} + char '|') and
+-- reluctant {n}?| (} + Op "?|").
+CREATE VIEW rpr_dp_rng AS SELECT
+ count(*) OVER w1 AS w1, count(*) OVER w2 AS w2, count(*) OVER w3 AS w3, count(*) OVER w4 AS w4,
+ count(*) OVER w5 AS w5, count(*) OVER w6 AS w6, count(*) OVER w7 AS w7, count(*) OVER w8 AS w8
+FROM rpr_glue
+WINDOW w1 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A{2}|B) DEFINE A AS val > 0, B AS val <= 0),
+ w2 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A{2,}|B) DEFINE A AS val > 0, B AS val <= 0),
+ w3 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A{,3}|B) DEFINE A AS val > 0, B AS val <= 0),
+ w4 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A{2,3}|B) DEFINE A AS val > 0, B AS val <= 0),
+ w5 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A{2}?|B) DEFINE A AS val > 0, B AS val <= 0),
+ w6 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A{2,}?|B) DEFINE A AS val > 0, B AS val <= 0),
+ w7 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A{,3}?|B) DEFINE A AS val > 0, B AS val <= 0),
+ w8 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A{2,3}?|B) DEFINE A AS val > 0, B AS val <= 0);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_dp_rng'), E'\n')) AS line WHERE line ~ 'PATTERN';
+DROP VIEW rpr_dp_rng;
+-- Mixed spacing: a space inside the quantifier with '|' still glued.
+-- "A* ?|B" = '*' + Op"?|" = reluctant "A*?" plus alternation.
+CREATE VIEW rpr_dp_mix AS SELECT count(*) OVER w1 AS w1, count(*) OVER w2 AS w2, count(*) OVER w3 AS w3
+FROM rpr_glue
+WINDOW w1 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A* ?|B) DEFINE A AS val > 0, B AS val <= 0),
+ w2 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A+ ?|B) DEFINE A AS val > 0, B AS val <= 0),
+ w3 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A? ?|B) DEFINE A AS val > 0, B AS val <= 0);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_dp_mix'), E'\n')) AS line WHERE line ~ 'PATTERN';
+DROP VIEW rpr_dp_mix;
+-- Structure: precedence (| is lowest, so its right operand is the whole
+-- following sequence), chaining, concatenation, and grouping.
+CREATE VIEW rpr_dp_struct AS SELECT
+ count(*) OVER w1 AS w1, count(*) OVER w2 AS w2, count(*) OVER w3 AS w3,
+ count(*) OVER w4 AS w4, count(*) OVER w5 AS w5, count(*) OVER w6 AS w6
+FROM rpr_glue
+WINDOW w1 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A*|B C) DEFINE A AS val > 0, B AS val <= 0, C AS val < 100),
+ w2 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A*|B*|C) DEFINE A AS val > 0, B AS val <= 0, C AS val < 100),
+ w3 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A B*|C D) DEFINE A AS val > 0, B AS val <= 0, C AS val < 100, D AS val > 5),
+ w4 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN ((A*|B)) DEFINE A AS val > 0, B AS val <= 0),
+ w5 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A*|(B|C)) DEFINE A AS val > 0, B AS val <= 0, C AS val < 100),
+ w6 AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN ((A*|B)+) DEFINE A AS val > 0, B AS val <= 0);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_dp_struct'), E'\n')) AS line WHERE line ~ 'PATTERN';
+DROP VIEW rpr_dp_struct;
+-- Execution semantics (deparse cannot show reluctant shortest-match). The
+-- rpr_glue rows -- an A-run followed by B rows -- make the '|B' alternative
+-- reachable: with "*" the greedy form matches the whole run while the
+-- reluctant form matches empty; with "+" the greedy form matches the run and
+-- the reluctant form matches one row, and on a B row (where "A+" fails) the B
+-- alternative fires.
+SELECT id, val,
+ count(*) OVER gs AS gstar, count(*) OVER rs AS rstar,
+ count(*) OVER gp AS gplus, count(*) OVER rp AS rplus
+FROM rpr_glue
+WINDOW gs AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A*|B) DEFINE A AS val > 0, B AS val <= 0),
+ rs AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A*?|B) DEFINE A AS val > 0, B AS val <= 0),
+ gp AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A+|B) DEFINE A AS val > 0, B AS val <= 0),
+ rp AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A+?|B) DEFINE A AS val > 0, B AS val <= 0)
+ORDER BY id;
+-- Patterns that must stay rejected. "&" is an invalid op; a '|' with an empty
+-- side (leading, trailing, doubled, or alone in a group) has no operand; "||"
+-- and "*||" are doubled pipes; "A* *|B"/"A* *?|B"/"A{2}*?|B" are doubled
+-- quantifiers.
+SELECT count(*) OVER w FROM rpr_glue WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A&B) DEFINE A AS val > 0);
+SELECT count(*) OVER w FROM rpr_glue WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A*|) DEFINE A AS val > 0);
+SELECT count(*) OVER w FROM rpr_glue WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A*| |B) DEFINE A AS val > 0, B AS val <= 0);
+SELECT count(*) OVER w FROM rpr_glue WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A*||B) DEFINE A AS val > 0, B AS val <= 0);
+SELECT count(*) OVER w FROM rpr_glue WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A||B) DEFINE A AS val > 0, B AS val <= 0);
+SELECT count(*) OVER w FROM rpr_glue WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A*|B|) DEFINE A AS val > 0, B AS val <= 0);
+SELECT count(*) OVER w FROM rpr_glue WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (|A) DEFINE A AS val > 0);
+SELECT count(*) OVER w FROM rpr_glue WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN ((A*|)) DEFINE A AS val > 0);
+SELECT count(*) OVER w FROM rpr_glue WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A* *|B) DEFINE A AS val > 0, B AS val <= 0);
+SELECT count(*) OVER w FROM rpr_glue WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A* *?|B) DEFINE A AS val > 0, B AS val <= 0);
+SELECT count(*) OVER w FROM rpr_glue WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A? *?|B) DEFINE A AS val > 0, B AS val <= 0);
+SELECT count(*) OVER w FROM rpr_glue WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A{2}*?|B) DEFINE A AS val > 0, B AS val <= 0);
+SELECT count(*) OVER w FROM rpr_glue WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A{2} *?|B) DEFINE A AS val > 0, B AS val <= 0);
+-- Doubled op-char quantifiers lex as one Op token and are unsupported, whether
+-- glued to '|' ("**|", "*+|", "???|") or on their own ("**").
+SELECT count(*) OVER w FROM rpr_glue WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A**|B) DEFINE A AS val > 0, B AS val <= 0);
+SELECT count(*) OVER w FROM rpr_glue WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A*+|B) DEFINE A AS val > 0, B AS val <= 0);
+SELECT count(*) OVER w FROM rpr_glue WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A???|B) DEFINE A AS val > 0, B AS val <= 0);
+SELECT count(*) OVER w FROM rpr_glue WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A**B) DEFINE A AS val > 0);
+DROP TABLE rpr_glue;
+
-- ============================================================
-- Error Cases Tests
-- ============================================================
--
2.50.1 (Apple Git-155)