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