public inbox for [email protected]
help / color / mirror / Atom feedFrom: Henson Choi <[email protected]>
To: Tatsuo Ishii <[email protected]>
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Subject: Re: Row pattern recognition
Date: Fri, 6 Mar 2026 13:38:30 +0900
Message-ID: <CAAAe_zBH=6nh6Yg9EuohthTgzHgtbhtvLYAnabg6mCHQPLLpqQ@mail.gmail.com> (raw)
In-Reply-To: <[email protected]>
References: <[email protected]>
<CAE+cgNiUbKeH1A0PoxV2QjpsoxJLe+pJcGz_gdxwOwu_9zqchw@mail.gmail.com>
<CAAAe_zBFKp7bn9YUamzNiy7s2LQ3C9VXsFLRTyVTbk+ETLfZUQ@mail.gmail.com>
<[email protected]>
Hi Tatsuo,
I reviewed the planner's Window clause optimizations and found
additional issues with RPR windows. Thanks to SungJun's Oracle
cross-validation work, all converted regression tests now pass
on both systems. With these fixes, I'd like to move on to
non-functional quality testing next.
Here are eight incremental patches on top of v44.
nocfbot-0001: Fix elog message to use lowercase per PostgreSQL convention
Minor style fix: lowercase the elog error message in
nodeWindowAgg.c to follow PostgreSQL coding convention.
nocfbot-0002: Fix RPR reluctant quantifier flag lost during VIEW
serialization
The reluctant flag on quantifiers was lost when a VIEW containing
RPR was serialized and restored via ruleutils.c. This patch fixes
gram.y, rpr.c, ruleutils.c, and parsenodes.h so the flag survives
the round-trip.
nocfbot-0003: Expand RPR test coverage and improve test comments
Reorganizes test cases across rpr.sql, rpr_base.sql, rpr_explain.sql,
and rpr_nfa.sql. Moves base functionality tests from rpr.sql to
rpr_base.sql and NFA-specific tests to rpr_nfa.sql. Adds better
section comments throughout. Duplicate tests in rpr.sql were
removed. No reduction in test coverage.
nocfbot-0004: Keep RPR test objects for pg_upgrade/pg_dump testing
Adjusts rpr_base.sql and rpr_explain.sql so that test tables and
views are not dropped at the end of the test. This allows
pg_upgrade and pg_dump regression testing to cover RPR objects.
nocfbot-0005: Disable run condition pushdown for RPR windows
Revised version of your run condition fix [1]. Existing test
expected output updated accordingly.
nocfbot-0006: Disable frame optimization for RPR windows
Prevents the planner from optimizing the window frame for RPR
windows. The frame clause in RPR has different semantics (it
bounds the pattern search space), so standard frame optimizations
must be disabled. Adds EXPLAIN tests to verify. Please review
this one -- it's a newly discovered issue.
nocfbot-0007: Add stock scenario tests for RPR pattern matching
Adds stock trading scenario tests using realistic synthetic data
(stock.data with 1632 rows). Tests V-shape recovery, W-shape,
consecutive rises, and other common pattern matching use cases.
nocfbot-0008: Fix zero-min reluctant quantifier to produce zero-length match
Fixes the bug reported by SungJun [2]: reluctant quantifiers with
min=0 (A*?, A??) were incorrectly consuming at least one row instead
of producing a zero-length match. The NFA can now internally
distinguish between a zero-length match and no match, so it will
be ready when additional infrastructure (e.g. MATCH_NUMBER) is
added. Now matches Oracle behavior.
[1]
https://www.postgresql.org/message-id/[email protected]
[2]
https://www.postgresql.org/message-id/[email protected]...
Best regards,
Henson
From f4abb464c187ef6d1334d856db881d40069531da Mon Sep 17 00:00:00 2001
From: Henson Choi <[email protected]>
Date: Tue, 3 Mar 2026 15:49:54 +0900
Subject: [PATCH 1/8] Fix elog message to use lowercase per PostgreSQL
convention
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 701e83b519b..4441b4d9c51 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -4710,7 +4710,7 @@ row_is_in_reduced_frame(WindowObject winobj, int64 pos)
break;
default:
- elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+ elog(ERROR, "unrecognized state: %d at: " INT64_FORMAT,
state, pos);
break;
}
--
2.50.1 (Apple Git-155)
From 2bc7f29dcfceefc83fd787eb138c96247f565360 Mon Sep 17 00:00:00 2001
From: Henson Choi <[email protected]>
Date: Tue, 3 Mar 2026 15:50:48 +0900
Subject: [PATCH 2/8] Fix RPR reluctant quantifier flag lost during VIEW
serialization
diff --git a/src/backend/optimizer/plan/rpr.c b/src/backend/optimizer/plan/rpr.c
index 8792d8174fa..009c0f5019d 100644
--- a/src/backend/optimizer/plan/rpr.c
+++ b/src/backend/optimizer/plan/rpr.c
@@ -198,7 +198,7 @@ flattenSeqChildren(List *children)
/* GROUP{1,1} should have been unwrapped by optimizeGroupPattern */
Assert(!(opt->nodeType == RPR_PATTERN_GROUP &&
- opt->min == 1 && opt->max == 1 && opt->reluctant < 0));
+ opt->min == 1 && opt->max == 1 && opt->reluctant == false));
if (opt->nodeType == RPR_PATTERN_SEQ)
{
@@ -234,7 +234,7 @@ mergeConsecutiveVars(List *children)
{
RPRPatternNode *child = (RPRPatternNode *) lfirst(lc);
- if (child->nodeType == RPR_PATTERN_VAR && child->reluctant < 0)
+ if (child->nodeType == RPR_PATTERN_VAR && child->reluctant == false)
{
/* ----------------------
* Can merge consecutive VAR nodes if:
@@ -253,7 +253,7 @@ mergeConsecutiveVars(List *children)
* Merge: accumulate min/max into prev. prev is guaranteed to
* be a non-reluctant VAR by the outer condition.
*/
- Assert(prev->nodeType == RPR_PATTERN_VAR && prev->reluctant < 0);
+ Assert(prev->nodeType == RPR_PATTERN_VAR && prev->reluctant == false);
prev->min += child->min;
@@ -308,7 +308,7 @@ mergeConsecutiveGroups(List *children)
{
RPRPatternNode *child = (RPRPatternNode *) lfirst(lc);
- if (child->nodeType == RPR_PATTERN_GROUP && child->reluctant < 0)
+ if (child->nodeType == RPR_PATTERN_GROUP && child->reluctant == false)
{
/* ----------------------
* Can merge consecutive GROUP nodes if:
@@ -327,7 +327,7 @@ mergeConsecutiveGroups(List *children)
* Merge: accumulate min/max into prev. prev is guaranteed to
* be a non-reluctant GROUP by the outer condition.
*/
- Assert(prev->nodeType == RPR_PATTERN_GROUP && prev->reluctant < 0);
+ Assert(prev->nodeType == RPR_PATTERN_GROUP && prev->reluctant == false);
prev->min += child->min;
@@ -385,7 +385,7 @@ mergeConsecutiveAlts(List *children)
{
RPRPatternNode *child = (RPRPatternNode *) lfirst(lc);
- if (child->nodeType == RPR_PATTERN_ALT && child->reluctant < 0)
+ if (child->nodeType == RPR_PATTERN_ALT && child->reluctant == false)
{
if (prev != NULL &&
rprPatternChildrenEqual(prev->children, child->children))
@@ -406,7 +406,8 @@ mergeConsecutiveAlts(List *children)
group->nodeType = RPR_PATTERN_GROUP;
group->min = count;
group->max = count;
- group->reluctant = -1;
+ group->reluctant = false;
+ group->reluctant_location = -1;
group->location = -1;
group->children = list_make1(prev);
mergedChildren = lappend(mergedChildren, group);
@@ -430,7 +431,8 @@ mergeConsecutiveAlts(List *children)
group->nodeType = RPR_PATTERN_GROUP;
group->min = count;
group->max = count;
- group->reluctant = -1;
+ group->reluctant = false;
+ group->reluctant_location = -1;
group->location = -1;
group->children = list_make1(prev);
mergedChildren = lappend(mergedChildren, group);
@@ -454,7 +456,8 @@ mergeConsecutiveAlts(List *children)
group->nodeType = RPR_PATTERN_GROUP;
group->min = count;
group->max = count;
- group->reluctant = -1;
+ group->reluctant = false;
+ group->reluctant_location = -1;
group->location = -1;
group->children = list_make1(prev);
mergedChildren = lappend(mergedChildren, group);
@@ -498,7 +501,7 @@ mergeGroupPrefixSuffix(List *children)
* children. GROUP's content may be wrapped in a SEQ - unwrap for
* comparison.
*/
- if (child->nodeType == RPR_PATTERN_GROUP && child->reluctant < 0)
+ if (child->nodeType == RPR_PATTERN_GROUP && child->reluctant == false)
{
List *groupContent = child->children;
int groupChildCount;
@@ -773,14 +776,14 @@ tryMultiplyQuantifiers(RPRPatternNode *pattern)
/* Parser always creates GROUP with exactly one child */
Assert(list_length(pattern->children) == 1);
- if (pattern->reluctant >= 0)
+ if (pattern->reluctant)
return pattern;
child = (RPRPatternNode *) linitial(pattern->children);
if ((child->nodeType != RPR_PATTERN_VAR &&
child->nodeType != RPR_PATTERN_GROUP) ||
- child->reluctant >= 0)
+ child->reluctant)
return pattern;
/* Case 1: Both unbounded - (A*)* -> A*, (A+)+ -> A+ */
@@ -862,11 +865,12 @@ tryUnwrapGroup(RPRPatternNode *pattern)
* the child and unwrap. E.g., (A)?? -> A??, (A)+? -> A+?
*/
if (child->nodeType == RPR_PATTERN_VAR &&
- child->min == 1 && child->max == 1 && child->reluctant < 0)
+ child->min == 1 && child->max == 1 && child->reluctant == false)
{
child->min = pattern->min;
child->max = pattern->max;
child->reluctant = pattern->reluctant;
+ child->reluctant_location = pattern->reluctant_location;
return child;
}
@@ -1150,7 +1154,7 @@ fillRPRPatternVar(RPRPatternNode *node, RPRPattern *pat, int *idx, RPRDepth dept
elem->max = (node->max == INT_MAX) ? RPR_QUANTITY_INF : node->max;
elem->next = RPR_ELEMIDX_INVALID;
elem->jump = RPR_ELEMIDX_INVALID;
- if (node->reluctant >= 0)
+ if (node->reluctant)
elem->flags |= RPR_ELEM_RELUCTANT;
(*idx)++;
@@ -1189,7 +1193,7 @@ fillRPRPatternGroup(RPRPatternNode *node, RPRPattern *pat, int *idx, RPRDepth de
elem->max = (node->max == INT_MAX) ? RPR_QUANTITY_INF : node->max;
elem->next = RPR_ELEMIDX_INVALID; /* set by finalize */
elem->jump = RPR_ELEMIDX_INVALID; /* set after END */
- if (node->reluctant >= 0)
+ if (node->reluctant)
elem->flags |= RPR_ELEM_RELUCTANT;
(*idx)++;
groupStartIdx = *idx; /* children start after BEGIN */
@@ -1214,7 +1218,7 @@ fillRPRPatternGroup(RPRPatternNode *node, RPRPattern *pat, int *idx, RPRDepth de
endElem->max = (node->max == INT_MAX) ? RPR_QUANTITY_INF : node->max;
endElem->next = RPR_ELEMIDX_INVALID;
endElem->jump = groupStartIdx; /* loop to first child */
- if (node->reluctant >= 0)
+ if (node->reluctant)
endElem->flags |= RPR_ELEM_RELUCTANT;
/*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index b55a11cc837..f1a71cd036b 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -16967,7 +16967,8 @@ row_pattern_alt:
n->children = list_make2($1, $3);
n->min = 1;
n->max = 1;
- n->reluctant = -1;
+ n->reluctant = false;
+ n->reluctant_location = -1;
n->location = @1;
$$ = (Node *) n;
}
@@ -16994,7 +16995,8 @@ row_pattern_seq:
n->children = list_make2($1, $2);
n->min = 1;
n->max = 1;
- n->reluctant = -1;
+ n->reluctant = false;
+ n->reluctant_location = -1;
n->location = @1;
$$ = (Node *) n;
}
@@ -17010,6 +17012,7 @@ row_pattern_term:
n->min = q->min;
n->max = q->max;
n->reluctant = q->reluctant;
+ n->reluctant_location = q->reluctant_location;
$$ = (Node *) n;
}
;
@@ -17022,7 +17025,8 @@ row_pattern_primary:
n->varName = $1;
n->min = 1;
n->max = 1;
- n->reluctant = -1;
+ n->reluctant = false;
+ n->reluctant_location = -1;
n->children = NIL;
n->location = @1;
$$ = (Node *) n;
@@ -17035,7 +17039,8 @@ row_pattern_primary:
n->children = list_make1(inner);
n->min = 1;
n->max = 1;
- n->reluctant = -1;
+ n->reluctant = false;
+ n->reluctant_location = -1;
n->location = @1;
$$ = (Node *) n;
}
@@ -20400,14 +20405,15 @@ makeRecursiveViewSelect(char *relname, List *aliases, Node *query)
* Create an RPRPatternNode with specified quantifier bounds.
*/
static RPRPatternNode *
-makeRPRQuantifier(int min, int max, ParseLoc reluctant, int location,
+makeRPRQuantifier(int min, int max, ParseLoc reluctant_location, int location,
core_yyscan_t yyscanner)
{
RPRPatternNode *n = makeNode(RPRPatternNode);
n->min = min;
n->max = max;
- n->reluctant = reluctant;
+ n->reluctant = (reluctant_location >= 0);
+ n->reluctant_location = reluctant_location;
n->location = location;
return n;
}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index f5bb81e8c05..928bed2e9fb 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -6778,7 +6778,7 @@ append_pattern_quantifier(StringInfo buf, RPRPatternNode *node)
else
appendStringInfo(buf, "{%d,%d}", node->min, node->max);
- if (node->reluctant >= 0)
+ if (node->reluctant)
{
if (!has_quantifier)
appendStringInfo(buf, "{1}"); /* make reluctant ? unambiguous */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 8af527b57d3..22e856c671d 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -608,8 +608,8 @@ typedef struct RPRPatternNode
RPRPatternNodeType nodeType; /* VAR, SEQ, ALT, GROUP */
int min; /* minimum repetitions (0 for *, ?) */
int max; /* maximum repetitions (INT_MAX for *, +) */
- ParseLoc reluctant; /* location of '?' for reluctant, -1 for
- * greedy */
+ bool reluctant; /* true for reluctant (non-greedy) */
+ ParseLoc reluctant_location; /* location of '?' token, or -1 */
ParseLoc location; /* token location, or -1 */
char *varName; /* VAR: variable name */
List *children; /* SEQ, ALT, GROUP: child nodes */
--
2.50.1 (Apple Git-155)
From 350f3234f318059826a2b2bd96e91e4d7fa22253 Mon Sep 17 00:00:00 2001
From: Henson Choi <[email protected]>
Date: Tue, 3 Mar 2026 17:06:59 +0900
Subject: [PATCH 3/8] Expand RPR test coverage and improve test comments
diff --git a/src/test/regress/expected/rpr.out b/src/test/regress/expected/rpr.out
index 8af44392700..35b90a04492 100644
--- a/src/test/regress/expected/rpr.out
+++ b/src/test/regress/expected/rpr.out
@@ -1,5 +1,10 @@
--
--- Test for row pattern definition clause
+-- Test for row pattern recognition: WINDOW clause integration and
+-- real-world scenario tests using stock price data.
+--
+-- Parser/planner tests: rpr_base.sql
+-- NFA engine tests: rpr_nfa.sql
+-- EXPLAIN statistics tests: rpr_explain.sql
--
CREATE TEMP TABLE stock (
company TEXT,
@@ -51,6 +56,9 @@ SELECT * FROM stock;
company2 | 07-10-2023 | 1300
(20 rows)
+--
+-- Basic pattern matching with PREV/NEXT
+--
-- basic test using PREV
SELECT company, tdate, price, first_value(price) OVER w, last_value(price) OVER w,
nth_value(tdate, 2) OVER w AS nth_second
@@ -426,7 +434,7 @@ SELECT company, tdate, price, first_value(price) OVER w, last_value(price) OVER
company2 | 07-10-2023 | 1300 | |
(20 rows)
--- basic test with none-greedy pattern
+-- basic test with fixed-length pattern (A A A = exactly 3)
SELECT company, tdate, price, count(*) OVER w
FROM stock
WINDOW w AS (
@@ -752,7 +760,9 @@ SELECT company, tdate, price, first_value(price) OVER w, last_value(price) OVER
company2 | 07-10-2023 | 1300 | |
(20 rows)
--- using AFTER MATCH SKIP TO NEXT ROW
+-- using AFTER MATCH SKIP TO NEXT ROW (same pattern as above;
+-- match length is always 2, so result is identical to SKIP PAST LAST ROW.
+-- SKIP TO NEXT ROW's distinct effect is tested in backtracking section.)
SELECT company, tdate, price, first_value(price) OVER w, last_value(price) OVER w
FROM stock
WINDOW w AS (
@@ -789,6 +799,179 @@ SELECT company, tdate, price, first_value(price) OVER w, last_value(price) OVER
company2 | 07-10-2023 | 1300 | |
(20 rows)
+-- PREV returns NULL at partition's first row (null_slot path)
+SELECT company, tdate, price, count(*) OVER w
+FROM stock
+WINDOW w AS (
+ PARTITION BY company
+ ORDER BY tdate
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (BOUNDARY REST+)
+ DEFINE
+ BOUNDARY AS PREV(price) IS NULL,
+ REST AS PREV(price) IS NOT NULL
+);
+ company | tdate | price | count
+----------+------------+-------+-------
+ company1 | 07-01-2023 | 100 | 10
+ company1 | 07-02-2023 | 200 | 0
+ company1 | 07-03-2023 | 150 | 0
+ company1 | 07-04-2023 | 140 | 0
+ company1 | 07-05-2023 | 150 | 0
+ company1 | 07-06-2023 | 90 | 0
+ company1 | 07-07-2023 | 110 | 0
+ company1 | 07-08-2023 | 130 | 0
+ company1 | 07-09-2023 | 120 | 0
+ company1 | 07-10-2023 | 130 | 0
+ company2 | 07-01-2023 | 50 | 10
+ company2 | 07-02-2023 | 2000 | 0
+ company2 | 07-03-2023 | 1500 | 0
+ company2 | 07-04-2023 | 1400 | 0
+ company2 | 07-05-2023 | 1500 | 0
+ company2 | 07-06-2023 | 60 | 0
+ company2 | 07-07-2023 | 1100 | 0
+ company2 | 07-08-2023 | 1300 | 0
+ company2 | 07-09-2023 | 1200 | 0
+ company2 | 07-10-2023 | 1300 | 0
+(20 rows)
+
+-- NEXT returns NULL at partition's last row (null_slot path)
+SELECT company, tdate, price, count(*) OVER w
+FROM stock
+WINDOW w AS (
+ PARTITION BY company
+ ORDER BY tdate
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A+ BOUNDARY)
+ DEFINE
+ A AS NEXT(price) IS NOT NULL,
+ BOUNDARY AS NEXT(price) IS NULL
+);
+ company | tdate | price | count
+----------+------------+-------+-------
+ company1 | 07-01-2023 | 100 | 10
+ company1 | 07-02-2023 | 200 | 0
+ company1 | 07-03-2023 | 150 | 0
+ company1 | 07-04-2023 | 140 | 0
+ company1 | 07-05-2023 | 150 | 0
+ company1 | 07-06-2023 | 90 | 0
+ company1 | 07-07-2023 | 110 | 0
+ company1 | 07-08-2023 | 130 | 0
+ company1 | 07-09-2023 | 120 | 0
+ company1 | 07-10-2023 | 130 | 0
+ company2 | 07-01-2023 | 50 | 10
+ company2 | 07-02-2023 | 2000 | 0
+ company2 | 07-03-2023 | 1500 | 0
+ company2 | 07-04-2023 | 1400 | 0
+ company2 | 07-05-2023 | 1500 | 0
+ company2 | 07-06-2023 | 60 | 0
+ company2 | 07-07-2023 | 1100 | 0
+ company2 | 07-08-2023 | 1300 | 0
+ company2 | 07-09-2023 | 1200 | 0
+ company2 | 07-10-2023 | 1300 | 0
+(20 rows)
+
+-- DESC order: PREV refers to the row with later date
+SELECT company, tdate, price, count(*) OVER w
+FROM stock
+WINDOW w AS (
+ PARTITION BY company
+ ORDER BY tdate DESC
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (START DOWN+ UP+)
+ DEFINE
+ START AS TRUE,
+ DOWN AS price < PREV(price),
+ UP AS price > PREV(price)
+);
+ company | tdate | price | count
+----------+------------+-------+-------
+ company1 | 07-10-2023 | 130 | 3
+ company1 | 07-09-2023 | 120 | 0
+ company1 | 07-08-2023 | 130 | 0
+ company1 | 07-07-2023 | 110 | 3
+ company1 | 07-06-2023 | 90 | 0
+ company1 | 07-05-2023 | 150 | 0
+ company1 | 07-04-2023 | 140 | 0
+ company1 | 07-03-2023 | 150 | 0
+ company1 | 07-02-2023 | 200 | 0
+ company1 | 07-01-2023 | 100 | 0
+ company2 | 07-10-2023 | 1300 | 3
+ company2 | 07-09-2023 | 1200 | 0
+ company2 | 07-08-2023 | 1300 | 0
+ company2 | 07-07-2023 | 1100 | 3
+ company2 | 07-06-2023 | 60 | 0
+ company2 | 07-05-2023 | 1500 | 0
+ company2 | 07-04-2023 | 1400 | 0
+ company2 | 07-03-2023 | 1500 | 0
+ company2 | 07-02-2023 | 2000 | 0
+ company2 | 07-01-2023 | 50 | 0
+(20 rows)
+
+-- Multiple partitions with unequal sizes
+WITH multi_part AS (
+ SELECT * FROM (VALUES
+ ('a', 1, 10), ('a', 2, 20), ('a', 3, 15),
+ ('b', 1, 5),
+ ('c', 1, 100), ('c', 2, 200), ('c', 3, 150), ('c', 4, 140), ('c', 5, 300)
+ ) AS t(grp, id, val)
+)
+SELECT grp, id, val, count(*) OVER w
+FROM multi_part
+WINDOW w AS (
+ PARTITION BY grp
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A B+)
+ DEFINE
+ A AS val <= NEXT(val),
+ B AS val > PREV(val) OR val < PREV(val)
+);
+ grp | id | val | count
+-----+----+-----+-------
+ a | 1 | 10 | 3
+ a | 2 | 20 | 0
+ a | 3 | 15 | 0
+ b | 1 | 5 | 0
+ c | 1 | 100 | 5
+ c | 2 | 200 | 0
+ c | 3 | 150 | 0
+ c | 4 | 140 | 0
+ c | 5 | 300 | 0
+(9 rows)
+
+-- FLOAT/NUMERIC DEFINE conditions
+WITH float_data AS (
+ SELECT * FROM (VALUES
+ (1, 1.0::float8), (2, 1.5), (3, 1.4999), (4, 1.50001), (5, 0.1)
+ ) AS t(id, val)
+)
+SELECT id, val, count(*) OVER w
+FROM float_data
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A B+)
+ DEFINE
+ A AS TRUE,
+ B AS val > PREV(val) * 0.99
+);
+ id | val | count
+----+---------+-------
+ 1 | 1 | 4
+ 2 | 1.5 | 0
+ 3 | 1.4999 | 0
+ 4 | 1.50001 | 0
+ 5 | 0.1 | 0
+(5 rows)
+
+--
+-- SKIP TO / Backtracking / Frame boundary
+--
-- match everything
SELECT company, tdate, price, first_value(price) OVER w, last_value(price) OVER w
FROM stock
@@ -1128,6 +1311,47 @@ DOWN AS price < PREV(price)
company2 | 07-10-2023 | 1300 | | | | | | | 0
(20 rows)
+-- row_number() within RPR reduced frame
+SELECT company, tdate, price, row_number() OVER w, count(*) OVER w
+FROM stock
+WINDOW w AS (
+ PARTITION BY company
+ ORDER BY tdate
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (START UP+ DOWN+)
+ DEFINE
+ START AS TRUE,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+);
+ company | tdate | price | row_number | count
+----------+------------+-------+------------+-------
+ company1 | 07-01-2023 | 100 | 1 | 4
+ company1 | 07-02-2023 | 200 | 2 | 0
+ company1 | 07-03-2023 | 150 | 3 | 0
+ company1 | 07-04-2023 | 140 | 4 | 0
+ company1 | 07-05-2023 | 150 | 5 | 0
+ company1 | 07-06-2023 | 90 | 6 | 4
+ company1 | 07-07-2023 | 110 | 7 | 0
+ company1 | 07-08-2023 | 130 | 8 | 0
+ company1 | 07-09-2023 | 120 | 9 | 0
+ company1 | 07-10-2023 | 130 | 10 | 0
+ company2 | 07-01-2023 | 50 | 1 | 4
+ company2 | 07-02-2023 | 2000 | 2 | 0
+ company2 | 07-03-2023 | 1500 | 3 | 0
+ company2 | 07-04-2023 | 1400 | 4 | 0
+ company2 | 07-05-2023 | 1500 | 5 | 0
+ company2 | 07-06-2023 | 60 | 6 | 4
+ company2 | 07-07-2023 | 1100 | 7 | 0
+ company2 | 07-08-2023 | 1300 | 8 | 0
+ company2 | 07-09-2023 | 1200 | 9 | 0
+ company2 | 07-10-2023 | 1300 | 10 | 0
+(20 rows)
+
+--
+-- SQL Integration: JOIN, CTE, LATERAL
+--
-- JOIN case
CREATE TEMP TABLE t1 (i int, v1 int);
CREATE TEMP TABLE t2 (j int, v2 int);
@@ -1262,6 +1486,9 @@ SELECT id, i, j, count(*) OVER w
1 | 10 | 20 | 0
(10 rows)
+--
+-- Large-scale / scalability tests
+--
-- Smoke test for larger partitions.
WITH s AS (
SELECT v, count(*) OVER w AS c
@@ -1329,7 +1556,7 @@ SELECT match_first, match_last, match_len FROM result WHERE match_len > 0;
(1 row)
--
--- Using IGNORE NULLS
+-- IGNORE NULLS
--
-- no NULL rows case. The result should be identical with "basic test using PREV"
SELECT company, tdate, price, first_value(price) IGNORE NULLS OVER w,
@@ -1371,7 +1598,7 @@ SELECT company, tdate, price, first_value(price) IGNORE NULLS OVER w,
(20 rows)
-- nth_value with IGNORE NULLS option wants to find the second row but
--- due a NULL in the midlle, it returns the third row.
+-- due to a NULL in the middle, it returns the third row.
WITH data AS (
SELECT * FROM (VALUES
(10, 1), (11, NULL), (12, 3), (13, 4)
@@ -1395,8 +1622,8 @@ WITH data AS (
(4 rows)
-- nth_value with IGNORE NULLS option wants to find the third row but
--- due a NULL in the midlle, it reaches the end of reduced frame and
--- return NULL
+-- due to a NULL in the middle, it reaches the end of reduced frame and
+-- returns NULL
WITH data AS (
SELECT * FROM (VALUES
(10, 1), (11, NULL), (12, 3), (13, 4)
@@ -1459,2555 +1686,156 @@ WINDOW w AS (
company2 | 07-10-2023 | 1300 |
(20 rows)
--- View and pg_get_viewdef tests.
-CREATE TEMP VIEW v_window AS
-SELECT company, tdate, price, first_value(price) OVER w, last_value(price) OVER w,
- nth_value(tdate, 2) OVER w AS nth_second
- FROM stock
- WINDOW w AS (
- PARTITION BY company
+-- IGNORE NULLS + first_value where first value in reduced frame is NULL
+WITH data AS (
+ SELECT * FROM (VALUES
+ (1, NULL), (2, NULL), (3, 30), (4, 40)
+ ) AS t(id, val))
+SELECT id, val,
+ first_value(val) IGNORE NULLS OVER w AS fv_ignull,
+ count(*) OVER w
+FROM data
+WINDOW w AS (
+ ORDER BY id
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN (START UP+ DOWN+)
- DEFINE
- START AS TRUE,
- UP AS price > PREV(price),
- DOWN AS price < PREV(price)
-);
-SELECT * FROM v_window;
- company | tdate | price | first_value | last_value | nth_second
-----------+------------+-------+-------------+------------+------------
- company1 | 07-01-2023 | 100 | 100 | 140 | 07-02-2023
- company1 | 07-02-2023 | 200 | | |
- company1 | 07-03-2023 | 150 | | |
- company1 | 07-04-2023 | 140 | | |
- company1 | 07-05-2023 | 150 | | |
- company1 | 07-06-2023 | 90 | 90 | 120 | 07-07-2023
- company1 | 07-07-2023 | 110 | | |
- company1 | 07-08-2023 | 130 | | |
- company1 | 07-09-2023 | 120 | | |
- company1 | 07-10-2023 | 130 | | |
- company2 | 07-01-2023 | 50 | 50 | 1400 | 07-02-2023
- company2 | 07-02-2023 | 2000 | | |
- company2 | 07-03-2023 | 1500 | | |
- company2 | 07-04-2023 | 1400 | | |
- company2 | 07-05-2023 | 1500 | | |
- company2 | 07-06-2023 | 60 | 60 | 1200 | 07-07-2023
- company2 | 07-07-2023 | 1100 | | |
- company2 | 07-08-2023 | 1300 | | |
- company2 | 07-09-2023 | 1200 | | |
- company2 | 07-10-2023 | 1300 | | |
-(20 rows)
-
-SELECT pg_get_viewdef('v_window');
- pg_get_viewdef
----------------------------------------------------------------------------------------
- SELECT company, +
- tdate, +
- price, +
- first_value(price) OVER w AS first_value, +
- last_value(price) OVER w AS last_value, +
- nth_value(tdate, 2) OVER w AS nth_second +
- FROM stock +
- WINDOW w AS (PARTITION BY company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING +
- AFTER MATCH SKIP PAST LAST ROW +
- INITIAL +
- PATTERN (start up+ down+) +
- DEFINE +
- start AS true, +
- up AS (price > prev(price)), +
- down AS (price < prev(price)) );
-(1 row)
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A+)
+ DEFINE A AS TRUE
+);
+ id | val | fv_ignull | count
+----+-----+-----------+-------
+ 1 | | 30 | 4
+ 2 | | | 0
+ 3 | 30 | | 0
+ 4 | 40 | | 0
+(4 rows)
---
--- Pattern optimization tests
--- VIEW shows original pattern, EXPLAIN shows optimized pattern
---
--- Test: duplicate alternatives removal (A | B | A)+ -> (A | B)+
-CREATE TEMP VIEW v_opt_dup AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
+-- IGNORE NULLS + all values NULL in reduced frame
+WITH data AS (
+ SELECT * FROM (VALUES
+ (1, NULL), (2, NULL), (3, NULL)
+ ) AS t(id, val))
+SELECT id, val,
+ first_value(val) IGNORE NULLS OVER w AS fv_ignull,
+ last_value(val) IGNORE NULLS OVER w AS lv_ignull,
+ count(*) OVER w
+FROM data
+WINDOW w AS (
+ ORDER BY id
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN ((A | B | A)+)
- DEFINE
- A AS price > 100,
- B AS price <= 100
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A+)
+ DEFINE A AS TRUE
);
-SELECT pg_get_viewdef('v_opt_dup'); -- original: ((a | b | a)+)
- pg_get_viewdef
----------------------------------------------------------------------------------------
- SELECT company, +
- tdate, +
- price, +
- count(*) OVER w AS count +
- FROM stock +
- WINDOW w AS (PARTITION BY company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING +
- AFTER MATCH SKIP PAST LAST ROW +
- INITIAL +
- PATTERN ((a | b | a)+) +
- DEFINE +
- a AS (price > 100), +
- b AS (price <= 100) );
-(1 row)
+ id | val | fv_ignull | lv_ignull | count
+----+-----+-----------+-----------+-------
+ 1 | | | | 3
+ 2 | | | | 0
+ 3 | | | | 0
+(3 rows)
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_dup; -- optimized: ((a | b)+)
- QUERY PLAN
-----------------------------------------------------------------------------------------------------
- Subquery Scan on v_opt_dup
- -> WindowAgg
- Window: w AS (PARTITION BY stock.company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
- Pattern: (a | b)+
- -> Sort
- Sort Key: stock.company
- -> Seq Scan on stock
-(7 rows)
+--
+-- last_value IGNORE NULLS with reduced frame containing all NULLs
+-- Exercises ignorenulls_getfuncarginframe SEEK_TAIL out-of-frame path
+-- when notnull_relpos >= num_reduced_frame.
+--
+CREATE TEMP TABLE rpr_nullval (id INT, val INT);
+INSERT INTO rpr_nullval VALUES (1, 10), (2, NULL), (3, NULL), (4, 20);
+SELECT id, val,
+ last_value(val) IGNORE NULLS OVER w AS lv_ignull,
+ count(*) OVER w AS cnt
+FROM rpr_nullval
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A B+)
+ DEFINE
+ A AS val IS NOT NULL,
+ B AS val IS NULL
+);
+ id | val | lv_ignull | cnt
+----+-----+-----------+-----
+ 1 | 10 | 10 | 3
+ 2 | | | 0
+ 3 | | | 0
+ 4 | 20 | | 0
+(4 rows)
--- Test: duplicate group removal ((A | B)+ | (A | B)+) -> (A | B)+
-CREATE TEMP VIEW v_opt_dup_group AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN ((A | B)+ | (A | B)+)
- DEFINE
- A AS price > 100,
- B AS price <= 100
+--
+-- NULL handling
+--
+CREATE TEMP TABLE stock_null (company TEXT, tdate DATE, price INTEGER);
+INSERT INTO stock_null VALUES ('c1', '2023-07-01', 100);
+INSERT INTO stock_null VALUES ('c1', '2023-07-02', NULL); -- NULL in middle
+INSERT INTO stock_null VALUES ('c1', '2023-07-03', 200);
+INSERT INTO stock_null VALUES ('c1', '2023-07-04', 150);
+SELECT company, tdate, price, count(*) OVER w AS match_count
+FROM stock_null
+WINDOW w AS (
+ PARTITION BY company
+ ORDER BY tdate
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (START UP DOWN)
+ DEFINE START AS TRUE, UP AS price > PREV(price), DOWN AS price <
+PREV(price)
);
-SELECT pg_get_viewdef('v_opt_dup_group'); -- original: ((a | b)+ | (a | b)+)
- pg_get_viewdef
----------------------------------------------------------------------------------------
- SELECT company, +
- tdate, +
- price, +
- count(*) OVER w AS count +
- FROM stock +
- WINDOW w AS (PARTITION BY company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING +
- AFTER MATCH SKIP PAST LAST ROW +
- INITIAL +
- PATTERN ((a | b)+ | (a | b)+) +
- DEFINE +
- a AS (price > 100), +
- b AS (price <= 100) );
-(1 row)
-
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_dup_group; -- optimized: ((a | b)+)
- QUERY PLAN
-----------------------------------------------------------------------------------------------------
- Subquery Scan on v_opt_dup_group
- -> WindowAgg
- Window: w AS (PARTITION BY stock.company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
- Pattern: (a | b)+
- -> Sort
- Sort Key: stock.company
- -> Seq Scan on stock
-(7 rows)
+ company | tdate | price | match_count
+---------+------------+-------+-------------
+ c1 | 07-01-2023 | 100 | 0
+ c1 | 07-02-2023 | | 0
+ c1 | 07-03-2023 | 200 | 0
+ c1 | 07-04-2023 | 150 | 0
+(4 rows)
--- Test: consecutive vars merge (A A A) -> A{3}
-CREATE TEMP VIEW v_opt_merge AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
+-- Consecutive NULLs: PREV navigates through NULL values
+CREATE TEMP TABLE rpr_consec_null (id INT, val INT);
+INSERT INTO rpr_consec_null VALUES
+ (1, 100), (2, NULL), (3, NULL), (4, NULL), (5, 200), (6, 300);
+-- PREV(val) IS NULL succeeds for both null_slot (first row) and actual NULL
+SELECT id, val, count(*) OVER w AS cnt
+FROM rpr_consec_null
+WINDOW w AS (
+ ORDER BY id
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN (A A A)
- DEFINE
- A AS price >= 140 AND price <= 150
-);
-SELECT pg_get_viewdef('v_opt_merge'); -- original: (a a a)
- pg_get_viewdef
----------------------------------------------------------------------------------------
- SELECT company, +
- tdate, +
- price, +
- count(*) OVER w AS count +
- FROM stock +
- WINDOW w AS (PARTITION BY company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING +
- AFTER MATCH SKIP PAST LAST ROW +
- INITIAL +
- PATTERN (a a a) +
- DEFINE +
- a AS ((price >= 140) AND (price <= 150)) );
-(1 row)
-
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_merge; -- optimized: a{3}
- QUERY PLAN
-----------------------------------------------------------------------------------------------------
- Subquery Scan on v_opt_merge
- -> WindowAgg
- Window: w AS (PARTITION BY stock.company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
- Pattern: a{3}
- -> Sort
- Sort Key: stock.company
- -> Seq Scan on stock
-(7 rows)
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A B+ C)
+ DEFINE
+ A AS val IS NULL,
+ B AS val IS NULL AND PREV(val) IS NULL,
+ C AS val IS NOT NULL
+);
+ id | val | cnt
+----+-----+-----
+ 1 | 100 | 0
+ 2 | | 4
+ 3 | | 0
+ 4 | | 0
+ 5 | 200 | 0
+ 6 | 300 | 0
+(6 rows)
--- Test: quantified vars merge (A A+ A) -> A{3,}
-CREATE TEMP VIEW v_opt_merge_quant AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
+-- NEXT(val) through consecutive NULLs
+SELECT id, val, count(*) OVER w AS cnt
+FROM rpr_consec_null
+WINDOW w AS (
+ ORDER BY id
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN (A A+ A)
- DEFINE
- A AS price > 100
-);
-SELECT pg_get_viewdef('v_opt_merge_quant'); -- original: (a a+ a)
- pg_get_viewdef
----------------------------------------------------------------------------------------
- SELECT company, +
- tdate, +
- price, +
- count(*) OVER w AS count +
- FROM stock +
- WINDOW w AS (PARTITION BY company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING +
- AFTER MATCH SKIP PAST LAST ROW +
- INITIAL +
- PATTERN (a a+ a) +
- DEFINE +
- a AS (price > 100) );
-(1 row)
-
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_merge_quant; -- optimized: a{3,}
- QUERY PLAN
-----------------------------------------------------------------------------------------------------
- Subquery Scan on v_opt_merge_quant
- -> WindowAgg
- Window: w AS (PARTITION BY stock.company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
- Pattern: a{3,}"
- -> Sort
- Sort Key: stock.company
- -> Seq Scan on stock
-(7 rows)
-
--- Test: merge two unbounded (A+ A+) -> A{2,}
-CREATE TEMP VIEW v_opt_merge_unbounded AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN (A+ A+)
- DEFINE
- A AS price > 100
-);
-SELECT pg_get_viewdef('v_opt_merge_unbounded'); -- original: (a+ a+)
- pg_get_viewdef
----------------------------------------------------------------------------------------
- SELECT company, +
- tdate, +
- price, +
- count(*) OVER w AS count +
- FROM stock +
- WINDOW w AS (PARTITION BY company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING +
- AFTER MATCH SKIP PAST LAST ROW +
- INITIAL +
- PATTERN (a+ a+) +
- DEFINE +
- a AS (price > 100) );
-(1 row)
-
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_merge_unbounded; -- optimized: a{2,}
- QUERY PLAN
-----------------------------------------------------------------------------------------------------
- Subquery Scan on v_opt_merge_unbounded
- -> WindowAgg
- Window: w AS (PARTITION BY stock.company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
- Pattern: a{2,}"
- -> Sort
- Sort Key: stock.company
- -> Seq Scan on stock
-(7 rows)
-
--- Test: merge with zero-min (A* A+) -> A+
-CREATE TEMP VIEW v_opt_merge_star AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN (A* A+)
- DEFINE
- A AS price > 100
-);
-SELECT pg_get_viewdef('v_opt_merge_star'); -- original: (a* a+)
- pg_get_viewdef
----------------------------------------------------------------------------------------
- SELECT company, +
- tdate, +
- price, +
- count(*) OVER w AS count +
- FROM stock +
- WINDOW w AS (PARTITION BY company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING +
- AFTER MATCH SKIP PAST LAST ROW +
- INITIAL +
- PATTERN (a* a+) +
- DEFINE +
- a AS (price > 100) );
-(1 row)
-
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_merge_star; -- optimized: a+
- QUERY PLAN
-----------------------------------------------------------------------------------------------------
- Subquery Scan on v_opt_merge_star
- -> WindowAgg
- Window: w AS (PARTITION BY stock.company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
- Pattern: a+"
- -> Sort
- Sort Key: stock.company
- -> Seq Scan on stock
-(7 rows)
-
--- Test: complex merge (A A{2} A+ A{3}) -> A{7,}
-CREATE TEMP VIEW v_opt_merge_complex AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN (A A{2} A+ A{3})
- DEFINE
- A AS price > 100
-);
-SELECT pg_get_viewdef('v_opt_merge_complex'); -- original: (a a{2} a+ a{3})
- pg_get_viewdef
----------------------------------------------------------------------------------------
- SELECT company, +
- tdate, +
- price, +
- count(*) OVER w AS count +
- FROM stock +
- WINDOW w AS (PARTITION BY company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING +
- AFTER MATCH SKIP PAST LAST ROW +
- INITIAL +
- PATTERN (a a{2} a+ a{3}) +
- DEFINE +
- a AS (price > 100) );
-(1 row)
-
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_merge_complex; -- optimized: a{7,}
- QUERY PLAN
-----------------------------------------------------------------------------------------------------
- Subquery Scan on v_opt_merge_complex
- -> WindowAgg
- Window: w AS (PARTITION BY stock.company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
- Pattern: a{7,}"
- -> Sort
- Sort Key: stock.company
- -> Seq Scan on stock
-(7 rows)
-
--- Test: group merge ((A B) (A B)+) -> (A B){2,}
-CREATE TEMP VIEW v_opt_merge_group AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN ((A B) (A B)+)
- DEFINE
- A AS price > 100,
- B AS price <= 100
-);
-SELECT pg_get_viewdef('v_opt_merge_group'); -- original: ((a b) (a b)+)
- pg_get_viewdef
----------------------------------------------------------------------------------------
- SELECT company, +
- tdate, +
- price, +
- count(*) OVER w AS count +
- FROM stock +
- WINDOW w AS (PARTITION BY company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING +
- AFTER MATCH SKIP PAST LAST ROW +
- INITIAL +
- PATTERN ((a b) (a b)+) +
- DEFINE +
- a AS (price > 100), +
- b AS (price <= 100) );
-(1 row)
-
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_merge_group; -- expected: (a b){2,}
- QUERY PLAN
-----------------------------------------------------------------------------------------------------
- Subquery Scan on v_opt_merge_group
- -> WindowAgg
- Window: w AS (PARTITION BY stock.company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
- Pattern: (a' b'){2,}"
- -> Sort
- Sort Key: stock.company
- -> Seq Scan on stock
-(7 rows)
-
--- Test: group merge A B (A B)+ -> (A B){2,}
-CREATE TEMP VIEW v_opt_merge_group2 AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN (A B (A B)+)
- DEFINE
- A AS price > 100,
- B AS price <= 100
-);
-SELECT pg_get_viewdef('v_opt_merge_group2'); -- original: (a b (a b)+)
- pg_get_viewdef
----------------------------------------------------------------------------------------
- SELECT company, +
- tdate, +
- price, +
- count(*) OVER w AS count +
- FROM stock +
- WINDOW w AS (PARTITION BY company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING +
- AFTER MATCH SKIP PAST LAST ROW +
- INITIAL +
- PATTERN (a b (a b)+) +
- DEFINE +
- a AS (price > 100), +
- b AS (price <= 100) );
-(1 row)
-
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_merge_group2; -- expected: (a b){2,}
- QUERY PLAN
-----------------------------------------------------------------------------------------------------
- Subquery Scan on v_opt_merge_group2
- -> WindowAgg
- Window: w AS (PARTITION BY stock.company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
- Pattern: (a' b'){2,}"
- -> Sort
- Sort Key: stock.company
- -> Seq Scan on stock
-(7 rows)
-
--- Test: group merge (A B) (A B)+ (A B) -> (A B){3,}
-CREATE TEMP VIEW v_opt_merge_group3 AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN ((A B) (A B)+ (A B))
- DEFINE
- A AS price > 100,
- B AS price <= 100
-);
-SELECT pg_get_viewdef('v_opt_merge_group3'); -- original: ((a b) (a b)+ (a b))
- pg_get_viewdef
----------------------------------------------------------------------------------------
- SELECT company, +
- tdate, +
- price, +
- count(*) OVER w AS count +
- FROM stock +
- WINDOW w AS (PARTITION BY company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING +
- AFTER MATCH SKIP PAST LAST ROW +
- INITIAL +
- PATTERN ((a b) (a b)+ (a b)) +
- DEFINE +
- a AS (price > 100), +
- b AS (price <= 100) );
-(1 row)
-
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_merge_group3; -- expected: (a b){3,}
- QUERY PLAN
-----------------------------------------------------------------------------------------------------
- Subquery Scan on v_opt_merge_group3
- -> WindowAgg
- Window: w AS (PARTITION BY stock.company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
- Pattern: (a' b'){3,}"
- -> Sort
- Sort Key: stock.company
- -> Seq Scan on stock
-(7 rows)
-
--- Test: group merge A B A B (A B)+ A B A B -> (A B){5,}
-CREATE TEMP VIEW v_opt_merge_group4 AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN (A B A B (A B)+ A B A B)
- DEFINE
- A AS price > 100,
- B AS price <= 100
-);
-SELECT pg_get_viewdef('v_opt_merge_group4'); -- original: (a b a b (a b)+ a b a b)
- pg_get_viewdef
----------------------------------------------------------------------------------------
- SELECT company, +
- tdate, +
- price, +
- count(*) OVER w AS count +
- FROM stock +
- WINDOW w AS (PARTITION BY company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING +
- AFTER MATCH SKIP PAST LAST ROW +
- INITIAL +
- PATTERN (a b a b (a b)+ a b a b) +
- DEFINE +
- a AS (price > 100), +
- b AS (price <= 100) );
-(1 row)
-
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_merge_group4; -- expected: (a b){5,}
- QUERY PLAN
-----------------------------------------------------------------------------------------------------
- Subquery Scan on v_opt_merge_group4
- -> WindowAgg
- Window: w AS (PARTITION BY stock.company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
- Pattern: (a' b'){5,}"
- -> Sort
- Sort Key: stock.company
- -> Seq Scan on stock
-(7 rows)
-
--- Test: group merge C A B (A B)+ A B C -> C (A B){3,} C
-CREATE TEMP VIEW v_opt_merge_group5 AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN (C A B (A B)+ A B C)
- DEFINE
- A AS price > 100,
- B AS price <= 100,
- C AS price > 200
-);
-SELECT pg_get_viewdef('v_opt_merge_group5'); -- original: (c a b (a b)+ a b c)
- pg_get_viewdef
----------------------------------------------------------------------------------------
- SELECT company, +
- tdate, +
- price, +
- count(*) OVER w AS count +
- FROM stock +
- WINDOW w AS (PARTITION BY company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING +
- AFTER MATCH SKIP PAST LAST ROW +
- INITIAL +
- PATTERN (c a b (a b)+ a b c) +
- DEFINE +
- a AS (price > 100), +
- b AS (price <= 100), +
- c AS (price > 200) );
-(1 row)
-
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_merge_group5; -- expected: c (a b){3,} c
- QUERY PLAN
-----------------------------------------------------------------------------------------------------
- Subquery Scan on v_opt_merge_group5
- -> WindowAgg
- Window: w AS (PARTITION BY stock.company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
- Pattern: c (a b){3,} c
- -> Sort
- Sort Key: stock.company
- -> Seq Scan on stock
-(7 rows)
-
--- Test: consecutive GROUP merge (A B)+ (A B)+ -> (A B){2,}
-CREATE TEMP VIEW v_opt_merge_consec_group AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN ((A B)+ (A B)+)
- DEFINE
- A AS price > 100,
- B AS price <= 100
-);
-SELECT pg_get_viewdef('v_opt_merge_consec_group'); -- original: ((a b)+ (a b)+)
- pg_get_viewdef
----------------------------------------------------------------------------------------
- SELECT company, +
- tdate, +
- price, +
- count(*) OVER w AS count +
- FROM stock +
- WINDOW w AS (PARTITION BY company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING +
- AFTER MATCH SKIP PAST LAST ROW +
- INITIAL +
- PATTERN ((a b)+ (a b)+) +
- DEFINE +
- a AS (price > 100), +
- b AS (price <= 100) );
-(1 row)
-
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_merge_consec_group; -- expected: (a b){2,}
- QUERY PLAN
-----------------------------------------------------------------------------------------------------
- Subquery Scan on v_opt_merge_consec_group
- -> WindowAgg
- Window: w AS (PARTITION BY stock.company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
- Pattern: (a' b'){2,}"
- -> Sort
- Sort Key: stock.company
- -> Seq Scan on stock
-(7 rows)
-
--- Test: consecutive GROUP merge with different quantifiers (A B){2} (A B){3} -> (A B){5}
-CREATE TEMP VIEW v_opt_merge_consec_group2 AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN ((A B){2} (A B){3})
- DEFINE
- A AS price > 100,
- B AS price <= 100
-);
-SELECT pg_get_viewdef('v_opt_merge_consec_group2'); -- original: ((a b){2} (a b){3})
- pg_get_viewdef
----------------------------------------------------------------------------------------
- SELECT company, +
- tdate, +
- price, +
- count(*) OVER w AS count +
- FROM stock +
- WINDOW w AS (PARTITION BY company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING +
- AFTER MATCH SKIP PAST LAST ROW +
- INITIAL +
- PATTERN ((a b){2} (a b){3}) +
- DEFINE +
- a AS (price > 100), +
- b AS (price <= 100) );
-(1 row)
-
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_merge_consec_group2; -- expected: (a b){5}
- QUERY PLAN
-----------------------------------------------------------------------------------------------------
- Subquery Scan on v_opt_merge_consec_group2
- -> WindowAgg
- Window: w AS (PARTITION BY stock.company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
- Pattern: (a b){5}
- -> Sort
- Sort Key: stock.company
- -> Seq Scan on stock
-(7 rows)
-
--- Test {n} quantifier display
-CREATE TEMP VIEW v_quantifier_n AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN (A{3})
- DEFINE
- A AS price > 100
-);
-SELECT pg_get_viewdef('v_quantifier_n');
- pg_get_viewdef
----------------------------------------------------------------------------------------
- SELECT company, +
- tdate, +
- price, +
- count(*) OVER w AS count +
- FROM stock +
- WINDOW w AS (PARTITION BY company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING +
- AFTER MATCH SKIP PAST LAST ROW +
- INITIAL +
- PATTERN (a{3}) +
- DEFINE +
- a AS (price > 100) );
-(1 row)
-
--- Test {n,} quantifier display
-CREATE TEMP VIEW v_quantifier_n_plus AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN (A{2,})
- DEFINE
- A AS price > 100
-);
-SELECT pg_get_viewdef('v_quantifier_n_plus');
- pg_get_viewdef
----------------------------------------------------------------------------------------
- SELECT company, +
- tdate, +
- price, +
- count(*) OVER w AS count +
- FROM stock +
- WINDOW w AS (PARTITION BY company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING +
- AFTER MATCH SKIP PAST LAST ROW +
- INITIAL +
- PATTERN (a{2,}) +
- DEFINE +
- a AS (price > 100) );
-(1 row)
-
--- Test: flatten nested SEQ (A (B C)) -> A B C
-CREATE TEMP VIEW v_opt_flatten_seq AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN (A (B C))
- DEFINE
- A AS price > 100,
- B AS price > 150,
- C AS price < 150
-);
-SELECT pg_get_viewdef('v_opt_flatten_seq'); -- original: (a (b c))
- pg_get_viewdef
----------------------------------------------------------------------------------------
- SELECT company, +
- tdate, +
- price, +
- count(*) OVER w AS count +
- FROM stock +
- WINDOW w AS (PARTITION BY company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING +
- AFTER MATCH SKIP PAST LAST ROW +
- INITIAL +
- PATTERN (a (b c)) +
- DEFINE +
- a AS (price > 100), +
- b AS (price > 150), +
- c AS (price < 150) );
-(1 row)
-
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_flatten_seq; -- optimized: a b c
- QUERY PLAN
-----------------------------------------------------------------------------------------------------
- Subquery Scan on v_opt_flatten_seq
- -> WindowAgg
- Window: w AS (PARTITION BY stock.company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
- Pattern: a b c
- -> Sort
- Sort Key: stock.company
- -> Seq Scan on stock
-(7 rows)
-
--- Test: flatten nested ALT (A | (B | C)) -> (A | B | C)
-CREATE TEMP VIEW v_opt_flatten_alt AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN ((A | (B | C))+)
- DEFINE
- A AS price > 200,
- B AS price > 100,
- C AS price <= 100
-);
-SELECT pg_get_viewdef('v_opt_flatten_alt'); -- original: ((a | (b | c))+)
- pg_get_viewdef
----------------------------------------------------------------------------------------
- SELECT company, +
- tdate, +
- price, +
- count(*) OVER w AS count +
- FROM stock +
- WINDOW w AS (PARTITION BY company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING +
- AFTER MATCH SKIP PAST LAST ROW +
- INITIAL +
- PATTERN ((a | (b | c))+) +
- DEFINE +
- a AS (price > 200), +
- b AS (price > 100), +
- c AS (price <= 100) );
-(1 row)
-
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_flatten_alt; -- optimized: ((a | b | c))+
- QUERY PLAN
-----------------------------------------------------------------------------------------------------
- Subquery Scan on v_opt_flatten_alt
- -> WindowAgg
- Window: w AS (PARTITION BY stock.company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
- Pattern: (a | b | c)+
- -> Sort
- Sort Key: stock.company
- -> Seq Scan on stock
-(7 rows)
-
--- Test: unwrap GROUP{1,1} ((A)) -> A
-CREATE TEMP VIEW v_opt_unwrap_group AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN (((A)))
- DEFINE
- A AS price > 100
-);
-SELECT pg_get_viewdef('v_opt_unwrap_group'); -- original: (((a)))
- pg_get_viewdef
----------------------------------------------------------------------------------------
- SELECT company, +
- tdate, +
- price, +
- count(*) OVER w AS count +
- FROM stock +
- WINDOW w AS (PARTITION BY company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING +
- AFTER MATCH SKIP PAST LAST ROW +
- INITIAL +
- PATTERN (((a))) +
- DEFINE +
- a AS (price > 100) );
-(1 row)
-
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_unwrap_group; -- optimized: a
- QUERY PLAN
-----------------------------------------------------------------------------------------------------
- Subquery Scan on v_opt_unwrap_group
- -> WindowAgg
- Window: w AS (PARTITION BY stock.company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
- Pattern: a
- -> Sort
- Sort Key: stock.company
- -> Seq Scan on stock
-(7 rows)
-
--- Test: quantifier multiplication (A{2}){3} -> A{6}
-CREATE TEMP VIEW v_opt_quant_mult AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN ((A{2}){3})
- DEFINE
- A AS price > 100
-);
-SELECT pg_get_viewdef('v_opt_quant_mult'); -- original: ((a{2}){3})
- pg_get_viewdef
----------------------------------------------------------------------------------------
- SELECT company, +
- tdate, +
- price, +
- count(*) OVER w AS count +
- FROM stock +
- WINDOW w AS (PARTITION BY company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING +
- AFTER MATCH SKIP PAST LAST ROW +
- INITIAL +
- PATTERN ((a{2}){3}) +
- DEFINE +
- a AS (price > 100) );
-(1 row)
-
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_quant_mult; -- optimized: a{6}
- QUERY PLAN
-----------------------------------------------------------------------------------------------------
- Subquery Scan on v_opt_quant_mult
- -> WindowAgg
- Window: w AS (PARTITION BY stock.company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
- Pattern: a{6}
- -> Sort
- Sort Key: stock.company
- -> Seq Scan on stock
-(7 rows)
-
--- Test: quantifier multiplication (A{2,4}){3} -> A{6,12}
-CREATE TEMP VIEW v_opt_quant_mult_range AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN ((A{2,4}){3})
- DEFINE
- A AS price > 100
-);
-SELECT pg_get_viewdef('v_opt_quant_mult_range'); -- original: ((a{2,4}){3})
- pg_get_viewdef
----------------------------------------------------------------------------------------
- SELECT company, +
- tdate, +
- price, +
- count(*) OVER w AS count +
- FROM stock +
- WINDOW w AS (PARTITION BY company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING +
- AFTER MATCH SKIP PAST LAST ROW +
- INITIAL +
- PATTERN ((a{2,4}){3}) +
- DEFINE +
- a AS (price > 100) );
-(1 row)
-
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_quant_mult_range; -- optimized: a{6,12}
- QUERY PLAN
-----------------------------------------------------------------------------------------------------
- Subquery Scan on v_opt_quant_mult_range
- -> WindowAgg
- Window: w AS (PARTITION BY stock.company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
- Pattern: a{6,12}
- -> Sort
- Sort Key: stock.company
- -> Seq Scan on stock
-(7 rows)
-
--- Test: quantifier multiplication blocked (A{2}){3,5} -> no change
--- outer range with child exact > 1 causes gaps (6,8,10 not 6,7,8,9,10)
-CREATE TEMP VIEW v_opt_quant_mult_range2 AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN ((A{2}){3,5})
- DEFINE
- A AS price > 100
-);
-SELECT pg_get_viewdef('v_opt_quant_mult_range2'); -- original: ((a{2}){3,5})
- pg_get_viewdef
----------------------------------------------------------------------------------------
- SELECT company, +
- tdate, +
- price, +
- count(*) OVER w AS count +
- FROM stock +
- WINDOW w AS (PARTITION BY company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING +
- AFTER MATCH SKIP PAST LAST ROW +
- INITIAL +
- PATTERN ((a{2}){3,5}) +
- DEFINE +
- a AS (price > 100) );
-(1 row)
-
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_quant_mult_range2; -- NOT optimized: (a{2}){3,5}
- QUERY PLAN
-----------------------------------------------------------------------------------------------------
- Subquery Scan on v_opt_quant_mult_range2
- -> WindowAgg
- Window: w AS (PARTITION BY stock.company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
- Pattern: (a{2}){3,5}
- -> Sort
- Sort Key: stock.company
- -> Seq Scan on stock
-(7 rows)
-
--- Test: quantifier multiplication blocked by INF (A+){3} -> no change
-CREATE TEMP VIEW v_opt_quant_mult_inf AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN ((A+){3})
- DEFINE
- A AS price > 100
-);
-SELECT pg_get_viewdef('v_opt_quant_mult_inf'); -- original: ((a+){3})
- pg_get_viewdef
----------------------------------------------------------------------------------------
- SELECT company, +
- tdate, +
- price, +
- count(*) OVER w AS count +
- FROM stock +
- WINDOW w AS (PARTITION BY company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING +
- AFTER MATCH SKIP PAST LAST ROW +
- INITIAL +
- PATTERN ((a+){3}) +
- DEFINE +
- a AS (price > 100) );
-(1 row)
-
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_quant_mult_inf; -- no multiply: (a+){3}
- QUERY PLAN
-----------------------------------------------------------------------------------------------------
- Subquery Scan on v_opt_quant_mult_inf
- -> WindowAgg
- Window: w AS (PARTITION BY stock.company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
- Pattern: (a+"){3}
- -> Sort
- Sort Key: stock.company
- -> Seq Scan on stock
-(7 rows)
-
--- Test: unwrap single-item ALT after duplicate removal (A | A) -> A
-CREATE TEMP VIEW v_opt_unwrap_alt AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN ((A | A)+)
- DEFINE
- A AS price > 100
-);
-SELECT pg_get_viewdef('v_opt_unwrap_alt'); -- original: ((a | a)+)
- pg_get_viewdef
----------------------------------------------------------------------------------------
- SELECT company, +
- tdate, +
- price, +
- count(*) OVER w AS count +
- FROM stock +
- WINDOW w AS (PARTITION BY company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING +
- AFTER MATCH SKIP PAST LAST ROW +
- INITIAL +
- PATTERN ((a | a)+) +
- DEFINE +
- a AS (price > 100) );
-(1 row)
-
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_unwrap_alt; -- optimized: a+
- QUERY PLAN
-----------------------------------------------------------------------------------------------------
- Subquery Scan on v_opt_unwrap_alt
- -> WindowAgg
- Window: w AS (PARTITION BY stock.company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
- Pattern: a+"
- -> Sort
- Sort Key: stock.company
- -> Seq Scan on stock
-(7 rows)
-
--- Test: GROUP{1,1} to SEQ with flatten ((A B)(C D)) -> A B C D
-CREATE TEMP VIEW v_opt_group_to_seq AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN (((A B)(C D)))
- DEFINE
- A AS price > 200,
- B AS price > 150,
- C AS price > 100,
- D AS price <= 100
-);
-SELECT pg_get_viewdef('v_opt_group_to_seq'); -- original: (((a b)(c d)))
- pg_get_viewdef
----------------------------------------------------------------------------------------
- SELECT company, +
- tdate, +
- price, +
- count(*) OVER w AS count +
- FROM stock +
- WINDOW w AS (PARTITION BY company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING +
- AFTER MATCH SKIP PAST LAST ROW +
- INITIAL +
- PATTERN (((a b) (c d))) +
- DEFINE +
- a AS (price > 200), +
- b AS (price > 150), +
- c AS (price > 100), +
- d AS (price <= 100) );
-(1 row)
-
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_group_to_seq; -- optimized: a b c d
- QUERY PLAN
-----------------------------------------------------------------------------------------------------
- Subquery Scan on v_opt_group_to_seq
- -> WindowAgg
- Window: w AS (PARTITION BY stock.company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
- Pattern: a b c d
- -> Sort
- Sort Key: stock.company
- -> Seq Scan on stock
-(7 rows)
-
--- Test: combined consecutive GROUP + prefix merge A B (A B)+ (A B)+ -> (A B){3,}
-CREATE TEMP VIEW v_opt_combined_merge AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN (A B (A B)+ (A B)+)
- DEFINE
- A AS price > 100,
- B AS price <= 100
-);
-SELECT pg_get_viewdef('v_opt_combined_merge'); -- original: (a b (a b)+ (a b)+)
- pg_get_viewdef
----------------------------------------------------------------------------------------
- SELECT company, +
- tdate, +
- price, +
- count(*) OVER w AS count +
- FROM stock +
- WINDOW w AS (PARTITION BY company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING +
- AFTER MATCH SKIP PAST LAST ROW +
- INITIAL +
- PATTERN (a b (a b)+ (a b)+) +
- DEFINE +
- a AS (price > 100), +
- b AS (price <= 100) );
-(1 row)
-
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_combined_merge; -- expected: (a b){3,}
- QUERY PLAN
-----------------------------------------------------------------------------------------------------
- Subquery Scan on v_opt_combined_merge
- -> WindowAgg
- Window: w AS (PARTITION BY stock.company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
- Pattern: (a' b'){3,}"
- -> Sort
- Sort Key: stock.company
- -> Seq Scan on stock
-(7 rows)
-
--- Test: nested ALT pattern - bug reproduction
-CREATE TEMP VIEW v_opt_nested_alt AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN (((A B) | C) D | A B C)
- DEFINE
- A AS price <= 100,
- B AS price <= 150,
- C AS price <= 200,
- D AS price > 200
-);
-SELECT pg_get_viewdef('v_opt_nested_alt');
- pg_get_viewdef
----------------------------------------------------------------------------------------
- SELECT company, +
- tdate, +
- price, +
- count(*) OVER w AS count +
- FROM stock +
- WINDOW w AS (PARTITION BY company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING +
- AFTER MATCH SKIP PAST LAST ROW +
- INITIAL +
- PATTERN (((a b) | c) d | a b c) +
- DEFINE +
- a AS (price <= 100), +
- b AS (price <= 150), +
- c AS (price <= 200), +
- d AS (price > 200) );
-(1 row)
-
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_nested_alt;
- QUERY PLAN
-----------------------------------------------------------------------------------------------------
- Subquery Scan on v_opt_nested_alt
- -> WindowAgg
- Window: w AS (PARTITION BY stock.company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
- Pattern: ((a b | c) d | a b c)
- -> Sort
- Sort Key: stock.company
- -> Seq Scan on stock
-(7 rows)
-
--- Test: nested ALT with unbounded - A+ inside
-CREATE TEMP VIEW v_opt_nested_alt2 AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN (((A+ B) | C) D | A B C)
- DEFINE
- A AS price <= 100,
- B AS price <= 150,
- C AS price <= 200,
- D AS price > 200
-);
-SELECT pg_get_viewdef('v_opt_nested_alt2');
- pg_get_viewdef
----------------------------------------------------------------------------------------
- SELECT company, +
- tdate, +
- price, +
- count(*) OVER w AS count +
- FROM stock +
- WINDOW w AS (PARTITION BY company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING +
- AFTER MATCH SKIP PAST LAST ROW +
- INITIAL +
- PATTERN (((a+ b) | c) d | a b c) +
- DEFINE +
- a AS (price <= 100), +
- b AS (price <= 150), +
- c AS (price <= 200), +
- d AS (price > 200) );
-(1 row)
-
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_nested_alt2;
- QUERY PLAN
-----------------------------------------------------------------------------------------------------
- Subquery Scan on v_opt_nested_alt2
- -> WindowAgg
- Window: w AS (PARTITION BY stock.company ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
- Pattern: ((a+" b | c) d | a b c)
- -> Sort
- Sort Key: stock.company
- -> Seq Scan on stock
-(7 rows)
-
---
--- Error cases
---
--- row pattern definition variable name must not appear more than once
-SELECT company, tdate, price, first_value(price) OVER w, last_value(price) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ORDER BY tdate
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN (START UP+ DOWN+)
- DEFINE
- START AS TRUE,
- UP AS price > PREV(price),
- DOWN AS price < PREV(price),
- UP AS price > PREV(price)
-);
-ERROR: row pattern definition variable name "up" appears more than once in DEFINE clause
-LINE 11: UP AS price > PREV(price),
- ^
--- subqueries in DEFINE clause are not supported
-SELECT company, tdate, price, first_value(price) OVER w, last_value(price) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ORDER BY tdate
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN (START LOWPRICE)
- DEFINE
- START AS TRUE,
- LOWPRICE AS price < (SELECT 100)
-);
-ERROR: cannot use subquery in DEFINE expression
-LINE 11: LOWPRICE AS price < (SELECT 100)
- ^
--- aggregates in DEFINE clause are not supported
-SELECT company, tdate, price, first_value(price) OVER w, last_value(price) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ORDER BY tdate
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN (START LOWPRICE)
- DEFINE
- START AS TRUE,
- LOWPRICE AS price < count(*)
-);
-ERROR: aggregate functions are not allowed in DEFINE
-LINE 11: LOWPRICE AS price < count(*)
- ^
--- FRAME must start at current row when row pattern recognition is used
-SELECT company, tdate, price, first_value(price) OVER w, last_value(price) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ORDER BY tdate
- ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN (START UP+ DOWN+)
- DEFINE
- START AS TRUE,
- UP AS price > PREV(price),
- DOWN AS price < PREV(price)
-);
-ERROR: FRAME must start at CURRENT ROW when row pattern recognition is used
-LINE 6: ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
- ^
-DETAIL: Current frame starts with UNBOUNDED PRECEDING.
-HINT: Use: ROWS BETWEEN CURRENT ROW AND ...
--- EXCLUDE is not permitted
-SELECT company, tdate, price, first_value(price) OVER w, last_value(price) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ORDER BY tdate
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- EXCLUDE CURRENT ROW
- INITIAL
- PATTERN (START UP+ DOWN+)
- DEFINE
- START AS TRUE,
- UP AS price > PREV(price),
- DOWN AS price < PREV(price)
-);
-ERROR: EXCLUDE options are not permitted when row pattern recognition is used
-LINE 7: EXCLUDE CURRENT ROW
- ^
-DETAIL: Frame definition includes EXCLUDE CURRENT ROW.
-HINT: Remove the EXCLUDE clause from the window definition.
--- SEEK is not supported
-SELECT company, tdate, price, first_value(price) OVER w, last_value(price) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ORDER BY tdate
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP TO NEXT ROW
- SEEK
- PATTERN (START UP+ DOWN+)
- DEFINE
- START AS TRUE,
- UP AS price > PREV(price),
- DOWN AS price < PREV(price)
-);
-ERROR: SEEK is not supported
-LINE 8: SEEK
- ^
-HINT: Use INITIAL instead.
--- PREV's argument must have at least 1 column reference
-SELECT company, tdate, price, first_value(price) OVER w, last_value(price) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ORDER BY tdate
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP TO NEXT ROW
- INITIAL
- PATTERN (START UP+ DOWN+)
- DEFINE
- START AS TRUE,
- UP AS price > PREV(1),
- DOWN AS price < PREV(1)
-);
-ERROR: row pattern navigation operation's argument must include at least one column reference
--- Unsupported quantifier
-SELECT company, tdate, price, first_value(price) OVER w, last_value(price) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ORDER BY tdate
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP TO NEXT ROW
- INITIAL
- PATTERN (START UP~ DOWN+)
- DEFINE
- START AS TRUE,
- UP AS price > PREV(1),
- DOWN AS price < PREV(1)
-);
-ERROR: unsupported quantifier "~"
-LINE 9: PATTERN (START UP~ DOWN+)
- ^
-HINT: Valid quantifiers are: *, +, ?, *?, +?, ??, {n}, {n,}, {,m}, {n,m} and their reluctant versions.
--- PREV(1) missing column reference (error)
-SELECT company, tdate, price, first_value(price) OVER w, last_value(price) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ORDER BY tdate
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP TO NEXT ROW
- INITIAL
- PATTERN (START UP+? DOWN+)
- DEFINE
- START AS TRUE,
- UP AS price > PREV(1),
- DOWN AS price < PREV(1)
-);
-ERROR: row pattern navigation operation's argument must include at least one column reference
--- Maximum pattern variables is 251 (RPR_VARID_MAX)
--- Error: 252 variables exceeds limit of 251
-DO $$
-DECLARE
- pattern_vars text;
- define_vars text;
- query text;
-BEGIN
- SELECT string_agg('v' || lpad(i::text, 3, '0'), ' '),
- string_agg('v' || lpad(i::text, 3, '0') || ' AS TRUE', ', ')
- INTO pattern_vars, define_vars
- FROM generate_series(1, 252) i;
-
- query := format('SELECT * FROM (SELECT 1 AS x) t WINDOW w AS (
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- PATTERN (%s)
- DEFINE %s)', pattern_vars, define_vars);
-
- EXECUTE query;
-END;
-$$;
-ERROR: too many pattern variables
-DETAIL: Maximum is 251.
-CONTEXT: SQL statement "SELECT * FROM (SELECT 1 AS x) t WINDOW w AS (
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- PATTERN (v001 v002 v003 v004 v005 v006 v007 v008 v009 v010 v011 v012 v013 v014 v015 v016 v017 v018 v019 v020 v021 v022 v023 v024 v025 v026 v027 v028 v029 v030 v031 v032 v033 v034 v035 v036 v037 v038 v039 v040 v041 v042 v043 v044 v045 v046 v047 v048 v049 v050 v051 v052 v053 v054 v055 v056 v057 v058 v059 v060 v061 v062 v063 v064 v065 v066 v067 v068 v069 v070 v071 v072 v073 v074 v075 v076 v077 v078 v079 v080 v081 v082 v083 v084 v085 v086 v087 v088 v089 v090 v091 v092 v093 v094 v095 v096 v097 v098 v099 v100 v101 v102 v103 v104 v105 v106 v107 v108 v109 v110 v111 v112 v113 v114 v115 v116 v117 v118 v119 v120 v121 v122 v123 v124 v125 v126 v127 v128 v129 v130 v131 v132 v133 v134 v135 v136 v137 v138 v139 v140 v141 v142 v143 v144 v145 v146 v147 v148 v149 v150 v151 v152 v153 v154 v155 v156 v157 v158 v159 v160 v161 v162 v163 v164 v165 v166 v167 v168 v169 v170 v171 v172 v173 v174 v175 v176 v177 v178 v179 v180 v181 v182 v183 v184 v185 v186 v187 v188 v189 v190 v191 v192 v193 v194 v195 v196 v197 v198 v199 v200 v201 v202 v203 v204 v205 v206 v207 v208 v209 v210 v211 v212 v213 v214 v215 v216 v217 v218 v219 v220 v221 v222 v223 v224 v225 v226 v227 v228 v229 v230 v231 v232 v233 v234 v235 v236 v237 v238 v239 v240 v241 v242 v243 v244 v245 v246 v247 v248 v249 v250 v251 v252)
- DEFINE v001 AS TRUE, v002 AS TRUE, v003 AS TRUE, v004 AS TRUE, v005 AS TRUE, v006 AS TRUE, v007 AS TRUE, v008 AS TRUE, v009 AS TRUE, v010 AS TRUE, v011 AS TRUE, v012 AS TRUE, v013 AS TRUE, v014 AS TRUE, v015 AS TRUE, v016 AS TRUE, v017 AS TRUE, v018 AS TRUE, v019 AS TRUE, v020 AS TRUE, v021 AS TRUE, v022 AS TRUE, v023 AS TRUE, v024 AS TRUE, v025 AS TRUE, v026 AS TRUE, v027 AS TRUE, v028 AS TRUE, v029 AS TRUE, v030 AS TRUE, v031 AS TRUE, v032 AS TRUE, v033 AS TRUE, v034 AS TRUE, v035 AS TRUE, v036 AS TRUE, v037 AS TRUE, v038 AS TRUE, v039 AS TRUE, v040 AS TRUE, v041 AS TRUE, v042 AS TRUE, v043 AS TRUE, v044 AS TRUE, v045 AS TRUE, v046 AS TRUE, v047 AS TRUE, v048 AS TRUE, v049 AS TRUE, v050 AS TRUE, v051 AS TRUE, v052 AS TRUE, v053 AS TRUE, v054 AS TRUE, v055 AS TRUE, v056 AS TRUE, v057 AS TRUE, v058 AS TRUE, v059 AS TRUE, v060 AS TRUE, v061 AS TRUE, v062 AS TRUE, v063 AS TRUE, v064 AS TRUE, v065 AS TRUE, v066 AS TRUE, v067 AS TRUE, v068 AS TRUE, v069 AS TRUE, v070 AS TRUE, v071 AS TRUE, v072 AS TRUE, v073 AS TRUE, v074 AS TRUE, v075 AS TRUE, v076 AS TRUE, v077 AS TRUE, v078 AS TRUE, v079 AS TRUE, v080 AS TRUE, v081 AS TRUE, v082 AS TRUE, v083 AS TRUE, v084 AS TRUE, v085 AS TRUE, v086 AS TRUE, v087 AS TRUE, v088 AS TRUE, v089 AS TRUE, v090 AS TRUE, v091 AS TRUE, v092 AS TRUE, v093 AS TRUE, v094 AS TRUE, v095 AS TRUE, v096 AS TRUE, v097 AS TRUE, v098 AS TRUE, v099 AS TRUE, v100 AS TRUE, v101 AS TRUE, v102 AS TRUE, v103 AS TRUE, v104 AS TRUE, v105 AS TRUE, v106 AS TRUE, v107 AS TRUE, v108 AS TRUE, v109 AS TRUE, v110 AS TRUE, v111 AS TRUE, v112 AS TRUE, v113 AS TRUE, v114 AS TRUE, v115 AS TRUE, v116 AS TRUE, v117 AS TRUE, v118 AS TRUE, v119 AS TRUE, v120 AS TRUE, v121 AS TRUE, v122 AS TRUE, v123 AS TRUE, v124 AS TRUE, v125 AS TRUE, v126 AS TRUE, v127 AS TRUE, v128 AS TRUE, v129 AS TRUE, v130 AS TRUE, v131 AS TRUE, v132 AS TRUE, v133 AS TRUE, v134 AS TRUE, v135 AS TRUE, v136 AS TRUE, v137 AS TRUE, v138 AS TRUE, v139 AS TRUE, v140 AS TRUE, v141 AS TRUE, v142 AS TRUE, v143 AS TRUE, v144 AS TRUE, v145 AS TRUE, v146 AS TRUE, v147 AS TRUE, v148 AS TRUE, v149 AS TRUE, v150 AS TRUE, v151 AS TRUE, v152 AS TRUE, v153 AS TRUE, v154 AS TRUE, v155 AS TRUE, v156 AS TRUE, v157 AS TRUE, v158 AS TRUE, v159 AS TRUE, v160 AS TRUE, v161 AS TRUE, v162 AS TRUE, v163 AS TRUE, v164 AS TRUE, v165 AS TRUE, v166 AS TRUE, v167 AS TRUE, v168 AS TRUE, v169 AS TRUE, v170 AS TRUE, v171 AS TRUE, v172 AS TRUE, v173 AS TRUE, v174 AS TRUE, v175 AS TRUE, v176 AS TRUE, v177 AS TRUE, v178 AS TRUE, v179 AS TRUE, v180 AS TRUE, v181 AS TRUE, v182 AS TRUE, v183 AS TRUE, v184 AS TRUE, v185 AS TRUE, v186 AS TRUE, v187 AS TRUE, v188 AS TRUE, v189 AS TRUE, v190 AS TRUE, v191 AS TRUE, v192 AS TRUE, v193 AS TRUE, v194 AS TRUE, v195 AS TRUE, v196 AS TRUE, v197 AS TRUE, v198 AS TRUE, v199 AS TRUE, v200 AS TRUE, v201 AS TRUE, v202 AS TRUE, v203 AS TRUE, v204 AS TRUE, v205 AS TRUE, v206 AS TRUE, v207 AS TRUE, v208 AS TRUE, v209 AS TRUE, v210 AS TRUE, v211 AS TRUE, v212 AS TRUE, v213 AS TRUE, v214 AS TRUE, v215 AS TRUE, v216 AS TRUE, v217 AS TRUE, v218 AS TRUE, v219 AS TRUE, v220 AS TRUE, v221 AS TRUE, v222 AS TRUE, v223 AS TRUE, v224 AS TRUE, v225 AS TRUE, v226 AS TRUE, v227 AS TRUE, v228 AS TRUE, v229 AS TRUE, v230 AS TRUE, v231 AS TRUE, v232 AS TRUE, v233 AS TRUE, v234 AS TRUE, v235 AS TRUE, v236 AS TRUE, v237 AS TRUE, v238 AS TRUE, v239 AS TRUE, v240 AS TRUE, v241 AS TRUE, v242 AS TRUE, v243 AS TRUE, v244 AS TRUE, v245 AS TRUE, v246 AS TRUE, v247 AS TRUE, v248 AS TRUE, v249 AS TRUE, v250 AS TRUE, v251 AS TRUE, v252 AS TRUE)"
-PL/pgSQL function inline_code_block line 17 at EXECUTE
--- Error: 253 variables exceeds limit of 251
-DO $$
-DECLARE
- pattern_vars text;
- define_vars text;
- query text;
-BEGIN
- SELECT string_agg('v' || lpad(i::text, 3, '0'), ' '),
- string_agg('v' || lpad(i::text, 3, '0') || ' AS TRUE', ', ')
- INTO pattern_vars, define_vars
- FROM generate_series(1, 253) i;
-
- query := format('SELECT * FROM (SELECT 1 AS x) t WINDOW w AS (
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- PATTERN (%s)
- DEFINE %s)', pattern_vars, define_vars);
-
- EXECUTE query;
-END;
-$$;
-ERROR: too many pattern variables
-DETAIL: Maximum is 251.
-CONTEXT: SQL statement "SELECT * FROM (SELECT 1 AS x) t WINDOW w AS (
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- PATTERN (v001 v002 v003 v004 v005 v006 v007 v008 v009 v010 v011 v012 v013 v014 v015 v016 v017 v018 v019 v020 v021 v022 v023 v024 v025 v026 v027 v028 v029 v030 v031 v032 v033 v034 v035 v036 v037 v038 v039 v040 v041 v042 v043 v044 v045 v046 v047 v048 v049 v050 v051 v052 v053 v054 v055 v056 v057 v058 v059 v060 v061 v062 v063 v064 v065 v066 v067 v068 v069 v070 v071 v072 v073 v074 v075 v076 v077 v078 v079 v080 v081 v082 v083 v084 v085 v086 v087 v088 v089 v090 v091 v092 v093 v094 v095 v096 v097 v098 v099 v100 v101 v102 v103 v104 v105 v106 v107 v108 v109 v110 v111 v112 v113 v114 v115 v116 v117 v118 v119 v120 v121 v122 v123 v124 v125 v126 v127 v128 v129 v130 v131 v132 v133 v134 v135 v136 v137 v138 v139 v140 v141 v142 v143 v144 v145 v146 v147 v148 v149 v150 v151 v152 v153 v154 v155 v156 v157 v158 v159 v160 v161 v162 v163 v164 v165 v166 v167 v168 v169 v170 v171 v172 v173 v174 v175 v176 v177 v178 v179 v180 v181 v182 v183 v184 v185 v186 v187 v188 v189 v190 v191 v192 v193 v194 v195 v196 v197 v198 v199 v200 v201 v202 v203 v204 v205 v206 v207 v208 v209 v210 v211 v212 v213 v214 v215 v216 v217 v218 v219 v220 v221 v222 v223 v224 v225 v226 v227 v228 v229 v230 v231 v232 v233 v234 v235 v236 v237 v238 v239 v240 v241 v242 v243 v244 v245 v246 v247 v248 v249 v250 v251 v252 v253)
- DEFINE v001 AS TRUE, v002 AS TRUE, v003 AS TRUE, v004 AS TRUE, v005 AS TRUE, v006 AS TRUE, v007 AS TRUE, v008 AS TRUE, v009 AS TRUE, v010 AS TRUE, v011 AS TRUE, v012 AS TRUE, v013 AS TRUE, v014 AS TRUE, v015 AS TRUE, v016 AS TRUE, v017 AS TRUE, v018 AS TRUE, v019 AS TRUE, v020 AS TRUE, v021 AS TRUE, v022 AS TRUE, v023 AS TRUE, v024 AS TRUE, v025 AS TRUE, v026 AS TRUE, v027 AS TRUE, v028 AS TRUE, v029 AS TRUE, v030 AS TRUE, v031 AS TRUE, v032 AS TRUE, v033 AS TRUE, v034 AS TRUE, v035 AS TRUE, v036 AS TRUE, v037 AS TRUE, v038 AS TRUE, v039 AS TRUE, v040 AS TRUE, v041 AS TRUE, v042 AS TRUE, v043 AS TRUE, v044 AS TRUE, v045 AS TRUE, v046 AS TRUE, v047 AS TRUE, v048 AS TRUE, v049 AS TRUE, v050 AS TRUE, v051 AS TRUE, v052 AS TRUE, v053 AS TRUE, v054 AS TRUE, v055 AS TRUE, v056 AS TRUE, v057 AS TRUE, v058 AS TRUE, v059 AS TRUE, v060 AS TRUE, v061 AS TRUE, v062 AS TRUE, v063 AS TRUE, v064 AS TRUE, v065 AS TRUE, v066 AS TRUE, v067 AS TRUE, v068 AS TRUE, v069 AS TRUE, v070 AS TRUE, v071 AS TRUE, v072 AS TRUE, v073 AS TRUE, v074 AS TRUE, v075 AS TRUE, v076 AS TRUE, v077 AS TRUE, v078 AS TRUE, v079 AS TRUE, v080 AS TRUE, v081 AS TRUE, v082 AS TRUE, v083 AS TRUE, v084 AS TRUE, v085 AS TRUE, v086 AS TRUE, v087 AS TRUE, v088 AS TRUE, v089 AS TRUE, v090 AS TRUE, v091 AS TRUE, v092 AS TRUE, v093 AS TRUE, v094 AS TRUE, v095 AS TRUE, v096 AS TRUE, v097 AS TRUE, v098 AS TRUE, v099 AS TRUE, v100 AS TRUE, v101 AS TRUE, v102 AS TRUE, v103 AS TRUE, v104 AS TRUE, v105 AS TRUE, v106 AS TRUE, v107 AS TRUE, v108 AS TRUE, v109 AS TRUE, v110 AS TRUE, v111 AS TRUE, v112 AS TRUE, v113 AS TRUE, v114 AS TRUE, v115 AS TRUE, v116 AS TRUE, v117 AS TRUE, v118 AS TRUE, v119 AS TRUE, v120 AS TRUE, v121 AS TRUE, v122 AS TRUE, v123 AS TRUE, v124 AS TRUE, v125 AS TRUE, v126 AS TRUE, v127 AS TRUE, v128 AS TRUE, v129 AS TRUE, v130 AS TRUE, v131 AS TRUE, v132 AS TRUE, v133 AS TRUE, v134 AS TRUE, v135 AS TRUE, v136 AS TRUE, v137 AS TRUE, v138 AS TRUE, v139 AS TRUE, v140 AS TRUE, v141 AS TRUE, v142 AS TRUE, v143 AS TRUE, v144 AS TRUE, v145 AS TRUE, v146 AS TRUE, v147 AS TRUE, v148 AS TRUE, v149 AS TRUE, v150 AS TRUE, v151 AS TRUE, v152 AS TRUE, v153 AS TRUE, v154 AS TRUE, v155 AS TRUE, v156 AS TRUE, v157 AS TRUE, v158 AS TRUE, v159 AS TRUE, v160 AS TRUE, v161 AS TRUE, v162 AS TRUE, v163 AS TRUE, v164 AS TRUE, v165 AS TRUE, v166 AS TRUE, v167 AS TRUE, v168 AS TRUE, v169 AS TRUE, v170 AS TRUE, v171 AS TRUE, v172 AS TRUE, v173 AS TRUE, v174 AS TRUE, v175 AS TRUE, v176 AS TRUE, v177 AS TRUE, v178 AS TRUE, v179 AS TRUE, v180 AS TRUE, v181 AS TRUE, v182 AS TRUE, v183 AS TRUE, v184 AS TRUE, v185 AS TRUE, v186 AS TRUE, v187 AS TRUE, v188 AS TRUE, v189 AS TRUE, v190 AS TRUE, v191 AS TRUE, v192 AS TRUE, v193 AS TRUE, v194 AS TRUE, v195 AS TRUE, v196 AS TRUE, v197 AS TRUE, v198 AS TRUE, v199 AS TRUE, v200 AS TRUE, v201 AS TRUE, v202 AS TRUE, v203 AS TRUE, v204 AS TRUE, v205 AS TRUE, v206 AS TRUE, v207 AS TRUE, v208 AS TRUE, v209 AS TRUE, v210 AS TRUE, v211 AS TRUE, v212 AS TRUE, v213 AS TRUE, v214 AS TRUE, v215 AS TRUE, v216 AS TRUE, v217 AS TRUE, v218 AS TRUE, v219 AS TRUE, v220 AS TRUE, v221 AS TRUE, v222 AS TRUE, v223 AS TRUE, v224 AS TRUE, v225 AS TRUE, v226 AS TRUE, v227 AS TRUE, v228 AS TRUE, v229 AS TRUE, v230 AS TRUE, v231 AS TRUE, v232 AS TRUE, v233 AS TRUE, v234 AS TRUE, v235 AS TRUE, v236 AS TRUE, v237 AS TRUE, v238 AS TRUE, v239 AS TRUE, v240 AS TRUE, v241 AS TRUE, v242 AS TRUE, v243 AS TRUE, v244 AS TRUE, v245 AS TRUE, v246 AS TRUE, v247 AS TRUE, v248 AS TRUE, v249 AS TRUE, v250 AS TRUE, v251 AS TRUE, v252 AS TRUE, v253 AS TRUE)"
-PL/pgSQL function inline_code_block line 17 at EXECUTE
- CREATE TEMP TABLE stock_null (company TEXT, tdate DATE, price INTEGER);
- INSERT INTO stock_null VALUES ('c1', '2023-07-01', 100);
- INSERT INTO stock_null VALUES ('c1', '2023-07-02', NULL); -- NULL in middle
- INSERT INTO stock_null VALUES ('c1', '2023-07-03', 200);
- INSERT INTO stock_null VALUES ('c1', '2023-07-04', 150);
- SELECT company, tdate, price, count(*) OVER w AS match_count
- FROM stock_null
- WINDOW w AS (
- PARTITION BY company
- ORDER BY tdate
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- PATTERN (START UP DOWN)
- DEFINE START AS TRUE, UP AS price > PREV(price), DOWN AS price <
-PREV(price)
- );
- company | tdate | price | match_count
----------+------------+-------+-------------
- c1 | 07-01-2023 | 100 | 0
- c1 | 07-02-2023 | | 0
- c1 | 07-03-2023 | 200 | 0
- c1 | 07-04-2023 | 150 | 0
-(4 rows)
-
--- Overlapping match tests (requires multi-context for correct behavior)
--- Using array flags: 'X' = ANY(flags) for multi-TRUE support
--- Test 1: A B C D E | B C D | C D E F - three overlapping patterns
--- Different end points: B C D (4), A B C D E (5), C D E F (6)
-WITH test_overlap1 AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['B']),
- (3, ARRAY['C']),
- (4, ARRAY['D']),
- (5, ARRAY['E']),
- (6, ARRAY['F'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM test_overlap1
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN (A B C D E | B C D | C D E F)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags),
- C AS 'C' = ANY(flags),
- D AS 'D' = ANY(flags),
- E AS 'E' = ANY(flags),
- F AS 'F' = ANY(flags)
-);
- id | flags | match_start | match_end
-----+-------+-------------+-----------
- 1 | {A} | 1 | 5
- 2 | {B} | |
- 3 | {C} | |
- 4 | {D} | |
- 5 | {E} | |
- 6 | {F} | |
-(6 rows)
-
-WITH test_overlap1 AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['B']),
- (3, ARRAY['C']),
- (4, ARRAY['D']),
- (5, ARRAY['E']),
- (6, ARRAY['F'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM test_overlap1
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP TO NEXT ROW
- PATTERN (A B C D E | B C D | C D E F)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags),
- C AS 'C' = ANY(flags),
- D AS 'D' = ANY(flags),
- E AS 'E' = ANY(flags),
- F AS 'F' = ANY(flags)
-);
- id | flags | match_start | match_end
-----+-------+-------------+-----------
- 1 | {A} | 1 | 5
- 2 | {B} | 2 | 4
- 3 | {C} | 3 | 6
- 4 | {D} | |
- 5 | {E} | |
- 6 | {F} | |
-(6 rows)
-
--- PAST LAST: only one match
--- TO NEXT ROW with multi-context: three matches
--- Row 1: A B C D E (1-5)
--- Row 2: B C D (2-4) <- ends first!
--- Row 3: C D E F (3-6) <- ends last!
--- Test 1b: Longer pattern FAILS, shorter pattern should survive
--- Pattern: A+ B C D E | B+ C
--- A+ B C D E fails (no E found in sequence)
--- B+ C matches at rows 2-3
--- Result: match 2-3 (B+ C)
-WITH test_overlap1b AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['B']),
- (3, ARRAY['C']),
- (4, ARRAY['D']),
- (5, ARRAY['X'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM test_overlap1b
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN (A+ B C D E | B+ C)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags),
- C AS 'C' = ANY(flags),
- D AS 'D' = ANY(flags),
- E AS 'E' = ANY(flags)
-);
- id | flags | match_start | match_end
-----+-------+-------------+-----------
- 1 | {A} | |
- 2 | {B} | 2 | 3
- 3 | {C} | |
- 4 | {D} | |
- 5 | {X} | |
-(5 rows)
-
--- Test 2: A B+ C | B+ D - long B sequence with different endings
-WITH test_overlap2 AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['B']),
- (3, ARRAY['B']),
- (4, ARRAY['B']),
- (5, ARRAY['B']),
- (6, ARRAY['C']),
- (7, ARRAY['B']),
- (8, ARRAY['B']),
- (9, ARRAY['B']),
- (10, ARRAY['D'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM test_overlap2
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP TO NEXT ROW
- PATTERN (A B+ C | B+ D)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags),
- C AS 'C' = ANY(flags),
- D AS 'D' = ANY(flags)
-);
- id | flags | match_start | match_end
-----+-------+-------------+-----------
- 1 | {A} | 1 | 6
- 2 | {B} | |
- 3 | {B} | |
- 4 | {B} | |
- 5 | {B} | |
- 6 | {C} | |
- 7 | {B} | 7 | 10
- 8 | {B} | 8 | 10
- 9 | {B} | 9 | 10
- 10 | {D} | |
-(10 rows)
-
--- Current result (correct):
--- Row 1: A B+ C (1-6)
--- Row 7-9: B+ D (7-10, 8-10, 9-10)
--- Note: Row 2-6 cannot match B+ D because Row 6 is C, not D
--- With absorption: 8-10 and 9-10 would be absorbed by 7-10 (earlier context covers later)
--- Test 3: Greedy quantifier with late failure - A B C+ D | A B
--- Pattern expects D after C+, but E comes instead ("betrayal")
-WITH test_betrayal AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['B']),
- (3, ARRAY['C']),
- (4, ARRAY['C']),
- (5, ARRAY['C']),
- (6, ARRAY['E'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM test_betrayal
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN (A B C+ D | A B)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags),
- C AS 'C' = ANY(flags),
- D AS 'D' = ANY(flags)
-);
- id | flags | match_start | match_end
-----+-------+-------------+-----------
- 1 | {A} | 1 | 2
- 2 | {B} | |
- 3 | {C} | |
- 4 | {C} | |
- 5 | {C} | |
- 6 | {E} | |
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A B+ C)
+ DEFINE
+ A AS val IS NOT NULL,
+ B AS val IS NULL AND NEXT(val) IS NULL,
+ C AS val IS NULL AND NEXT(val) IS NOT NULL
+);
+ id | val | cnt
+----+-----+-----
+ 1 | 100 | 4
+ 2 | | 0
+ 3 | | 0
+ 4 | | 0
+ 5 | 200 | 0
+ 6 | 300 | 0
(6 rows)
--- A B C+ D fails at Row 6 (E instead of D)
--- Question: Does it fallback to A B (1-2)?
--- Test 4: Lexical Order test - A B C | A B C D E
--- SQL standard: first matching alternative wins
-WITH test_lexical AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['B']),
- (3, ARRAY['C']),
- (4, ARRAY['D']),
- (5, ARRAY['E'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM test_lexical
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN (A B C | A B C D E)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags),
- C AS 'C' = ANY(flags),
- D AS 'D' = ANY(flags),
- E AS 'E' = ANY(flags)
-);
- id | flags | match_start | match_end
-----+-------+-------------+-----------
- 1 | {A} | 1 | 3
- 2 | {B} | |
- 3 | {C} | |
- 4 | {D} | |
- 5 | {E} | |
-(5 rows)
-
--- SQL standard Lexical Order: A B C (1-3) wins (first alternative)
--- Test 4b: Reversed pattern order - A B C D E | A B C
-WITH test_lexical2 AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['B']),
- (3, ARRAY['C']),
- (4, ARRAY['D']),
- (5, ARRAY['E'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM test_lexical2
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN (A B C D E | A B C)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags),
- C AS 'C' = ANY(flags),
- D AS 'D' = ANY(flags),
- E AS 'E' = ANY(flags)
-);
- id | flags | match_start | match_end
-----+-------+-------------+-----------
- 1 | {A} | 1 | 5
- 2 | {B} | |
- 3 | {C} | |
- 4 | {D} | |
- 5 | {E} | |
-(5 rows)
-
--- SQL standard Lexical Order: A B C D E (1-5) wins (first alternative)
--- Test 5: Multiple TRUE in single row (overlapping pattern variables)
--- Each row matches multiple DEFINE conditions simultaneously
-WITH test_multi_true AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A','B']), -- A and B both TRUE
- (2, ARRAY['B','C']), -- B and C both TRUE
- (3, ARRAY['C','D']), -- C and D both TRUE
- (4, ARRAY['D','E']), -- D and E both TRUE
- (5, ARRAY['E','_']) -- E only
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM test_multi_true
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN (A B C D E)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags),
- C AS 'C' = ANY(flags),
- D AS 'D' = ANY(flags),
- E AS 'E' = ANY(flags)
-);
- id | flags | match_start | match_end
-----+-------+-------------+-----------
- 1 | {A,B} | 1 | 5
- 2 | {B,C} | |
- 3 | {C,D} | |
- 4 | {D,E} | |
- 5 | {E,_} | |
-(5 rows)
-
--- Row 1: A=T, B=T -> matches A
--- Row 2: B=T, C=T -> matches B
--- Row 3: C=T, D=T -> matches C
--- Row 4: D=T, E=T -> matches D
--- Row 5: E=T -> matches E
--- Result: match 1-5 (A B C D E)
--- Test 6: Diagonal pattern with multi-TRUE (shifted overlap)
-WITH test_diagonal AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A','_']),
- (2, ARRAY['B','A']),
- (3, ARRAY['C','B']),
- (4, ARRAY['D','C']),
- (5, ARRAY['_','D'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM test_diagonal
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP TO NEXT ROW
- PATTERN (A B C D)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags),
- C AS 'C' = ANY(flags),
- D AS 'D' = ANY(flags)
-);
- id | flags | match_start | match_end
-----+-------+-------------+-----------
- 1 | {A,_} | 1 | 4
- 2 | {B,A} | 2 | 5
- 3 | {C,B} | |
- 4 | {D,C} | |
- 5 | {_,D} | |
-(5 rows)
-
--- Possible matches:
--- Start Row 1: A(1) B(2) C(3) D(4) -> 1-4
--- Start Row 2: A(2) B(3) C(4) D(5) -> 2-5 (because Row 2 has A too!)
--- ===================================================================
--- Context Absorption Tests
--- ===================================================================
--- Test absorption 1: Basic A+ pattern - later contexts absorbed by earlier
-WITH test_absorb_basic AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['A']),
- (3, ARRAY['A']),
- (4, ARRAY['A']),
- (5, ARRAY['B'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM test_absorb_basic
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP TO NEXT ROW
- PATTERN (A+)
- DEFINE A AS 'A' = ANY(flags)
-);
- id | flags | match_start | match_end
-----+-------+-------------+-----------
- 1 | {A} | 1 | 4
- 2 | {A} | 2 | 4
- 3 | {A} | 3 | 4
- 4 | {A} | 4 | 4
- 5 | {B} | |
-(5 rows)
-
--- Pattern A+ is absorbable (unbounded first element, only one unbounded)
--- 4 matches: (1-4, 2-4, 3-4, 4-4)
--- Test absorption 2: A+ B pattern - absorption with fixed suffix
-WITH test_absorb_suffix AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['A']),
- (3, ARRAY['A']),
- (4, ARRAY['B']),
- (5, ARRAY['X'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM test_absorb_suffix
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP TO NEXT ROW
- PATTERN (A+ B)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags)
-);
- id | flags | match_start | match_end
-----+-------+-------------+-----------
- 1 | {A} | 1 | 4
- 2 | {A} | 2 | 4
- 3 | {A} | 3 | 4
- 4 | {B} | |
- 5 | {X} | |
-(5 rows)
-
--- Pattern A+ B is absorbable (A+ unbounded first, B bounded suffix)
--- All potential matches end at same row (row 4 with B)
--- 3 matches: (1-4, 2-4, 3-4)
--- Test absorption 3: Per-branch absorption with ALT (B+ C | B+ D)
-WITH test_absorb_alt AS (
- SELECT * FROM (VALUES
- (1, ARRAY['B']),
- (2, ARRAY['B']),
- (3, ARRAY['B']),
- (4, ARRAY['D']),
- (5, ARRAY['X'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM test_absorb_alt
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP TO NEXT ROW
- PATTERN (B+ C | B+ D)
- DEFINE
- B AS 'B' = ANY(flags),
- C AS 'C' = ANY(flags),
- D AS 'D' = ANY(flags)
-);
- id | flags | match_start | match_end
-----+-------+-------------+-----------
- 1 | {B} | 1 | 4
- 2 | {B} | 2 | 4
- 3 | {B} | 3 | 4
- 4 | {D} | |
- 5 | {X} | |
-(5 rows)
-
--- Both branches B+ C and B+ D are absorbable (B+ unbounded first)
--- B+ D branch matches: 3 matches (1-4, 2-4, 3-4)
--- Test absorption 4: Non-absorbable pattern (A B+ - unbounded not first)
-WITH test_no_absorb AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['B']),
- (3, ARRAY['B']),
- (4, ARRAY['B']),
- (5, ARRAY['X'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM test_no_absorb
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP TO NEXT ROW
- PATTERN (A B+)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags)
-);
- id | flags | match_start | match_end
-----+-------+-------------+-----------
- 1 | {A} | 1 | 4
- 2 | {B} | |
- 3 | {B} | |
- 4 | {B} | |
- 5 | {X} | |
-(5 rows)
-
--- Pattern A B+ is NOT absorbable (A bounded first, B+ unbounded but not first)
--- Only Row 1 can start match (only row with A), so only one match: 1-4
--- Test absorption 5: GROUP merge enables absorption
-WITH test_absorb_group AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['B']),
- (3, ARRAY['A']),
- (4, ARRAY['B']),
- (5, ARRAY['A']),
- (6, ARRAY['B']),
- (7, ARRAY['X'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM test_absorb_group
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP TO NEXT ROW
- PATTERN ((A B) (A B)+)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags)
-);
- id | flags | match_start | match_end
-----+-------+-------------+-----------
- 1 | {A} | 1 | 6
- 2 | {B} | |
- 3 | {A} | 3 | 6
- 4 | {B} | |
- 5 | {A} | |
- 6 | {B} | |
- 7 | {X} | |
-(7 rows)
-
--- Pattern optimized: (A B) (A B)+ -> (A B){2,}
--- 2 matches: 1-6 (3 reps), 3-6 (2 reps)
--- Test absorption 6: Multiple unbounded - first element unbounded enables absorption
-WITH test_multi_unbounded AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['A']),
- (3, ARRAY['B']),
- (4, ARRAY['B']),
- (5, ARRAY['X'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM test_multi_unbounded
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP TO NEXT ROW
- PATTERN (A+ B+)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags)
-);
- id | flags | match_start | match_end
-----+-------+-------------+-----------
- 1 | {A} | 1 | 4
- 2 | {A} | 2 | 4
- 3 | {B} | |
- 4 | {B} | |
- 5 | {X} | |
-(5 rows)
-
--- 2 matches: 1-4, 2-4 (same endpoint 4)
--- ============================================
--- Jacob's RPR Patterns (from jacob branch)
--- ============================================
--- Test: A? (optional, greedy)
-WITH jacob_optional AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['X'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM jacob_optional
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN (A?)
- DEFINE A AS 'A' = ANY(flags)
-);
- id | flags | match_start | match_end
-----+-------+-------------+-----------
- 1 | {A} | 1 | 1
- 2 | {X} | |
-(2 rows)
-
--- Expected: 1-1 (matches A)
--- Test: A{2} (exact count)
-WITH jacob_exact AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['A']),
- (3, ARRAY['A']),
- (4, ARRAY['X'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM jacob_exact
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN (A{2})
- DEFINE A AS 'A' = ANY(flags)
-);
- id | flags | match_start | match_end
-----+-------+-------------+-----------
- 1 | {A} | 1 | 2
- 2 | {A} | |
- 3 | {A} | |
- 4 | {X} | |
-(4 rows)
-
--- Expected: 1-2
--- Test: A{1,3} (bounded range, greedy)
-WITH jacob_bounded AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['A']),
- (3, ARRAY['A']),
- (4, ARRAY['A']),
- (5, ARRAY['X'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM jacob_bounded
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN (A{1,3})
- DEFINE A AS 'A' = ANY(flags)
-);
- id | flags | match_start | match_end
-----+-------+-------------+-----------
- 1 | {A} | 1 | 3
- 2 | {A} | |
- 3 | {A} | |
- 4 | {A} | 4 | 4
- 5 | {X} | |
-(5 rows)
-
--- Expected: 1-3 (greedy takes max), then 4-4
--- Test: A | B (simple alternation)
-WITH jacob_simple_alt AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['B']),
- (3, ARRAY['X'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM jacob_simple_alt
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN (A | B)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags)
-);
- id | flags | match_start | match_end
-----+-------+-------------+-----------
- 1 | {A} | 1 | 1
- 2 | {B} | 2 | 2
- 3 | {X} | |
-(3 rows)
-
--- Expected: 1-1 (A), 2-2 (B)
--- Test: A | B | C (three-way alternation)
-WITH jacob_three_alt AS (
- SELECT * FROM (VALUES
- (1, ARRAY['B']),
- (2, ARRAY['X'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM jacob_three_alt
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN (A | B | C)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags),
- C AS 'C' = ANY(flags)
-);
- id | flags | match_start | match_end
-----+-------+-------------+-----------
- 1 | {B} | 1 | 1
- 2 | {X} | |
-(2 rows)
-
--- Expected: 1-1 (B)
--- Test: A B C (concatenation)
-WITH jacob_concat AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['B']),
- (3, ARRAY['C']),
- (4, ARRAY['X'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM jacob_concat
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN (A B C)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags),
- C AS 'C' = ANY(flags)
-);
- id | flags | match_start | match_end
-----+-------+-------------+-----------
- 1 | {A} | 1 | 3
- 2 | {B} | |
- 3 | {C} | |
- 4 | {X} | |
-(4 rows)
-
--- Expected: 1-3
--- Test: A B? C (optional middle)
-WITH jacob_optional_mid AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['C']),
- (3, ARRAY['X'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM jacob_optional_mid
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN (A B? C)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags),
- C AS 'C' = ANY(flags)
-);
- id | flags | match_start | match_end
-----+-------+-------------+-----------
- 1 | {A} | 1 | 2
- 2 | {C} | |
- 3 | {X} | |
-(3 rows)
-
--- Expected: 1-2 (A C, B skipped)
--- Test: (A B){2} (nested group with quantifier)
-WITH jacob_nested_group AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['B']),
- (3, ARRAY['A']),
- (4, ARRAY['B']),
- (5, ARRAY['X'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM jacob_nested_group
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN ((A B){2})
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags)
-);
- id | flags | match_start | match_end
-----+-------+-------------+-----------
- 1 | {A} | 1 | 4
- 2 | {B} | |
- 3 | {A} | |
- 4 | {B} | |
- 5 | {X} | |
-(5 rows)
-
--- Expected: 1-4 (A B A B)
--- Test: (A){3} (quantifier on grouped single element)
-WITH jacob_group_quant AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['A']),
- (3, ARRAY['A']),
- (4, ARRAY['X'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM jacob_group_quant
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN ((A){3})
- DEFINE A AS 'A' = ANY(flags)
-);
- id | flags | match_start | match_end
-----+-------+-------------+-----------
- 1 | {A} | 1 | 3
- 2 | {A} | |
- 3 | {A} | |
- 4 | {X} | |
-(4 rows)
-
--- Expected: 1-3
--- Test: A B C | A B C D E (lexical order - first alt wins)
-WITH jacob_lex_first AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['B']),
- (3, ARRAY['C']),
- (4, ARRAY['D']),
- (5, ARRAY['E'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM jacob_lex_first
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN (A B C | A B C D E)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags),
- C AS 'C' = ANY(flags),
- D AS 'D' = ANY(flags),
- E AS 'E' = ANY(flags)
-);
- id | flags | match_start | match_end
-----+-------+-------------+-----------
- 1 | {A} | 1 | 3
- 2 | {B} | |
- 3 | {C} | |
- 4 | {D} | |
- 5 | {E} | |
-(5 rows)
-
--- Expected: 1-3 (A B C wins by lexical order)
--- Test: A B C D E | A B C (lexical order - longer first wins)
-WITH jacob_lex_long AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['B']),
- (3, ARRAY['C']),
- (4, ARRAY['D']),
- (5, ARRAY['E'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM jacob_lex_long
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN (A B C D E | A B C)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags),
- C AS 'C' = ANY(flags),
- D AS 'D' = ANY(flags),
- E AS 'E' = ANY(flags)
-);
- id | flags | match_start | match_end
-----+-------+-------------+-----------
- 1 | {A} | 1 | 5
- 2 | {B} | |
- 3 | {C} | |
- 4 | {D} | |
- 5 | {E} | |
-(5 rows)
-
--- Expected: 1-5 (A B C D E wins by lexical order)
--- ============================================
--- Alternation with quantifiers (BUG cases from Jacob's tests)
--- ============================================
--- Test: (A | B)+ C - alternation inside quantified group followed by C
-WITH jacob_alt_quant AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['B']),
- (3, ARRAY['A']),
- (4, ARRAY['C'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM jacob_alt_quant
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN ((A | B)+ C)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags),
- C AS 'C' = ANY(flags)
-);
- id | flags | match_start | match_end
-----+-------+-------------+-----------
- 1 | {A} | 1 | 4
- 2 | {B} | |
- 3 | {A} | |
- 4 | {C} | |
-(4 rows)
-
--- Expected: 1-4 (A B A C)
--- Test: ((A | B) C)+ - alternation inside group with outer quantifier
-WITH jacob_alt_group AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['C']),
- (3, ARRAY['B']),
- (4, ARRAY['C']),
- (5, ARRAY['X'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM jacob_alt_group
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN (((A | B) C)+)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags),
- C AS 'C' = ANY(flags)
-);
- id | flags | match_start | match_end
-----+-------+-------------+-----------
- 1 | {A} | 1 | 4
- 2 | {C} | |
- 3 | {B} | |
- 4 | {C} | |
- 5 | {X} | |
-(5 rows)
-
--- Expected: 1-4 (A C B C)
--- ============================================
--- RELUCTANT quantifiers
--- ============================================
--- Test: A+? B (reluctant) - B available at row 3, A exits early
-WITH jacob_reluctant AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A','_']),
- (2, ARRAY['A','_']),
- (3, ARRAY['A','B']),
- (4, ARRAY['B','_'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM jacob_reluctant
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN (A+? B)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags)
-);
- id | flags | match_start | match_end
-----+-------+-------------+-----------
- 1 | {A,_} | 1 | 3
- 2 | {A,_} | |
- 3 | {A,B} | |
- 4 | {B,_} | |
-(4 rows)
-
--- Test: A{1,3}? B (reluctant bounded) - same data, bounded quantifier
-WITH jacob_reluctant_bounded AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A','_']),
- (2, ARRAY['A','_']),
- (3, ARRAY['A','B']),
- (4, ARRAY['B','_'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM jacob_reluctant_bounded
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN (A{1,3}? B)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags)
-);
- id | flags | match_start | match_end
-----+-------+-------------+-----------
- 1 | {A,_} | 1 | 3
- 2 | {A,_} | |
- 3 | {A,B} | |
- 4 | {B,_} | |
-(4 rows)
-
--- ============================================
--- Nested quantifiers (pathological patterns)
--- ============================================
--- These patterns previously caused segfault or infinite loop.
--- Now they are either optimized at compile time or handled safely at runtime.
--- Test: (A*)* - nested unbounded quantifiers (optimized to A*)
-SELECT v, count(*) OVER w AS c
-FROM (SELECT generate_series(1, 5) v)
-WINDOW w AS (
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- INITIAL
- PATTERN ((A*)*)
- DEFINE A AS TRUE
-);
- v | c
----+---
- 1 | 5
- 2 | 0
- 3 | 0
- 4 | 0
- 5 | 0
-(5 rows)
-
--- Test: (A*)+ - inner nullable, outer requires one (optimized to A*)
-SELECT v, count(*) OVER w AS c
-FROM (SELECT generate_series(1, 5) v)
-WINDOW w AS (
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- INITIAL
- PATTERN ((A*)+)
- DEFINE A AS TRUE
-);
- v | c
----+---
- 1 | 5
- 2 | 0
- 3 | 0
- 4 | 0
- 5 | 0
-(5 rows)
-
--- Test: (A+)* - outer nullable (optimized to A*)
-SELECT v, count(*) OVER w AS c
-FROM (SELECT generate_series(1, 5) v)
-WINDOW w AS (
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- INITIAL
- PATTERN ((A+)*)
- DEFINE A AS TRUE
-);
- v | c
----+---
- 1 | 5
- 2 | 0
- 3 | 0
- 4 | 0
- 5 | 0
-(5 rows)
-
--- Test: (A+)+ - both require match (optimized to A+)
-SELECT v, count(*) OVER w AS c
-FROM (SELECT generate_series(1, 5) v)
-WINDOW w AS (
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- INITIAL
- PATTERN ((A+)+)
- DEFINE A AS TRUE
-);
- v | c
----+---
- 1 | 5
- 2 | 0
- 3 | 0
- 4 | 0
- 5 | 0
-(5 rows)
-
--- Test: (A* B*)* - complex nested pattern (runtime protection)
--- Not optimized but handled safely by empty-match loop prevention
-SELECT v, count(*) OVER w AS c
-FROM (SELECT generate_series(1, 5) v)
-WINDOW w AS (
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- INITIAL
- PATTERN ((A* B*)*)
- DEFINE A AS TRUE, B AS TRUE
-);
- v | c
----+---
- 1 | 5
- 2 | 0
- 3 | 0
- 4 | 0
- 5 | 0
-(5 rows)
-
--- Test: (((A)*)*)* - triple nested (optimized through recursive optimization)
-SELECT v, count(*) OVER w AS c
-FROM (SELECT generate_series(1, 3) v)
-WINDOW w AS (
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- INITIAL
- PATTERN ((((A)*)*)*)
- DEFINE A AS TRUE
-);
- v | c
----+---
- 1 | 3
- 2 | 0
- 3 | 0
-(3 rows)
-
+DROP TABLE rpr_consec_null;
diff --git a/src/test/regress/expected/rpr_base.out b/src/test/regress/expected/rpr_base.out
index ab878443379..3383b242ef0 100644
--- a/src/test/regress/expected/rpr_base.out
+++ b/src/test/regress/expected/rpr_base.out
@@ -1449,7 +1449,7 @@ ERROR: syntax error at or near "*"
LINE 6: PATTERN (A+ *)
^
-- Expected: ERROR: syntax error at or near "*"
--- ? ? (invalid combination)
+-- ? ? (parsed as ?? reluctant quantifier)
SELECT COUNT(*) OVER w
FROM rpr_reluctant
WINDOW w AS (
@@ -2203,7 +2203,32 @@ SELECT pg_get_viewdef('rpr_serial_v8'::regclass);
(1 row)
DROP VIEW rpr_serial_v8;
-DROP TABLE rpr_serial;
+-- Reluctant {1}? quantifier deparse through ruleutils
+CREATE VIEW rpr_quant_reluctant_v AS
+SELECT id, val, count(*) OVER w
+FROM rpr_serial
+WINDOW w AS (ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ INITIAL
+ PATTERN (A{1}? B)
+ DEFINE A AS val > 0, B AS val > 0);
+SELECT pg_get_viewdef('rpr_quant_reluctant_v'::regclass);
+ pg_get_viewdef
+------------------------------------------------------------------------------
+ SELECT id, +
+ val, +
+ count(*) OVER w AS count +
+ FROM rpr_serial +
+ WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING +
+ AFTER MATCH SKIP PAST LAST ROW +
+ INITIAL +
+ PATTERN (a{1}? b) +
+ DEFINE +
+ a AS (val > 0), +
+ b AS (val > 0) );
+(1 row)
+
+DROP VIEW rpr_quant_reluctant_v;
-- Materialized view (if supported)
CREATE TABLE rpr_mview (id INT, val INT);
INSERT INTO rpr_mview VALUES (1, 10), (2, 20), (3, 30);
@@ -2251,6 +2276,52 @@ SELECT * FROM rpr_mview_v1 ORDER BY id;
DROP MATERIALIZED VIEW rpr_mview_v1;
DROP TABLE rpr_mview;
+-- CREATE TABLE AS SELECT with RPR
+CREATE TABLE rpr_ctas (id INT, val INT);
+INSERT INTO rpr_ctas VALUES (1, 10), (2, 20), (3, 15), (4, 25);
+CREATE TABLE rpr_ctas_result AS
+SELECT id, val, count(*) OVER w AS cnt
+FROM rpr_ctas
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A B+)
+ DEFINE A AS TRUE, B AS val > PREV(val)
+);
+SELECT * FROM rpr_ctas_result ORDER BY id;
+ id | val | cnt
+----+-----+-----
+ 1 | 10 | 2
+ 2 | 20 | 0
+ 3 | 15 | 2
+ 4 | 25 | 0
+(4 rows)
+
+-- INSERT INTO ... SELECT with RPR
+CREATE TABLE rpr_insert_target (id INT, val INT, cnt BIGINT);
+INSERT INTO rpr_insert_target
+SELECT id, val, count(*) OVER w
+FROM rpr_ctas
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A B+)
+ DEFINE A AS TRUE, B AS val > PREV(val)
+);
+SELECT * FROM rpr_insert_target ORDER BY id;
+ id | val | cnt
+----+-----+-----
+ 1 | 10 | 2
+ 2 | 20 | 0
+ 3 | 15 | 2
+ 4 | 25 | 0
+(4 rows)
+
+DROP TABLE rpr_ctas_result;
+DROP TABLE rpr_insert_target;
+DROP TABLE rpr_ctas;
-- Prepared statements (tests outfuncs.c / readfuncs.c)
CREATE TABLE rpr_prep (id INT, val INT);
INSERT INTO rpr_prep VALUES (1, 10), (2, 20), (3, 30);
@@ -2608,6 +2679,57 @@ SELECT pg_get_viewdef('rpr_multiwin_v'::regclass);
DROP VIEW rpr_multiwin_v;
DROP TABLE rpr_multiwin;
+-- {n} quantifier display in view
+CREATE VIEW rpr_quant_n_v AS
+SELECT id, val, count(*) OVER w
+FROM rpr_serial
+WINDOW w AS (ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ INITIAL
+ PATTERN (A{3})
+ DEFINE A AS val > 0);
+SELECT pg_get_viewdef('rpr_quant_n_v'::regclass);
+ pg_get_viewdef
+------------------------------------------------------------------------------
+ SELECT id, +
+ val, +
+ count(*) OVER w AS count +
+ FROM rpr_serial +
+ WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING +
+ AFTER MATCH SKIP PAST LAST ROW +
+ INITIAL +
+ PATTERN (a{3}) +
+ DEFINE +
+ a AS (val > 0) );
+(1 row)
+
+DROP VIEW rpr_quant_n_v;
+-- {n,} quantifier display in view
+CREATE VIEW rpr_quant_n_plus_v AS
+SELECT id, val, count(*) OVER w
+FROM rpr_serial
+WINDOW w AS (ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ INITIAL
+ PATTERN (A{2,})
+ DEFINE A AS val > 0);
+SELECT pg_get_viewdef('rpr_quant_n_plus_v'::regclass);
+ pg_get_viewdef
+------------------------------------------------------------------------------
+ SELECT id, +
+ val, +
+ count(*) OVER w AS count +
+ FROM rpr_serial +
+ WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING +
+ AFTER MATCH SKIP PAST LAST ROW +
+ INITIAL +
+ PATTERN (a{2,}) +
+ DEFINE +
+ a AS (val > 0) );
+(1 row)
+
+DROP VIEW rpr_quant_n_plus_v;
+DROP TABLE rpr_serial;
-- ============================================================
-- Error Cases Tests
-- ============================================================
@@ -2694,6 +2816,7 @@ WINDOW w AS (
ERROR: pattern variable qualified column reference "a.val" is not supported in DEFINE clause
LINE 7: DEFINE A AS A.val > 0
^
+-- Expected: ERROR: pattern variable qualified column reference "a.val" is not supported
-- PATTERN-only variable qualified name: not supported even without DEFINE entry
SELECT COUNT(*) OVER w
FROM rpr_err
@@ -2706,6 +2829,7 @@ WINDOW w AS (
ERROR: pattern variable qualified column reference "b.val" is not supported in DEFINE clause
LINE 7: DEFINE A AS B.val > 0
^
+-- Expected: ERROR: pattern variable qualified column reference "b.val" is not supported
-- DEFINE-only variable qualified name: still a pattern variable, not a range variable
SELECT COUNT(*) OVER w
FROM rpr_err
@@ -2718,6 +2842,7 @@ WINDOW w AS (
ERROR: pattern variable qualified column reference "b.val" is not supported in DEFINE clause
LINE 7: DEFINE A AS val > 0, B AS B.val > 0
^
+-- Expected: ERROR: pattern variable qualified column reference "b.val" is not supported
-- FROM-clause range variable qualified name: not allowed (prohibited by §6.5)
SELECT COUNT(*) OVER w
FROM rpr_err
@@ -2730,6 +2855,7 @@ WINDOW w AS (
ERROR: range variable qualified column reference "rpr_err.val" is not allowed in DEFINE clause
LINE 7: DEFINE A AS rpr_err.val > 0
^
+-- Expected: ERROR: range variable qualified column reference "rpr_err.val" is not allowed
-- Semantic errors
-- Undefined column in DEFINE
SELECT COUNT(*) OVER w
@@ -2769,7 +2895,7 @@ WINDOW w AS (
ERROR: aggregate functions are not allowed in DEFINE
LINE 7: DEFINE A AS COUNT(*) > 0
^
--- Expected: ERROR or works depending on implementation
+-- Expected: ERROR: aggregate functions are not allowed in DEFINE
-- Subquery in DEFINE (NOT SUPPORTED)
SELECT COUNT(*) OVER w
FROM rpr_err
@@ -3019,6 +3145,26 @@ WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
-> Seq Scan on rpr_plan
(6 rows)
+-- Data execution: SEQ flatten produces correct results
+SELECT id, val, count(*) OVER w AS cnt
+FROM rpr_plan
+WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP TO NEXT ROW
+ PATTERN (A ((B) (C))) DEFINE A AS val <= 30, B AS val <= 60, C AS val > 60);
+ id | val | cnt
+----+-----+-----
+ 1 | 10 | 0
+ 2 | 20 | 0
+ 3 | 30 | 0
+ 4 | 40 | 0
+ 5 | 50 | 0
+ 6 | 60 | 0
+ 7 | 70 | 0
+ 8 | 80 | 0
+ 9 | 90 | 0
+ 10 | 100 | 0
+(10 rows)
+
-- ALT flatten: (A | (B | C))+ -> (a | b | c)+
EXPLAIN (COSTS OFF)
SELECT COUNT(*) OVER w FROM rpr_plan
@@ -3049,6 +3195,26 @@ WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
-> Seq Scan on rpr_plan
(6 rows)
+-- Data execution: ALT dedup produces correct results
+SELECT id, val, count(*) OVER w AS cnt
+FROM rpr_plan
+WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN ((A | B | A)+) DEFINE A AS val <= 50, B AS val > 50);
+ id | val | cnt
+----+-----+-----
+ 1 | 10 | 10
+ 2 | 20 | 0
+ 3 | 30 | 0
+ 4 | 40 | 0
+ 5 | 50 | 0
+ 6 | 60 | 0
+ 7 | 70 | 0
+ 8 | 80 | 0
+ 9 | 90 | 0
+ 10 | 100 | 0
+(10 rows)
+
-- Quantifier multiply: (A{2}){3} -> a{6}
EXPLAIN (COSTS OFF)
SELECT COUNT(*) OVER w FROM rpr_plan
@@ -3468,6 +3634,26 @@ WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
-> Seq Scan on rpr_plan
(6 rows)
+-- Data execution: GROUP unwrap produces correct results
+SELECT id, val, count(*) OVER w AS cnt
+FROM rpr_plan
+WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP TO NEXT ROW
+ PATTERN ((A | B | C)) DEFINE A AS val <= 30, B AS val <= 60, C AS val > 60);
+ id | val | cnt
+----+-----+-----
+ 1 | 10 | 1
+ 2 | 20 | 1
+ 3 | 30 | 1
+ 4 | 40 | 1
+ 5 | 50 | 1
+ 6 | 60 | 1
+ 7 | 70 | 1
+ 8 | 80 | 1
+ 9 | 90 | 1
+ 10 | 100 | 1
+(10 rows)
+
-- Reluctant optimization bypass: VAR merge
-- A+? A stays as a+? a (greedy A+ A merges to a{2,})
EXPLAIN (COSTS OFF)
@@ -3612,6 +3798,134 @@ WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
-> Seq Scan on rpr_plan
(6 rows)
+-- Duplicate GROUP removal: ((A | B)+ | (A | B)+) -> (a | b)+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) OVER w FROM rpr_plan
+WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((A | B)+ | (A | B)+) DEFINE A AS val <= 50, B AS val > 50);
+ QUERY PLAN
+-------------------------------------------------------------------------------
+ WindowAgg
+ Window: w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+ Pattern: (a | b)+
+ -> Sort
+ Sort Key: id
+ -> Seq Scan on rpr_plan
+(6 rows)
+
+-- Consecutive VAR merge with zero-min: A* A+ -> a+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) OVER w FROM rpr_plan
+WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A* A+) DEFINE A AS val > 0);
+ QUERY PLAN
+-------------------------------------------------------------------------------
+ WindowAgg
+ Window: w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+ Pattern: a+"
+ -> Sort
+ Sort Key: id
+ -> Seq Scan on rpr_plan
+(6 rows)
+
+-- Consecutive VAR merge (4-element): A A{2} A+ A{3} -> a{7,}
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) OVER w FROM rpr_plan
+WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A A{2} A+ A{3}) DEFINE A AS val > 0);
+ QUERY PLAN
+-------------------------------------------------------------------------------
+ WindowAgg
+ Window: w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+ Pattern: a{7,}"
+ -> Sort
+ Sort Key: id
+ -> Seq Scan on rpr_plan
+(6 rows)
+
+-- PREFIX+SUFFIX merge (5-way): A B A B (A B)+ A B A B -> (a b){5,}
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) OVER w FROM rpr_plan
+WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A B A B (A B)+ A B A B)
+ DEFINE A AS val <= 50, B AS val > 50);
+ QUERY PLAN
+-------------------------------------------------------------------------------
+ WindowAgg
+ Window: w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+ Pattern: (a' b'){5,}"
+ -> Sort
+ Sort Key: id
+ -> Seq Scan on rpr_plan
+(6 rows)
+
+-- Unwrap single-item ALT after dedup: (A | A)+ -> a+
+-- ALT dedup reduces to single-item, then GROUP unwrap
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) OVER w FROM rpr_plan
+WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((A | A)+) DEFINE A AS val > 0);
+ QUERY PLAN
+-------------------------------------------------------------------------------
+ WindowAgg
+ Window: w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+ Pattern: a+"
+ -> Sort
+ Sort Key: id
+ -> Seq Scan on rpr_plan
+(6 rows)
+
+-- GROUP{1,1} to SEQ with flatten: ((A B)(C D)) -> a b c d
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) OVER w FROM rpr_plan
+WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (((A B)(C D)))
+ DEFINE A AS val <= 25, B AS val > 25 AND val <= 50,
+ C AS val > 50 AND val <= 75, D AS val > 75);
+ QUERY PLAN
+-------------------------------------------------------------------------------
+ WindowAgg
+ Window: w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+ Pattern: a b c d
+ -> Sort
+ Sort Key: id
+ -> Seq Scan on rpr_plan
+(6 rows)
+
+-- Nested ALT pattern: ((A B) | C) D | A B C
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) OVER w FROM rpr_plan
+WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (((A B) | C) D | A B C)
+ DEFINE A AS val <= 25, B AS val > 25 AND val <= 50,
+ C AS val > 50 AND val <= 75, D AS val > 75);
+ QUERY PLAN
+-------------------------------------------------------------------------------
+ WindowAgg
+ Window: w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+ Pattern: ((a b | c) d | a b c)
+ -> Sort
+ Sort Key: id
+ -> Seq Scan on rpr_plan
+(6 rows)
+
+-- Nested ALT with unbounded: ((A+ B) | C) D | A B C
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) OVER w FROM rpr_plan
+WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (((A+ B) | C) D | A B C)
+ DEFINE A AS val <= 25, B AS val > 25 AND val <= 50,
+ C AS val > 50 AND val <= 75, D AS val > 75);
+ QUERY PLAN
+-------------------------------------------------------------------------------
+ WindowAgg
+ Window: w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+ Pattern: ((a+" b | c) d | a b c)
+ -> Sort
+ Sort Key: id
+ -> Seq Scan on rpr_plan
+(6 rows)
+
-- ============================================================
-- Absorption Flag Display Tests
-- ============================================================
@@ -3801,6 +4115,28 @@ WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND 10 FOLLOWING
-> Seq Scan on rpr_plan
(6 rows)
+-- Reluctant {1}? quantifier deparse
+-- A{1}? is a reluctant {1,1} quantifier. The deparse code must
+-- output "{1}" explicitly to disambiguate from a bare "?" quantifier
+-- (which would mean {0,1}).
+EXPLAIN (COSTS OFF) SELECT count(*) OVER w
+FROM rpr_plan
+WINDOW w AS (
+ ORDER BY val
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A{1}? B)
+ DEFINE A AS val > 0, B AS val > 0
+);
+ QUERY PLAN
+--------------------------------------------------------------------------------
+ WindowAgg
+ Window: w AS (ORDER BY val ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+ Pattern: a{1}? b
+ -> Sort
+ Sort Key: val
+ -> Seq Scan on rpr_plan
+(6 rows)
+
-- ============================================================
-- Absorption Analysis Tests
-- ============================================================
@@ -4529,7 +4865,8 @@ ORDER BY category;
ERROR: syntax error at or near "GROUP"
LINE 12: GROUP BY category
^
--- Expected: ERROR (GROUP BY with window RPR not supported)
+-- Expected: ERROR: syntax error at or near "GROUP"
+-- (GROUP BY after WINDOW clause is not valid SQL syntax)
-- ============================================================
-- Subquery and CTE Tests
-- Files: planner.c, prepjointree.c
@@ -5007,7 +5344,8 @@ CREATE TABLE rpr_sort (id INT, category VARCHAR(10), val INT);
INSERT INTO rpr_sort VALUES
(1, 'A', 30), (2, 'B', 20), (3, 'A', 10),
(4, 'B', 40), (5, 'A', 50), (6, 'B', 60);
--- RPR with GROUP BY
+-- RPR with GROUP BY (aggregate in DEFINE → ERROR before GROUP BY interaction)
+-- Expected: ERROR: aggregate functions are not allowed in DEFINE
SELECT category,
COUNT(*) as group_cnt,
MAX(val) as max_val,
@@ -5024,7 +5362,8 @@ ORDER BY category;
ERROR: aggregate functions are not allowed in DEFINE
LINE 11: DEFINE A AS COUNT(*) > 0
^
--- RPR with HAVING
+-- RPR with HAVING (same aggregate-in-DEFINE error)
+-- Expected: ERROR: aggregate functions are not allowed in DEFINE
SELECT category,
COUNT(*) as group_cnt,
COUNT(*) OVER w as window_cnt
@@ -5787,6 +6126,3 @@ FROM (SELECT id, val,
(2 rows)
DROP TABLE rpr_plan;
--- ============================================================
--- End of rpr_base.sql
--- ============================================================
diff --git a/src/test/regress/expected/rpr_explain.out b/src/test/regress/expected/rpr_explain.out
index 340408cc454..817269021f4 100644
--- a/src/test/regress/expected/rpr_explain.out
+++ b/src/test/regress/expected/rpr_explain.out
@@ -6,8 +6,9 @@
-- This test suite validates EXPLAIN output for RPR queries,
-- including NFA statistics shown in EXPLAIN ANALYZE:
-- - NFA States: peak, total, merged
--- - NFA Contexts: peak, total, absorbed, skipped
+-- - NFA Contexts: peak, total, pruned
-- - NFA: matched (len min/max/avg), mismatched (len min/max/avg)
+-- - NFA: absorbed (len min/max/avg), skipped (len min/max/avg)
-- - Pattern deparse formatting
-- - Multiple output formats (text, JSON, XML)
--
@@ -434,7 +435,7 @@ WINDOW w AS (
(9 rows)
DROP VIEW rpr_v;
--- Grouped pattern with quantifier - state merging
+-- Grouped pattern with quantifier - state count with grouping
CREATE TEMP VIEW rpr_v AS
SELECT count(*) OVER w
FROM generate_series(1, 60) AS s(v)
@@ -637,7 +638,7 @@ WINDOW w AS (
(9 rows)
DROP VIEW rpr_v;
--- High state merging - alternation with plus quantifier
+-- High state count - alternation with plus quantifier
CREATE TEMP VIEW rpr_v AS
SELECT count(*) OVER w
FROM generate_series(1, 100) AS s(v)
@@ -758,7 +759,7 @@ WINDOW w AS (
DROP VIEW rpr_v;
-- ============================================================
--- Context Statistics Tests (peak, total, absorbed, skipped)
+-- Context Statistics Tests (peak, total, pruned + absorbed/skipped)
-- ============================================================
-- Context absorption with unbounded quantifier at start
CREATE TEMP VIEW rpr_v AS
@@ -1047,7 +1048,7 @@ WINDOW w AS (
(9 rows)
DROP VIEW rpr_v;
--- Mix of short and long matches
+-- Uniform match length with mismatches from gap rows (v%20 = 11..15)
CREATE TEMP VIEW rpr_v AS
SELECT count(*) OVER w
FROM generate_series(1, 100) AS s(v)
@@ -1094,8 +1095,8 @@ DROP VIEW rpr_v;
-- ============================================================
-- Mismatch Length Statistics Tests
-- ============================================================
--- Pattern that causes mismatches with length > 1
--- Mismatch happens when partial match fails after processing multiple rows
+-- Pattern with complete match every cycle: 0 mismatched
+-- A(1,2,3) B(4,5) C(6) repeats perfectly; X rows are pruned, not mismatched
CREATE TEMP VIEW rpr_v AS
SELECT count(*) OVER w
FROM (
@@ -1374,87 +1375,6 @@ WINDOW w AS (
]
(1 row)
-DROP VIEW rpr_v;
--- ============================================================
--- XML Format Tests
--- ============================================================
--- XML format output
-CREATE TEMP VIEW rpr_v AS
-SELECT count(*) OVER w
-FROM generate_series(1, 30) AS s(v)
-WINDOW w AS (
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN (A B)
- DEFINE A AS v % 2 = 1, B AS v % 2 = 0
-);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), 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, FORMAT XML)
-SELECT count(*) OVER w
-FROM generate_series(1, 30) AS s(v)
-WINDOW w AS (
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN (A B)
- DEFINE A AS v % 2 = 1, B AS v % 2 = 0
-)');
- rpr_explain_filter
---------------------------------------------------------------------------------
- <explain xmlns="http://www.postgresql.org/2009/explain"; +
- <Query> +
- <Plan> +
- <Node-Type>WindowAgg</Node-Type> +
- <Parallel-Aware>false</Parallel-Aware> +
- <Async-Capable>false</Async-Capable> +
- <Actual-Rows>30.00</Actual-Rows> +
- <Actual-Loops>1</Actual-Loops> +
- <Disabled>false</Disabled> +
- <Window>w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)</Window>+
- <Pattern>a b</Pattern> +
- <Storage>Memory</Storage> +
- <Maximum-Storage>0</Maximum-Storage> +
- <NFA-States-Peak>2</NFA-States-Peak> +
- <NFA-States-Total>31</NFA-States-Total> +
- <NFA-States-Merged>0</NFA-States-Merged> +
- <NFA-Contexts-Peak>2</NFA-Contexts-Peak> +
- <NFA-Contexts-Total>31</NFA-Contexts-Total> +
- <NFA-Contexts-Absorbed>0</NFA-Contexts-Absorbed> +
- <NFA-Contexts-Skipped>15</NFA-Contexts-Skipped> +
- <NFA-Contexts-Pruned>0</NFA-Contexts-Pruned> +
- <NFA-Matched>15</NFA-Matched> +
- <NFA-Mismatched>0</NFA-Mismatched> +
- <NFA-Match-Length-Min>2</NFA-Match-Length-Min> +
- <NFA-Match-Length-Max>2</NFA-Match-Length-Max> +
- <NFA-Match-Length-Avg>2.0</NFA-Match-Length-Avg> +
- <NFA-Skipped-Length-Min>1</NFA-Skipped-Length-Min> +
- <NFA-Skipped-Length-Max>1</NFA-Skipped-Length-Max> +
- <NFA-Skipped-Length-Avg>1.0</NFA-Skipped-Length-Avg> +
- <Plans> +
- <Plan> +
- <Node-Type>Function Scan</Node-Type> +
- <Parent-Relationship>Outer</Parent-Relationship> +
- <Parallel-Aware>false</Parallel-Aware> +
- <Async-Capable>false</Async-Capable> +
- <Function-Name>generate_series</Function-Name> +
- <Alias>s</Alias> +
- <Actual-Rows>30.00</Actual-Rows> +
- <Actual-Loops>1</Actual-Loops> +
- <Disabled>false</Disabled> +
- </Plan> +
- </Plans> +
- </Plan> +
- <Triggers> +
- </Triggers> +
- </Query> +
- </explain>
-(1 row)
-
DROP VIEW rpr_v;
-- JSON format with mismatch statistics
-- Pattern A B C expects 1,2,3 but gets 1,2,4 twice causing mismatches
@@ -1618,6 +1538,87 @@ WINDOW w AS (
]
(1 row)
+DROP VIEW rpr_v;
+-- ============================================================
+-- XML Format Tests
+-- ============================================================
+-- XML format output
+CREATE TEMP VIEW rpr_v AS
+SELECT count(*) OVER w
+FROM generate_series(1, 30) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A B)
+ DEFINE A AS v % 2 = 1, B AS v % 2 = 0
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), 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, FORMAT XML)
+SELECT count(*) OVER w
+FROM generate_series(1, 30) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A B)
+ DEFINE A AS v % 2 = 1, B AS v % 2 = 0
+)');
+ rpr_explain_filter
+--------------------------------------------------------------------------------
+ <explain xmlns="http://www.postgresql.org/2009/explain"; +
+ <Query> +
+ <Plan> +
+ <Node-Type>WindowAgg</Node-Type> +
+ <Parallel-Aware>false</Parallel-Aware> +
+ <Async-Capable>false</Async-Capable> +
+ <Actual-Rows>30.00</Actual-Rows> +
+ <Actual-Loops>1</Actual-Loops> +
+ <Disabled>false</Disabled> +
+ <Window>w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)</Window>+
+ <Pattern>a b</Pattern> +
+ <Storage>Memory</Storage> +
+ <Maximum-Storage>0</Maximum-Storage> +
+ <NFA-States-Peak>2</NFA-States-Peak> +
+ <NFA-States-Total>31</NFA-States-Total> +
+ <NFA-States-Merged>0</NFA-States-Merged> +
+ <NFA-Contexts-Peak>2</NFA-Contexts-Peak> +
+ <NFA-Contexts-Total>31</NFA-Contexts-Total> +
+ <NFA-Contexts-Absorbed>0</NFA-Contexts-Absorbed> +
+ <NFA-Contexts-Skipped>15</NFA-Contexts-Skipped> +
+ <NFA-Contexts-Pruned>0</NFA-Contexts-Pruned> +
+ <NFA-Matched>15</NFA-Matched> +
+ <NFA-Mismatched>0</NFA-Mismatched> +
+ <NFA-Match-Length-Min>2</NFA-Match-Length-Min> +
+ <NFA-Match-Length-Max>2</NFA-Match-Length-Max> +
+ <NFA-Match-Length-Avg>2.0</NFA-Match-Length-Avg> +
+ <NFA-Skipped-Length-Min>1</NFA-Skipped-Length-Min> +
+ <NFA-Skipped-Length-Max>1</NFA-Skipped-Length-Max> +
+ <NFA-Skipped-Length-Avg>1.0</NFA-Skipped-Length-Avg> +
+ <Plans> +
+ <Plan> +
+ <Node-Type>Function Scan</Node-Type> +
+ <Parent-Relationship>Outer</Parent-Relationship> +
+ <Parallel-Aware>false</Parallel-Aware> +
+ <Async-Capable>false</Async-Capable> +
+ <Function-Name>generate_series</Function-Name> +
+ <Alias>s</Alias> +
+ <Actual-Rows>30.00</Actual-Rows> +
+ <Actual-Loops>1</Actual-Loops> +
+ <Disabled>false</Disabled> +
+ </Plan> +
+ </Plans> +
+ </Plan> +
+ <Triggers> +
+ </Triggers> +
+ </Query> +
+ </explain>
+(1 row)
+
DROP VIEW rpr_v;
-- ============================================================
-- Multiple Partitions Tests
diff --git a/src/test/regress/expected/rpr_nfa.out b/src/test/regress/expected/rpr_nfa.out
index 03ee174d359..c8464e8fcf4 100644
--- a/src/test/regress/expected/rpr_nfa.out
+++ b/src/test/regress/expected/rpr_nfa.out
@@ -322,6 +322,161 @@ WINDOW w AS (
5 | {_} | |
(5 rows)
+-- Absorption with fixed suffix: A+ B
+WITH test_absorb_suffix AS (
+ SELECT * FROM (VALUES
+ (1, ARRAY['A']),
+ (2, ARRAY['A']),
+ (3, ARRAY['A']),
+ (4, ARRAY['B']),
+ (5, ARRAY['X'])
+ ) AS t(id, flags)
+)
+SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
+FROM test_absorb_suffix
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP TO NEXT ROW
+ PATTERN (A+ B)
+ DEFINE
+ A AS 'A' = ANY(flags),
+ B AS 'B' = ANY(flags)
+);
+ id | flags | match_start | match_end
+----+-------+-------------+-----------
+ 1 | {A} | 1 | 4
+ 2 | {A} | 2 | 4
+ 3 | {A} | 3 | 4
+ 4 | {B} | |
+ 5 | {X} | |
+(5 rows)
+
+-- Per-branch absorption with ALT: B+ C | B+ D
+WITH test_absorb_alt AS (
+ SELECT * FROM (VALUES
+ (1, ARRAY['B']),
+ (2, ARRAY['B']),
+ (3, ARRAY['B']),
+ (4, ARRAY['D']),
+ (5, ARRAY['X'])
+ ) AS t(id, flags)
+)
+SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
+FROM test_absorb_alt
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP TO NEXT ROW
+ PATTERN (B+ C | B+ D)
+ DEFINE
+ B AS 'B' = ANY(flags),
+ C AS 'C' = ANY(flags),
+ D AS 'D' = ANY(flags)
+);
+ id | flags | match_start | match_end
+----+-------+-------------+-----------
+ 1 | {B} | 1 | 4
+ 2 | {B} | 2 | 4
+ 3 | {B} | 3 | 4
+ 4 | {D} | |
+ 5 | {X} | |
+(5 rows)
+
+-- Non-absorbable: A B+ (unbounded not in first position)
+WITH test_no_absorb AS (
+ SELECT * FROM (VALUES
+ (1, ARRAY['A']),
+ (2, ARRAY['B']),
+ (3, ARRAY['B']),
+ (4, ARRAY['B']),
+ (5, ARRAY['X'])
+ ) AS t(id, flags)
+)
+SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
+FROM test_no_absorb
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP TO NEXT ROW
+ PATTERN (A B+)
+ DEFINE
+ A AS 'A' = ANY(flags),
+ B AS 'B' = ANY(flags)
+);
+ id | flags | match_start | match_end
+----+-------+-------------+-----------
+ 1 | {A} | 1 | 4
+ 2 | {B} | |
+ 3 | {B} | |
+ 4 | {B} | |
+ 5 | {X} | |
+(5 rows)
+
+-- GROUP merge enables absorption: (A B) (A B)+ optimized to (A B){2,}
+WITH test_absorb_group AS (
+ SELECT * FROM (VALUES
+ (1, ARRAY['A']),
+ (2, ARRAY['B']),
+ (3, ARRAY['A']),
+ (4, ARRAY['B']),
+ (5, ARRAY['A']),
+ (6, ARRAY['B']),
+ (7, ARRAY['X'])
+ ) AS t(id, flags)
+)
+SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
+FROM test_absorb_group
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP TO NEXT ROW
+ PATTERN ((A B) (A B)+)
+ DEFINE
+ A AS 'A' = ANY(flags),
+ B AS 'B' = ANY(flags)
+);
+ id | flags | match_start | match_end
+----+-------+-------------+-----------
+ 1 | {A} | 1 | 6
+ 2 | {B} | |
+ 3 | {A} | 3 | 6
+ 4 | {B} | |
+ 5 | {A} | |
+ 6 | {B} | |
+ 7 | {X} | |
+(7 rows)
+
+-- Multiple unbounded: A+ B+ (first element unbounded enables absorption)
+WITH test_multi_unbounded AS (
+ SELECT * FROM (VALUES
+ (1, ARRAY['A']),
+ (2, ARRAY['A']),
+ (3, ARRAY['B']),
+ (4, ARRAY['B']),
+ (5, ARRAY['X'])
+ ) AS t(id, flags)
+)
+SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
+FROM test_multi_unbounded
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP TO NEXT ROW
+ PATTERN (A+ B+)
+ DEFINE
+ A AS 'A' = ANY(flags),
+ B AS 'B' = ANY(flags)
+);
+ id | flags | match_start | match_end
+----+-------+-------------+-----------
+ 1 | {A} | 1 | 4
+ 2 | {A} | 2 | 4
+ 3 | {B} | |
+ 4 | {B} | |
+ 5 | {X} | |
+(5 rows)
+
-- ============================================================
-- Context Lifecycle
-- ============================================================
@@ -674,7 +829,8 @@ WINDOW w AS (
(5 rows)
-- Optional reluctant group: (A B)?? C
--- nfa_advance_begin: reluctant prefers skip (0 iterations) over enter
+-- nfa_advance_begin: reluctant tries skip first, but skip path needs C
+-- at row 1 which is A → skip fails. Enter path succeeds: A(1) B(2) C(3).
WITH test_optional_reluctant AS (
SELECT * FROM (VALUES
(1, ARRAY['A']),
@@ -734,6 +890,44 @@ WINDOW w AS (
4 | {B} | |
(4 rows)
+-- Reluctant optional group skip-to-FIN
+-- When a reluctant optional group's skip path reaches FIN, the group
+-- entry path is abandoned (nodeWindowAgg.c nfa_advance_begin).
+-- Pattern: C (A B)?? -- after C matches, the reluctant group (A B)??
+-- prefers to skip. Skip goes to FIN (group is last element), so
+-- the match completes with just C.
+WITH test_begin_skip_fin AS (
+ SELECT * FROM (VALUES
+ (1, ARRAY['C']),
+ (2, ARRAY['A']),
+ (3, ARRAY['B']),
+ (4, ARRAY['C','A']),
+ (5, ARRAY['B'])
+ ) AS t(id, flags)
+)
+SELECT id, flags,
+ first_value(id) OVER w AS match_start,
+ last_value(id) OVER w AS match_end
+FROM test_begin_skip_fin
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP TO NEXT ROW
+ PATTERN (C (A B)??)
+ DEFINE
+ C AS 'C' = ANY(flags),
+ A AS 'A' = ANY(flags),
+ B AS 'B' = ANY(flags)
+);
+ id | flags | match_start | match_end
+----+-------+-------------+-----------
+ 1 | {C} | 1 | 1
+ 2 | {A} | |
+ 3 | {B} | |
+ 4 | {C,A} | 4 | 4
+ 5 | {B} | |
+(5 rows)
+
-- ============================================================
-- Match Phase
-- ============================================================
@@ -1664,6 +1858,64 @@ WINDOW w AS (
4 | {B} | |
(4 rows)
+-- A+? B (reluctant plus): exits A at first B availability
+-- (Same scenario as greedy-vs-reluctant comparison above; retained for
+-- standalone quantifier coverage alongside A{1,3}? and A{2,3}? below)
+WITH test_reluctant_plus AS (
+ SELECT * FROM (VALUES
+ (1, ARRAY['A','_']),
+ (2, ARRAY['A','_']),
+ (3, ARRAY['A','B']),
+ (4, ARRAY['B','_'])
+ ) AS t(id, flags)
+)
+SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
+FROM test_reluctant_plus
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A+? B)
+ DEFINE
+ A AS 'A' = ANY(flags),
+ B AS 'B' = ANY(flags)
+);
+ id | flags | match_start | match_end
+----+-------+-------------+-----------
+ 1 | {A,_} | 1 | 3
+ 2 | {A,_} | |
+ 3 | {A,B} | |
+ 4 | {B,_} | |
+(4 rows)
+
+-- A{1,3}? B (reluctant bounded): same data, bounded quantifier
+WITH test_reluctant_bounded AS (
+ SELECT * FROM (VALUES
+ (1, ARRAY['A','_']),
+ (2, ARRAY['A','_']),
+ (3, ARRAY['A','B']),
+ (4, ARRAY['B','_'])
+ ) AS t(id, flags)
+)
+SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
+FROM test_reluctant_bounded
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A{1,3}? B)
+ DEFINE
+ A AS 'A' = ANY(flags),
+ B AS 'B' = ANY(flags)
+);
+ id | flags | match_start | match_end
+----+-------+-------------+-----------
+ 1 | {A,_} | 1 | 3
+ 2 | {A,_} | |
+ 3 | {A,B} | |
+ 4 | {B,_} | |
+(4 rows)
+
-- ============================================================
-- Pathological Pattern Runtime Protection
-- ============================================================
@@ -2030,6 +2282,283 @@ WINDOW w AS (
3 | {B} | 3 | 3
(3 rows)
+-- Overlapping match: A B C D E | B C D | C D E F (SKIP PAST LAST ROW)
+WITH test_overlap1 AS (
+ SELECT * FROM (VALUES
+ (1, ARRAY['A']),
+ (2, ARRAY['B']),
+ (3, ARRAY['C']),
+ (4, ARRAY['D']),
+ (5, ARRAY['E']),
+ (6, ARRAY['F'])
+ ) AS t(id, flags)
+)
+SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
+FROM test_overlap1
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A B C D E | B C D | C D E F)
+ DEFINE
+ A AS 'A' = ANY(flags),
+ B AS 'B' = ANY(flags),
+ C AS 'C' = ANY(flags),
+ D AS 'D' = ANY(flags),
+ E AS 'E' = ANY(flags),
+ F AS 'F' = ANY(flags)
+);
+ id | flags | match_start | match_end
+----+-------+-------------+-----------
+ 1 | {A} | 1 | 5
+ 2 | {B} | |
+ 3 | {C} | |
+ 4 | {D} | |
+ 5 | {E} | |
+ 6 | {F} | |
+(6 rows)
+
+-- Same with SKIP TO NEXT ROW: three overlapping matches
+WITH test_overlap1 AS (
+ SELECT * FROM (VALUES
+ (1, ARRAY['A']),
+ (2, ARRAY['B']),
+ (3, ARRAY['C']),
+ (4, ARRAY['D']),
+ (5, ARRAY['E']),
+ (6, ARRAY['F'])
+ ) AS t(id, flags)
+)
+SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
+FROM test_overlap1
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP TO NEXT ROW
+ PATTERN (A B C D E | B C D | C D E F)
+ DEFINE
+ A AS 'A' = ANY(flags),
+ B AS 'B' = ANY(flags),
+ C AS 'C' = ANY(flags),
+ D AS 'D' = ANY(flags),
+ E AS 'E' = ANY(flags),
+ F AS 'F' = ANY(flags)
+);
+ id | flags | match_start | match_end
+----+-------+-------------+-----------
+ 1 | {A} | 1 | 5
+ 2 | {B} | 2 | 4
+ 3 | {C} | 3 | 6
+ 4 | {D} | |
+ 5 | {E} | |
+ 6 | {F} | |
+(6 rows)
+
+-- Longer pattern fails, shorter survives: A+ B C D E | B+ C
+WITH test_overlap1b AS (
+ SELECT * FROM (VALUES
+ (1, ARRAY['A']),
+ (2, ARRAY['B']),
+ (3, ARRAY['C']),
+ (4, ARRAY['D']),
+ (5, ARRAY['X'])
+ ) AS t(id, flags)
+)
+SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
+FROM test_overlap1b
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A+ B C D E | B+ C)
+ DEFINE
+ A AS 'A' = ANY(flags),
+ B AS 'B' = ANY(flags),
+ C AS 'C' = ANY(flags),
+ D AS 'D' = ANY(flags),
+ E AS 'E' = ANY(flags)
+);
+ id | flags | match_start | match_end
+----+-------+-------------+-----------
+ 1 | {A} | |
+ 2 | {B} | 2 | 3
+ 3 | {C} | |
+ 4 | {D} | |
+ 5 | {X} | |
+(5 rows)
+
+-- Long B sequence with different endings: A B+ C | B+ D
+WITH test_overlap2 AS (
+ SELECT * FROM (VALUES
+ (1, ARRAY['A']),
+ (2, ARRAY['B']),
+ (3, ARRAY['B']),
+ (4, ARRAY['B']),
+ (5, ARRAY['B']),
+ (6, ARRAY['C']),
+ (7, ARRAY['B']),
+ (8, ARRAY['B']),
+ (9, ARRAY['B']),
+ (10, ARRAY['D'])
+ ) AS t(id, flags)
+)
+SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
+FROM test_overlap2
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP TO NEXT ROW
+ PATTERN (A B+ C | B+ D)
+ DEFINE
+ A AS 'A' = ANY(flags),
+ B AS 'B' = ANY(flags),
+ C AS 'C' = ANY(flags),
+ D AS 'D' = ANY(flags)
+);
+ id | flags | match_start | match_end
+----+-------+-------------+-----------
+ 1 | {A} | 1 | 6
+ 2 | {B} | |
+ 3 | {B} | |
+ 4 | {B} | |
+ 5 | {B} | |
+ 6 | {C} | |
+ 7 | {B} | 7 | 10
+ 8 | {B} | 8 | 10
+ 9 | {B} | 9 | 10
+ 10 | {D} | |
+(10 rows)
+
+-- Greedy with late failure ("betrayal"): A B C+ D | A B
+WITH test_betrayal AS (
+ SELECT * FROM (VALUES
+ (1, ARRAY['A']),
+ (2, ARRAY['B']),
+ (3, ARRAY['C']),
+ (4, ARRAY['C']),
+ (5, ARRAY['C']),
+ (6, ARRAY['E'])
+ ) AS t(id, flags)
+)
+SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
+FROM test_betrayal
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A B C+ D | A B)
+ DEFINE
+ A AS 'A' = ANY(flags),
+ B AS 'B' = ANY(flags),
+ C AS 'C' = ANY(flags),
+ D AS 'D' = ANY(flags)
+);
+ id | flags | match_start | match_end
+----+-------+-------------+-----------
+ 1 | {A} | 1 | 2
+ 2 | {B} | |
+ 3 | {C} | |
+ 4 | {C} | |
+ 5 | {C} | |
+ 6 | {E} | |
+(6 rows)
+
+-- Multiple TRUE per row: overlapping pattern variables
+WITH test_multi_true AS (
+ SELECT * FROM (VALUES
+ (1, ARRAY['A','B']),
+ (2, ARRAY['B','C']),
+ (3, ARRAY['C','D']),
+ (4, ARRAY['D','E']),
+ (5, ARRAY['E','_'])
+ ) AS t(id, flags)
+)
+SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
+FROM test_multi_true
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A B C D E)
+ DEFINE
+ A AS 'A' = ANY(flags),
+ B AS 'B' = ANY(flags),
+ C AS 'C' = ANY(flags),
+ D AS 'D' = ANY(flags),
+ E AS 'E' = ANY(flags)
+);
+ id | flags | match_start | match_end
+----+-------+-------------+-----------
+ 1 | {A,B} | 1 | 5
+ 2 | {B,C} | |
+ 3 | {C,D} | |
+ 4 | {D,E} | |
+ 5 | {E,_} | |
+(5 rows)
+
+-- Diagonal pattern with shifted multi-TRUE overlap
+WITH test_diagonal AS (
+ SELECT * FROM (VALUES
+ (1, ARRAY['A','_']),
+ (2, ARRAY['B','A']),
+ (3, ARRAY['C','B']),
+ (4, ARRAY['D','C']),
+ (5, ARRAY['_','D'])
+ ) AS t(id, flags)
+)
+SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
+FROM test_diagonal
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP TO NEXT ROW
+ PATTERN (A B C D)
+ DEFINE
+ A AS 'A' = ANY(flags),
+ B AS 'B' = ANY(flags),
+ C AS 'C' = ANY(flags),
+ D AS 'D' = ANY(flags)
+);
+ id | flags | match_start | match_end
+----+-------+-------------+-----------
+ 1 | {A,_} | 1 | 4
+ 2 | {B,A} | 2 | 5
+ 3 | {C,B} | |
+ 4 | {D,C} | |
+ 5 | {_,D} | |
+(5 rows)
+
+-- ((A | B) C)+ - alternation inside group with outer quantifier
+WITH test_alt_group AS (
+ SELECT * FROM (VALUES
+ (1, ARRAY['A']),
+ (2, ARRAY['C']),
+ (3, ARRAY['B']),
+ (4, ARRAY['C']),
+ (5, ARRAY['X'])
+ ) AS t(id, flags)
+)
+SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
+FROM test_alt_group
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (((A | B) C)+)
+ DEFINE
+ A AS 'A' = ANY(flags),
+ B AS 'B' = ANY(flags),
+ C AS 'C' = ANY(flags)
+);
+ id | flags | match_start | match_end
+----+-------+-------------+-----------
+ 1 | {A} | 1 | 4
+ 2 | {C} | |
+ 3 | {B} | |
+ 4 | {C} | |
+ 5 | {X} | |
+(5 rows)
+
-- ============================================================
-- Deep Nested Groups
-- ============================================================
@@ -2227,6 +2756,69 @@ WINDOW w AS (
5 | {C} | |
(5 rows)
+-- (A B){2} - group with exact quantifier
+WITH test_group_exact AS (
+ SELECT * FROM (VALUES
+ (1, ARRAY['A']),
+ (2, ARRAY['B']),
+ (3, ARRAY['A']),
+ (4, ARRAY['B']),
+ (5, ARRAY['X'])
+ ) AS t(id, flags)
+)
+SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
+FROM test_group_exact
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN ((A B){2})
+ DEFINE
+ A AS 'A' = ANY(flags),
+ B AS 'B' = ANY(flags)
+);
+ id | flags | match_start | match_end
+----+-------+-------------+-----------
+ 1 | {A} | 1 | 4
+ 2 | {B} | |
+ 3 | {A} | |
+ 4 | {B} | |
+ 5 | {X} | |
+(5 rows)
+
+-- Nested END->END fast-forward
+-- When an inner group has a nullable body and count < min, the
+-- fast-forward path exits through the outer END, incrementing
+-- the outer group's count (nodeWindowAgg.c nfa_advance_end).
+-- Pattern: ((A?){2,3}){2,3} -- nested groups, neither collapses
+-- because the optimizer cannot safely multiply non-exact quantifiers.
+-- Data has no A rows, forcing all-empty iterations via fast-forward.
+WITH test_nested_ff AS (
+ SELECT * FROM (VALUES
+ (1, ARRAY['B']),
+ (2, ARRAY['B']),
+ (3, ARRAY['B'])
+ ) AS t(id, flags)
+)
+SELECT id, flags,
+ first_value(id) OVER w AS match_start,
+ last_value(id) OVER w AS match_end
+FROM test_nested_ff
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP TO NEXT ROW
+ PATTERN (((A?){2,3}){2,3})
+ DEFINE
+ A AS 'A' = ANY(flags)
+);
+ id | flags | match_start | match_end
+----+-------+-------------+-----------
+ 1 | {B} | |
+ 2 | {B} | |
+ 3 | {B} | |
+(3 rows)
+
-- ============================================================
-- SKIP Options (Runtime)
-- ============================================================
@@ -2390,6 +2982,8 @@ ORDER BY mode, id;
-- ============================================================
-- INITIAL Mode (Runtime)
+-- Placeholder: INITIAL is not yet implemented (syntax error).
+-- Kept here so tests convert to runtime tests when implemented.
-- ============================================================
-- INITIAL mode (not yet supported - produces syntax error)
WITH test_initial_mode AS (
@@ -2597,6 +3191,80 @@ WINDOW w AS (
5 | {B} | |
(5 rows)
+-- N FOLLOWING + SKIP TO NEXT ROW: overlapping matches bounded by frame
+-- Row 1: frame [1,4], A(1-3) B(4) -> match
+-- Row 2: frame [2,5], A(2-3) B(4) -> match
+-- Row 3: frame [3,6], A(3) B(4) -> match
+-- Row 5: frame [5,6], A(5) B(6) -> match
+WITH test_n_skip_next AS (
+ SELECT * FROM (VALUES
+ (1, ARRAY['A']),
+ (2, ARRAY['A']),
+ (3, ARRAY['A']),
+ (4, ARRAY['B']),
+ (5, ARRAY['A']),
+ (6, ARRAY['B'])
+ ) AS t(id, flags)
+)
+SELECT id, flags,
+ first_value(id) OVER w AS match_start,
+ last_value(id) OVER w AS match_end
+FROM test_n_skip_next
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND 3 FOLLOWING
+ AFTER MATCH SKIP TO NEXT ROW
+ PATTERN (A+ B)
+ DEFINE
+ A AS 'A' = ANY(flags),
+ B AS 'B' = ANY(flags)
+);
+ id | flags | match_start | match_end
+----+-------+-------------+-----------
+ 1 | {A} | 1 | 4
+ 2 | {A} | 2 | 4
+ 3 | {A} | 3 | 4
+ 4 | {B} | |
+ 5 | {A} | 5 | 6
+ 6 | {B} | |
+(6 rows)
+
+-- Frame exactly 1 row short of potential match
+-- From row 1: A A A B needs 4 rows but frame holds 3 -> no match
+-- From row 2: A A B fits in 3-row frame -> match
+WITH test_frame_one_short AS (
+ SELECT * FROM (VALUES
+ (1, ARRAY['A']),
+ (2, ARRAY['A']),
+ (3, ARRAY['A']),
+ (4, ARRAY['B']),
+ (5, ARRAY['A']),
+ (6, ARRAY['B'])
+ ) AS t(id, flags)
+)
+SELECT id, flags,
+ first_value(id) OVER w AS match_start,
+ last_value(id) OVER w AS match_end
+FROM test_frame_one_short
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING
+ AFTER MATCH SKIP TO NEXT ROW
+ PATTERN (A+ B)
+ DEFINE
+ A AS 'A' = ANY(flags),
+ B AS 'B' = ANY(flags)
+);
+ id | flags | match_start | match_end
+----+-------+-------------+-----------
+ 1 | {A} | |
+ 2 | {A} | 2 | 4
+ 3 | {A} | 3 | 4
+ 4 | {B} | |
+ 5 | {A} | 5 | 6
+ 6 | {B} | |
+(6 rows)
+
-- ============================================================
-- Special Partition Cases
-- ============================================================
@@ -3407,7 +4075,8 @@ WINDOW w AS (
-- (A?){0,3}: min=0, nullable inner.
-- A never matches. A? matches empty, min=0 satisfied immediately.
--- Expected: empty match (match_start = match_end) for every row
+-- Per standard: empty match expected for every row.
+-- XXX: visited bitmap blocks empty iteration → no match (same as {2,3})
WITH test_728_min0 AS (
SELECT * FROM (VALUES
(1, ARRAY['B']),
@@ -3436,7 +4105,8 @@ WINDOW w AS (
-- (A?){1,3}: min=1, nullable inner.
-- A never matches. Need 1 empty iteration to satisfy min=1.
--- Expected: empty match for every row
+-- Per standard: empty match expected for every row.
+-- XXX: visited bitmap blocks empty iteration → no match (same as {2,3})
WITH test_728_min1 AS (
SELECT * FROM (VALUES
(1, ARRAY['B']),
@@ -3497,7 +4167,8 @@ WINDOW w AS (
-- (A?){2,3} mixed: some rows match A, some don't
-- Rows 1-2: A matches, greedy takes 2 → min satisfied
-- Row 3: A doesn't match, needs 2 empty iterations for min=2
--- Expected: all rows produce matches
+-- XXX: Row 3 fails due to visited bitmap (same as pure empty {2,3})
+-- Row 4: A matches 1 real iter + 1 ff empty exit → match 4-4
WITH test_728_min2_mixed AS (
SELECT * FROM (VALUES
(1, ARRAY['A']),
diff --git a/src/test/regress/sql/rpr.sql b/src/test/regress/sql/rpr.sql
index a263074e3e9..8dbd3f9036a 100644
--- a/src/test/regress/sql/rpr.sql
+++ b/src/test/regress/sql/rpr.sql
@@ -1,5 +1,10 @@
--
--- Test for row pattern definition clause
+-- Test for row pattern recognition: WINDOW clause integration and
+-- real-world scenario tests using stock price data.
+--
+-- Parser/planner tests: rpr_base.sql
+-- NFA engine tests: rpr_nfa.sql
+-- EXPLAIN statistics tests: rpr_explain.sql
--
CREATE TEMP TABLE stock (
@@ -30,6 +35,10 @@ INSERT INTO stock VALUES ('company2', '2023-07-10', 1300);
SELECT * FROM stock;
+--
+-- Basic pattern matching with PREV/NEXT
+--
+
-- basic test using PREV
SELECT company, tdate, price, first_value(price) OVER w, last_value(price) OVER w,
nth_value(tdate, 2) OVER w AS nth_second
@@ -175,7 +184,7 @@ SELECT company, tdate, price, first_value(price) OVER w, last_value(price) OVER
HIGH AS price > 150
);
--- basic test with none-greedy pattern
+-- basic test with fixed-length pattern (A A A = exactly 3)
SELECT company, tdate, price, count(*) OVER w
FROM stock
WINDOW w AS (
@@ -294,7 +303,9 @@ SELECT company, tdate, price, first_value(price) OVER w, last_value(price) OVER
UPDOWN AS price > PREV(price) AND price > NEXT(price)
);
--- using AFTER MATCH SKIP TO NEXT ROW
+-- using AFTER MATCH SKIP TO NEXT ROW (same pattern as above;
+-- match length is always 2, so result is identical to SKIP PAST LAST ROW.
+-- SKIP TO NEXT ROW's distinct effect is tested in backtracking section.)
SELECT company, tdate, price, first_value(price) OVER w, last_value(price) OVER w
FROM stock
WINDOW w AS (
@@ -308,8 +319,92 @@ SELECT company, tdate, price, first_value(price) OVER w, last_value(price) OVER
UPDOWN AS price > PREV(price) AND price > NEXT(price)
);
--- match everything
+-- PREV returns NULL at partition's first row (null_slot path)
+SELECT company, tdate, price, count(*) OVER w
+FROM stock
+WINDOW w AS (
+ PARTITION BY company
+ ORDER BY tdate
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (BOUNDARY REST+)
+ DEFINE
+ BOUNDARY AS PREV(price) IS NULL,
+ REST AS PREV(price) IS NOT NULL
+);
+
+-- NEXT returns NULL at partition's last row (null_slot path)
+SELECT company, tdate, price, count(*) OVER w
+FROM stock
+WINDOW w AS (
+ PARTITION BY company
+ ORDER BY tdate
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A+ BOUNDARY)
+ DEFINE
+ A AS NEXT(price) IS NOT NULL,
+ BOUNDARY AS NEXT(price) IS NULL
+);
+
+-- DESC order: PREV refers to the row with later date
+SELECT company, tdate, price, count(*) OVER w
+FROM stock
+WINDOW w AS (
+ PARTITION BY company
+ ORDER BY tdate DESC
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (START DOWN+ UP+)
+ DEFINE
+ START AS TRUE,
+ DOWN AS price < PREV(price),
+ UP AS price > PREV(price)
+);
+
+-- Multiple partitions with unequal sizes
+WITH multi_part AS (
+ SELECT * FROM (VALUES
+ ('a', 1, 10), ('a', 2, 20), ('a', 3, 15),
+ ('b', 1, 5),
+ ('c', 1, 100), ('c', 2, 200), ('c', 3, 150), ('c', 4, 140), ('c', 5, 300)
+ ) AS t(grp, id, val)
+)
+SELECT grp, id, val, count(*) OVER w
+FROM multi_part
+WINDOW w AS (
+ PARTITION BY grp
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A B+)
+ DEFINE
+ A AS val <= NEXT(val),
+ B AS val > PREV(val) OR val < PREV(val)
+);
+
+-- FLOAT/NUMERIC DEFINE conditions
+WITH float_data AS (
+ SELECT * FROM (VALUES
+ (1, 1.0::float8), (2, 1.5), (3, 1.4999), (4, 1.50001), (5, 0.1)
+ ) AS t(id, val)
+)
+SELECT id, val, count(*) OVER w
+FROM float_data
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A B+)
+ DEFINE
+ A AS TRUE,
+ B AS val > PREV(val) * 0.99
+);
+
+--
+-- SKIP TO / Backtracking / Frame boundary
+--
+-- match everything
SELECT company, tdate, price, first_value(price) OVER w, last_value(price) OVER w
FROM stock
WINDOW w AS (
@@ -474,6 +569,25 @@ UP AS price > PREV(price),
DOWN AS price < PREV(price)
);
+-- row_number() within RPR reduced frame
+SELECT company, tdate, price, row_number() OVER w, count(*) OVER w
+FROM stock
+WINDOW w AS (
+ PARTITION BY company
+ ORDER BY tdate
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (START UP+ DOWN+)
+ DEFINE
+ START AS TRUE,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+);
+
+--
+-- SQL Integration: JOIN, CTE, LATERAL
+--
+
-- JOIN case
CREATE TEMP TABLE t1 (i int, v1 int);
CREATE TEMP TABLE t2 (j int, v2 int);
@@ -552,6 +666,10 @@ SELECT id, i, j, count(*) OVER w
COND AS PREV(i + j + 1) < 10
);
+--
+-- Large-scale / scalability tests
+--
+
-- Smoke test for larger partitions.
WITH s AS (
SELECT v, count(*) OVER w AS c
@@ -607,8 +725,9 @@ result AS (
SELECT match_first, match_last, match_len FROM result WHERE match_len > 0;
--
--- Using IGNORE NULLS
+-- IGNORE NULLS
--
+
-- no NULL rows case. The result should be identical with "basic test using PREV"
SELECT company, tdate, price, first_value(price) IGNORE NULLS OVER w,
last_value(price) IGNORE NULLS OVER w,
@@ -626,7 +745,7 @@ SELECT company, tdate, price, first_value(price) IGNORE NULLS OVER w,
);
-- nth_value with IGNORE NULLS option wants to find the second row but
--- due a NULL in the midlle, it returns the third row.
+-- due to a NULL in the middle, it returns the third row.
WITH data AS (
SELECT * FROM (VALUES
(10, 1), (11, NULL), (12, 3), (13, 4)
@@ -643,8 +762,8 @@ WITH data AS (
);
-- nth_value with IGNORE NULLS option wants to find the third row but
--- due a NULL in the midlle, it reaches the end of reduced frame and
--- return NULL
+-- due to a NULL in the middle, it reaches the end of reduced frame and
+-- returns NULL
WITH data AS (
SELECT * FROM (VALUES
(10, 1), (11, NULL), (12, 3), (13, 4)
@@ -677,1503 +796,115 @@ WINDOW w AS (
DOWN AS price < PREV(price)
);
--- View and pg_get_viewdef tests.
-CREATE TEMP VIEW v_window AS
-SELECT company, tdate, price, first_value(price) OVER w, last_value(price) OVER w,
- nth_value(tdate, 2) OVER w AS nth_second
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN (START UP+ DOWN+)
- DEFINE
- START AS TRUE,
- UP AS price > PREV(price),
- DOWN AS price < PREV(price)
-);
-
-SELECT * FROM v_window;
-SELECT pg_get_viewdef('v_window');
-
---
--- Pattern optimization tests
--- VIEW shows original pattern, EXPLAIN shows optimized pattern
---
-
--- Test: duplicate alternatives removal (A | B | A)+ -> (A | B)+
-CREATE TEMP VIEW v_opt_dup AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN ((A | B | A)+)
- DEFINE
- A AS price > 100,
- B AS price <= 100
-);
-SELECT pg_get_viewdef('v_opt_dup'); -- original: ((a | b | a)+)
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_dup; -- optimized: ((a | b)+)
-
--- Test: duplicate group removal ((A | B)+ | (A | B)+) -> (A | B)+
-CREATE TEMP VIEW v_opt_dup_group AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN ((A | B)+ | (A | B)+)
- DEFINE
- A AS price > 100,
- B AS price <= 100
-);
-SELECT pg_get_viewdef('v_opt_dup_group'); -- original: ((a | b)+ | (a | b)+)
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_dup_group; -- optimized: ((a | b)+)
-
--- Test: consecutive vars merge (A A A) -> A{3}
-CREATE TEMP VIEW v_opt_merge AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN (A A A)
- DEFINE
- A AS price >= 140 AND price <= 150
-);
-SELECT pg_get_viewdef('v_opt_merge'); -- original: (a a a)
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_merge; -- optimized: a{3}
-
--- Test: quantified vars merge (A A+ A) -> A{3,}
-CREATE TEMP VIEW v_opt_merge_quant AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN (A A+ A)
- DEFINE
- A AS price > 100
-);
-SELECT pg_get_viewdef('v_opt_merge_quant'); -- original: (a a+ a)
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_merge_quant; -- optimized: a{3,}
-
--- Test: merge two unbounded (A+ A+) -> A{2,}
-CREATE TEMP VIEW v_opt_merge_unbounded AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
+-- IGNORE NULLS + first_value where first value in reduced frame is NULL
+WITH data AS (
+ SELECT * FROM (VALUES
+ (1, NULL), (2, NULL), (3, 30), (4, 40)
+ ) AS t(id, val))
+SELECT id, val,
+ first_value(val) IGNORE NULLS OVER w AS fv_ignull,
+ count(*) OVER w
+FROM data
+WINDOW w AS (
+ ORDER BY id
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN (A+ A+)
- DEFINE
- A AS price > 100
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A+)
+ DEFINE A AS TRUE
);
-SELECT pg_get_viewdef('v_opt_merge_unbounded'); -- original: (a+ a+)
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_merge_unbounded; -- optimized: a{2,}
--- Test: merge with zero-min (A* A+) -> A+
-CREATE TEMP VIEW v_opt_merge_star AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
+-- IGNORE NULLS + all values NULL in reduced frame
+WITH data AS (
+ SELECT * FROM (VALUES
+ (1, NULL), (2, NULL), (3, NULL)
+ ) AS t(id, val))
+SELECT id, val,
+ first_value(val) IGNORE NULLS OVER w AS fv_ignull,
+ last_value(val) IGNORE NULLS OVER w AS lv_ignull,
+ count(*) OVER w
+FROM data
+WINDOW w AS (
+ ORDER BY id
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN (A* A+)
- DEFINE
- A AS price > 100
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A+)
+ DEFINE A AS TRUE
);
-SELECT pg_get_viewdef('v_opt_merge_star'); -- original: (a* a+)
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_merge_star; -- optimized: a+
--- Test: complex merge (A A{2} A+ A{3}) -> A{7,}
-CREATE TEMP VIEW v_opt_merge_complex AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN (A A{2} A+ A{3})
- DEFINE
- A AS price > 100
-);
-SELECT pg_get_viewdef('v_opt_merge_complex'); -- original: (a a{2} a+ a{3})
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_merge_complex; -- optimized: a{7,}
+--
+-- last_value IGNORE NULLS with reduced frame containing all NULLs
+-- Exercises ignorenulls_getfuncarginframe SEEK_TAIL out-of-frame path
+-- when notnull_relpos >= num_reduced_frame.
+--
+CREATE TEMP TABLE rpr_nullval (id INT, val INT);
+INSERT INTO rpr_nullval VALUES (1, 10), (2, NULL), (3, NULL), (4, 20);
--- Test: group merge ((A B) (A B)+) -> (A B){2,}
-CREATE TEMP VIEW v_opt_merge_group AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN ((A B) (A B)+)
- DEFINE
- A AS price > 100,
- B AS price <= 100
+SELECT id, val,
+ last_value(val) IGNORE NULLS OVER w AS lv_ignull,
+ count(*) OVER w AS cnt
+FROM rpr_nullval
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A B+)
+ DEFINE
+ A AS val IS NOT NULL,
+ B AS val IS NULL
);
-SELECT pg_get_viewdef('v_opt_merge_group'); -- original: ((a b) (a b)+)
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_merge_group; -- expected: (a b){2,}
--- Test: group merge A B (A B)+ -> (A B){2,}
-CREATE TEMP VIEW v_opt_merge_group2 AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN (A B (A B)+)
- DEFINE
- A AS price > 100,
- B AS price <= 100
-);
-SELECT pg_get_viewdef('v_opt_merge_group2'); -- original: (a b (a b)+)
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_merge_group2; -- expected: (a b){2,}
+--
+-- NULL handling
+--
--- Test: group merge (A B) (A B)+ (A B) -> (A B){3,}
-CREATE TEMP VIEW v_opt_merge_group3 AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN ((A B) (A B)+ (A B))
- DEFINE
- A AS price > 100,
- B AS price <= 100
-);
-SELECT pg_get_viewdef('v_opt_merge_group3'); -- original: ((a b) (a b)+ (a b))
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_merge_group3; -- expected: (a b){3,}
+CREATE TEMP TABLE stock_null (company TEXT, tdate DATE, price INTEGER);
+INSERT INTO stock_null VALUES ('c1', '2023-07-01', 100);
+INSERT INTO stock_null VALUES ('c1', '2023-07-02', NULL); -- NULL in middle
+INSERT INTO stock_null VALUES ('c1', '2023-07-03', 200);
+INSERT INTO stock_null VALUES ('c1', '2023-07-04', 150);
--- Test: group merge A B A B (A B)+ A B A B -> (A B){5,}
-CREATE TEMP VIEW v_opt_merge_group4 AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN (A B A B (A B)+ A B A B)
- DEFINE
- A AS price > 100,
- B AS price <= 100
+SELECT company, tdate, price, count(*) OVER w AS match_count
+FROM stock_null
+WINDOW w AS (
+ PARTITION BY company
+ ORDER BY tdate
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (START UP DOWN)
+ DEFINE START AS TRUE, UP AS price > PREV(price), DOWN AS price <
+PREV(price)
);
-SELECT pg_get_viewdef('v_opt_merge_group4'); -- original: (a b a b (a b)+ a b a b)
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_merge_group4; -- expected: (a b){5,}
--- Test: group merge C A B (A B)+ A B C -> C (A B){3,} C
-CREATE TEMP VIEW v_opt_merge_group5 AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN (C A B (A B)+ A B C)
- DEFINE
- A AS price > 100,
- B AS price <= 100,
- C AS price > 200
-);
-SELECT pg_get_viewdef('v_opt_merge_group5'); -- original: (c a b (a b)+ a b c)
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_merge_group5; -- expected: c (a b){3,} c
+-- Consecutive NULLs: PREV navigates through NULL values
+CREATE TEMP TABLE rpr_consec_null (id INT, val INT);
+INSERT INTO rpr_consec_null VALUES
+ (1, 100), (2, NULL), (3, NULL), (4, NULL), (5, 200), (6, 300);
--- Test: consecutive GROUP merge (A B)+ (A B)+ -> (A B){2,}
-CREATE TEMP VIEW v_opt_merge_consec_group AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
+-- PREV(val) IS NULL succeeds for both null_slot (first row) and actual NULL
+SELECT id, val, count(*) OVER w AS cnt
+FROM rpr_consec_null
+WINDOW w AS (
+ ORDER BY id
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN ((A B)+ (A B)+)
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A B+ C)
DEFINE
- A AS price > 100,
- B AS price <= 100
+ A AS val IS NULL,
+ B AS val IS NULL AND PREV(val) IS NULL,
+ C AS val IS NOT NULL
);
-SELECT pg_get_viewdef('v_opt_merge_consec_group'); -- original: ((a b)+ (a b)+)
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_merge_consec_group; -- expected: (a b){2,}
--- Test: consecutive GROUP merge with different quantifiers (A B){2} (A B){3} -> (A B){5}
-CREATE TEMP VIEW v_opt_merge_consec_group2 AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
+-- NEXT(val) through consecutive NULLs
+SELECT id, val, count(*) OVER w AS cnt
+FROM rpr_consec_null
+WINDOW w AS (
+ ORDER BY id
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN ((A B){2} (A B){3})
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A B+ C)
DEFINE
- A AS price > 100,
- B AS price <= 100
+ A AS val IS NOT NULL,
+ B AS val IS NULL AND NEXT(val) IS NULL,
+ C AS val IS NULL AND NEXT(val) IS NOT NULL
);
-SELECT pg_get_viewdef('v_opt_merge_consec_group2'); -- original: ((a b){2} (a b){3})
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_merge_consec_group2; -- expected: (a b){5}
--- Test {n} quantifier display
-CREATE TEMP VIEW v_quantifier_n AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN (A{3})
- DEFINE
- A AS price > 100
-);
-SELECT pg_get_viewdef('v_quantifier_n');
-
--- Test {n,} quantifier display
-CREATE TEMP VIEW v_quantifier_n_plus AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN (A{2,})
- DEFINE
- A AS price > 100
-);
-SELECT pg_get_viewdef('v_quantifier_n_plus');
-
--- Test: flatten nested SEQ (A (B C)) -> A B C
-CREATE TEMP VIEW v_opt_flatten_seq AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN (A (B C))
- DEFINE
- A AS price > 100,
- B AS price > 150,
- C AS price < 150
-);
-SELECT pg_get_viewdef('v_opt_flatten_seq'); -- original: (a (b c))
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_flatten_seq; -- optimized: a b c
-
--- Test: flatten nested ALT (A | (B | C)) -> (A | B | C)
-CREATE TEMP VIEW v_opt_flatten_alt AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN ((A | (B | C))+)
- DEFINE
- A AS price > 200,
- B AS price > 100,
- C AS price <= 100
-);
-SELECT pg_get_viewdef('v_opt_flatten_alt'); -- original: ((a | (b | c))+)
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_flatten_alt; -- optimized: ((a | b | c))+
-
--- Test: unwrap GROUP{1,1} ((A)) -> A
-CREATE TEMP VIEW v_opt_unwrap_group AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN (((A)))
- DEFINE
- A AS price > 100
-);
-SELECT pg_get_viewdef('v_opt_unwrap_group'); -- original: (((a)))
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_unwrap_group; -- optimized: a
-
--- Test: quantifier multiplication (A{2}){3} -> A{6}
-CREATE TEMP VIEW v_opt_quant_mult AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN ((A{2}){3})
- DEFINE
- A AS price > 100
-);
-SELECT pg_get_viewdef('v_opt_quant_mult'); -- original: ((a{2}){3})
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_quant_mult; -- optimized: a{6}
-
--- Test: quantifier multiplication (A{2,4}){3} -> A{6,12}
-CREATE TEMP VIEW v_opt_quant_mult_range AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN ((A{2,4}){3})
- DEFINE
- A AS price > 100
-);
-SELECT pg_get_viewdef('v_opt_quant_mult_range'); -- original: ((a{2,4}){3})
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_quant_mult_range; -- optimized: a{6,12}
-
--- Test: quantifier multiplication blocked (A{2}){3,5} -> no change
--- outer range with child exact > 1 causes gaps (6,8,10 not 6,7,8,9,10)
-CREATE TEMP VIEW v_opt_quant_mult_range2 AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN ((A{2}){3,5})
- DEFINE
- A AS price > 100
-);
-SELECT pg_get_viewdef('v_opt_quant_mult_range2'); -- original: ((a{2}){3,5})
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_quant_mult_range2; -- NOT optimized: (a{2}){3,5}
-
--- Test: quantifier multiplication blocked by INF (A+){3} -> no change
-CREATE TEMP VIEW v_opt_quant_mult_inf AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN ((A+){3})
- DEFINE
- A AS price > 100
-);
-SELECT pg_get_viewdef('v_opt_quant_mult_inf'); -- original: ((a+){3})
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_quant_mult_inf; -- no multiply: (a+){3}
-
--- Test: unwrap single-item ALT after duplicate removal (A | A) -> A
-CREATE TEMP VIEW v_opt_unwrap_alt AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN ((A | A)+)
- DEFINE
- A AS price > 100
-);
-SELECT pg_get_viewdef('v_opt_unwrap_alt'); -- original: ((a | a)+)
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_unwrap_alt; -- optimized: a+
-
--- Test: GROUP{1,1} to SEQ with flatten ((A B)(C D)) -> A B C D
-CREATE TEMP VIEW v_opt_group_to_seq AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN (((A B)(C D)))
- DEFINE
- A AS price > 200,
- B AS price > 150,
- C AS price > 100,
- D AS price <= 100
-);
-SELECT pg_get_viewdef('v_opt_group_to_seq'); -- original: (((a b)(c d)))
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_group_to_seq; -- optimized: a b c d
-
--- Test: combined consecutive GROUP + prefix merge A B (A B)+ (A B)+ -> (A B){3,}
-CREATE TEMP VIEW v_opt_combined_merge AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN (A B (A B)+ (A B)+)
- DEFINE
- A AS price > 100,
- B AS price <= 100
-);
-SELECT pg_get_viewdef('v_opt_combined_merge'); -- original: (a b (a b)+ (a b)+)
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_combined_merge; -- expected: (a b){3,}
-
--- Test: nested ALT pattern - bug reproduction
-CREATE TEMP VIEW v_opt_nested_alt AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN (((A B) | C) D | A B C)
- DEFINE
- A AS price <= 100,
- B AS price <= 150,
- C AS price <= 200,
- D AS price > 200
-);
-SELECT pg_get_viewdef('v_opt_nested_alt');
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_nested_alt;
-
--- Test: nested ALT with unbounded - A+ inside
-CREATE TEMP VIEW v_opt_nested_alt2 AS
-SELECT company, tdate, price, count(*) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN (((A+ B) | C) D | A B C)
- DEFINE
- A AS price <= 100,
- B AS price <= 150,
- C AS price <= 200,
- D AS price > 200
-);
-SELECT pg_get_viewdef('v_opt_nested_alt2');
-EXPLAIN (COSTS OFF) SELECT * FROM v_opt_nested_alt2;
-
---
--- Error cases
---
-
--- row pattern definition variable name must not appear more than once
-SELECT company, tdate, price, first_value(price) OVER w, last_value(price) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ORDER BY tdate
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN (START UP+ DOWN+)
- DEFINE
- START AS TRUE,
- UP AS price > PREV(price),
- DOWN AS price < PREV(price),
- UP AS price > PREV(price)
-);
-
--- subqueries in DEFINE clause are not supported
-SELECT company, tdate, price, first_value(price) OVER w, last_value(price) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ORDER BY tdate
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN (START LOWPRICE)
- DEFINE
- START AS TRUE,
- LOWPRICE AS price < (SELECT 100)
-);
-
--- aggregates in DEFINE clause are not supported
-SELECT company, tdate, price, first_value(price) OVER w, last_value(price) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ORDER BY tdate
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN (START LOWPRICE)
- DEFINE
- START AS TRUE,
- LOWPRICE AS price < count(*)
-);
-
--- FRAME must start at current row when row pattern recognition is used
-SELECT company, tdate, price, first_value(price) OVER w, last_value(price) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ORDER BY tdate
- ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
- INITIAL
- PATTERN (START UP+ DOWN+)
- DEFINE
- START AS TRUE,
- UP AS price > PREV(price),
- DOWN AS price < PREV(price)
-);
-
--- EXCLUDE is not permitted
-SELECT company, tdate, price, first_value(price) OVER w, last_value(price) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ORDER BY tdate
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- EXCLUDE CURRENT ROW
- INITIAL
- PATTERN (START UP+ DOWN+)
- DEFINE
- START AS TRUE,
- UP AS price > PREV(price),
- DOWN AS price < PREV(price)
-);
-
--- SEEK is not supported
-SELECT company, tdate, price, first_value(price) OVER w, last_value(price) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ORDER BY tdate
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP TO NEXT ROW
- SEEK
- PATTERN (START UP+ DOWN+)
- DEFINE
- START AS TRUE,
- UP AS price > PREV(price),
- DOWN AS price < PREV(price)
-);
-
--- PREV's argument must have at least 1 column reference
-SELECT company, tdate, price, first_value(price) OVER w, last_value(price) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ORDER BY tdate
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP TO NEXT ROW
- INITIAL
- PATTERN (START UP+ DOWN+)
- DEFINE
- START AS TRUE,
- UP AS price > PREV(1),
- DOWN AS price < PREV(1)
-);
-
--- Unsupported quantifier
-SELECT company, tdate, price, first_value(price) OVER w, last_value(price) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ORDER BY tdate
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP TO NEXT ROW
- INITIAL
- PATTERN (START UP~ DOWN+)
- DEFINE
- START AS TRUE,
- UP AS price > PREV(1),
- DOWN AS price < PREV(1)
-);
-
--- PREV(1) missing column reference (error)
-SELECT company, tdate, price, first_value(price) OVER w, last_value(price) OVER w
- FROM stock
- WINDOW w AS (
- PARTITION BY company
- ORDER BY tdate
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP TO NEXT ROW
- INITIAL
- PATTERN (START UP+? DOWN+)
- DEFINE
- START AS TRUE,
- UP AS price > PREV(1),
- DOWN AS price < PREV(1)
-);
-
--- Maximum pattern variables is 251 (RPR_VARID_MAX)
-
--- Error: 252 variables exceeds limit of 251
-DO $$
-DECLARE
- pattern_vars text;
- define_vars text;
- query text;
-BEGIN
- SELECT string_agg('v' || lpad(i::text, 3, '0'), ' '),
- string_agg('v' || lpad(i::text, 3, '0') || ' AS TRUE', ', ')
- INTO pattern_vars, define_vars
- FROM generate_series(1, 252) i;
-
- query := format('SELECT * FROM (SELECT 1 AS x) t WINDOW w AS (
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- PATTERN (%s)
- DEFINE %s)', pattern_vars, define_vars);
-
- EXECUTE query;
-END;
-$$;
-
--- Error: 253 variables exceeds limit of 251
-DO $$
-DECLARE
- pattern_vars text;
- define_vars text;
- query text;
-BEGIN
- SELECT string_agg('v' || lpad(i::text, 3, '0'), ' '),
- string_agg('v' || lpad(i::text, 3, '0') || ' AS TRUE', ', ')
- INTO pattern_vars, define_vars
- FROM generate_series(1, 253) i;
-
- query := format('SELECT * FROM (SELECT 1 AS x) t WINDOW w AS (
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- PATTERN (%s)
- DEFINE %s)', pattern_vars, define_vars);
-
- EXECUTE query;
-END;
-$$;
-
- CREATE TEMP TABLE stock_null (company TEXT, tdate DATE, price INTEGER);
- INSERT INTO stock_null VALUES ('c1', '2023-07-01', 100);
- INSERT INTO stock_null VALUES ('c1', '2023-07-02', NULL); -- NULL in middle
- INSERT INTO stock_null VALUES ('c1', '2023-07-03', 200);
- INSERT INTO stock_null VALUES ('c1', '2023-07-04', 150);
-
- SELECT company, tdate, price, count(*) OVER w AS match_count
- FROM stock_null
- WINDOW w AS (
- PARTITION BY company
- ORDER BY tdate
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- PATTERN (START UP DOWN)
- DEFINE START AS TRUE, UP AS price > PREV(price), DOWN AS price <
-PREV(price)
- );
-
-
--- Overlapping match tests (requires multi-context for correct behavior)
--- Using array flags: 'X' = ANY(flags) for multi-TRUE support
-
--- Test 1: A B C D E | B C D | C D E F - three overlapping patterns
--- Different end points: B C D (4), A B C D E (5), C D E F (6)
-WITH test_overlap1 AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['B']),
- (3, ARRAY['C']),
- (4, ARRAY['D']),
- (5, ARRAY['E']),
- (6, ARRAY['F'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM test_overlap1
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN (A B C D E | B C D | C D E F)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags),
- C AS 'C' = ANY(flags),
- D AS 'D' = ANY(flags),
- E AS 'E' = ANY(flags),
- F AS 'F' = ANY(flags)
-);
-
-WITH test_overlap1 AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['B']),
- (3, ARRAY['C']),
- (4, ARRAY['D']),
- (5, ARRAY['E']),
- (6, ARRAY['F'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM test_overlap1
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP TO NEXT ROW
- PATTERN (A B C D E | B C D | C D E F)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags),
- C AS 'C' = ANY(flags),
- D AS 'D' = ANY(flags),
- E AS 'E' = ANY(flags),
- F AS 'F' = ANY(flags)
-);
--- PAST LAST: only one match
--- TO NEXT ROW with multi-context: three matches
--- Row 1: A B C D E (1-5)
--- Row 2: B C D (2-4) <- ends first!
--- Row 3: C D E F (3-6) <- ends last!
-
--- Test 1b: Longer pattern FAILS, shorter pattern should survive
--- Pattern: A+ B C D E | B+ C
--- A+ B C D E fails (no E found in sequence)
--- B+ C matches at rows 2-3
--- Result: match 2-3 (B+ C)
-WITH test_overlap1b AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['B']),
- (3, ARRAY['C']),
- (4, ARRAY['D']),
- (5, ARRAY['X'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM test_overlap1b
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN (A+ B C D E | B+ C)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags),
- C AS 'C' = ANY(flags),
- D AS 'D' = ANY(flags),
- E AS 'E' = ANY(flags)
-);
-
--- Test 2: A B+ C | B+ D - long B sequence with different endings
-WITH test_overlap2 AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['B']),
- (3, ARRAY['B']),
- (4, ARRAY['B']),
- (5, ARRAY['B']),
- (6, ARRAY['C']),
- (7, ARRAY['B']),
- (8, ARRAY['B']),
- (9, ARRAY['B']),
- (10, ARRAY['D'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM test_overlap2
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP TO NEXT ROW
- PATTERN (A B+ C | B+ D)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags),
- C AS 'C' = ANY(flags),
- D AS 'D' = ANY(flags)
-);
--- Current result (correct):
--- Row 1: A B+ C (1-6)
--- Row 7-9: B+ D (7-10, 8-10, 9-10)
--- Note: Row 2-6 cannot match B+ D because Row 6 is C, not D
--- With absorption: 8-10 and 9-10 would be absorbed by 7-10 (earlier context covers later)
-
--- Test 3: Greedy quantifier with late failure - A B C+ D | A B
--- Pattern expects D after C+, but E comes instead ("betrayal")
-WITH test_betrayal AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['B']),
- (3, ARRAY['C']),
- (4, ARRAY['C']),
- (5, ARRAY['C']),
- (6, ARRAY['E'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM test_betrayal
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN (A B C+ D | A B)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags),
- C AS 'C' = ANY(flags),
- D AS 'D' = ANY(flags)
-);
--- A B C+ D fails at Row 6 (E instead of D)
--- Question: Does it fallback to A B (1-2)?
-
--- Test 4: Lexical Order test - A B C | A B C D E
--- SQL standard: first matching alternative wins
-WITH test_lexical AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['B']),
- (3, ARRAY['C']),
- (4, ARRAY['D']),
- (5, ARRAY['E'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM test_lexical
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN (A B C | A B C D E)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags),
- C AS 'C' = ANY(flags),
- D AS 'D' = ANY(flags),
- E AS 'E' = ANY(flags)
-);
--- SQL standard Lexical Order: A B C (1-3) wins (first alternative)
-
--- Test 4b: Reversed pattern order - A B C D E | A B C
-WITH test_lexical2 AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['B']),
- (3, ARRAY['C']),
- (4, ARRAY['D']),
- (5, ARRAY['E'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM test_lexical2
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN (A B C D E | A B C)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags),
- C AS 'C' = ANY(flags),
- D AS 'D' = ANY(flags),
- E AS 'E' = ANY(flags)
-);
--- SQL standard Lexical Order: A B C D E (1-5) wins (first alternative)
-
--- Test 5: Multiple TRUE in single row (overlapping pattern variables)
--- Each row matches multiple DEFINE conditions simultaneously
-WITH test_multi_true AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A','B']), -- A and B both TRUE
- (2, ARRAY['B','C']), -- B and C both TRUE
- (3, ARRAY['C','D']), -- C and D both TRUE
- (4, ARRAY['D','E']), -- D and E both TRUE
- (5, ARRAY['E','_']) -- E only
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM test_multi_true
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN (A B C D E)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags),
- C AS 'C' = ANY(flags),
- D AS 'D' = ANY(flags),
- E AS 'E' = ANY(flags)
-);
--- Row 1: A=T, B=T -> matches A
--- Row 2: B=T, C=T -> matches B
--- Row 3: C=T, D=T -> matches C
--- Row 4: D=T, E=T -> matches D
--- Row 5: E=T -> matches E
--- Result: match 1-5 (A B C D E)
-
--- Test 6: Diagonal pattern with multi-TRUE (shifted overlap)
-WITH test_diagonal AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A','_']),
- (2, ARRAY['B','A']),
- (3, ARRAY['C','B']),
- (4, ARRAY['D','C']),
- (5, ARRAY['_','D'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM test_diagonal
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP TO NEXT ROW
- PATTERN (A B C D)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags),
- C AS 'C' = ANY(flags),
- D AS 'D' = ANY(flags)
-);
--- Possible matches:
--- Start Row 1: A(1) B(2) C(3) D(4) -> 1-4
--- Start Row 2: A(2) B(3) C(4) D(5) -> 2-5 (because Row 2 has A too!)
-
--- ===================================================================
--- Context Absorption Tests
--- ===================================================================
-
--- Test absorption 1: Basic A+ pattern - later contexts absorbed by earlier
-WITH test_absorb_basic AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['A']),
- (3, ARRAY['A']),
- (4, ARRAY['A']),
- (5, ARRAY['B'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM test_absorb_basic
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP TO NEXT ROW
- PATTERN (A+)
- DEFINE A AS 'A' = ANY(flags)
-);
--- Pattern A+ is absorbable (unbounded first element, only one unbounded)
--- 4 matches: (1-4, 2-4, 3-4, 4-4)
-
--- Test absorption 2: A+ B pattern - absorption with fixed suffix
-WITH test_absorb_suffix AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['A']),
- (3, ARRAY['A']),
- (4, ARRAY['B']),
- (5, ARRAY['X'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM test_absorb_suffix
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP TO NEXT ROW
- PATTERN (A+ B)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags)
-);
--- Pattern A+ B is absorbable (A+ unbounded first, B bounded suffix)
--- All potential matches end at same row (row 4 with B)
--- 3 matches: (1-4, 2-4, 3-4)
-
--- Test absorption 3: Per-branch absorption with ALT (B+ C | B+ D)
-WITH test_absorb_alt AS (
- SELECT * FROM (VALUES
- (1, ARRAY['B']),
- (2, ARRAY['B']),
- (3, ARRAY['B']),
- (4, ARRAY['D']),
- (5, ARRAY['X'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM test_absorb_alt
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP TO NEXT ROW
- PATTERN (B+ C | B+ D)
- DEFINE
- B AS 'B' = ANY(flags),
- C AS 'C' = ANY(flags),
- D AS 'D' = ANY(flags)
-);
--- Both branches B+ C and B+ D are absorbable (B+ unbounded first)
--- B+ D branch matches: 3 matches (1-4, 2-4, 3-4)
-
--- Test absorption 4: Non-absorbable pattern (A B+ - unbounded not first)
-WITH test_no_absorb AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['B']),
- (3, ARRAY['B']),
- (4, ARRAY['B']),
- (5, ARRAY['X'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM test_no_absorb
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP TO NEXT ROW
- PATTERN (A B+)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags)
-);
--- Pattern A B+ is NOT absorbable (A bounded first, B+ unbounded but not first)
--- Only Row 1 can start match (only row with A), so only one match: 1-4
-
--- Test absorption 5: GROUP merge enables absorption
-WITH test_absorb_group AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['B']),
- (3, ARRAY['A']),
- (4, ARRAY['B']),
- (5, ARRAY['A']),
- (6, ARRAY['B']),
- (7, ARRAY['X'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM test_absorb_group
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP TO NEXT ROW
- PATTERN ((A B) (A B)+)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags)
-);
--- Pattern optimized: (A B) (A B)+ -> (A B){2,}
--- 2 matches: 1-6 (3 reps), 3-6 (2 reps)
-
--- Test absorption 6: Multiple unbounded - first element unbounded enables absorption
-WITH test_multi_unbounded AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['A']),
- (3, ARRAY['B']),
- (4, ARRAY['B']),
- (5, ARRAY['X'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM test_multi_unbounded
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP TO NEXT ROW
- PATTERN (A+ B+)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags)
-);
--- 2 matches: 1-4, 2-4 (same endpoint 4)
-
--- ============================================
--- Jacob's RPR Patterns (from jacob branch)
--- ============================================
-
--- Test: A? (optional, greedy)
-WITH jacob_optional AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['X'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM jacob_optional
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN (A?)
- DEFINE A AS 'A' = ANY(flags)
-);
--- Expected: 1-1 (matches A)
-
--- Test: A{2} (exact count)
-WITH jacob_exact AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['A']),
- (3, ARRAY['A']),
- (4, ARRAY['X'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM jacob_exact
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN (A{2})
- DEFINE A AS 'A' = ANY(flags)
-);
--- Expected: 1-2
-
--- Test: A{1,3} (bounded range, greedy)
-WITH jacob_bounded AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['A']),
- (3, ARRAY['A']),
- (4, ARRAY['A']),
- (5, ARRAY['X'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM jacob_bounded
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN (A{1,3})
- DEFINE A AS 'A' = ANY(flags)
-);
--- Expected: 1-3 (greedy takes max), then 4-4
-
--- Test: A | B (simple alternation)
-WITH jacob_simple_alt AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['B']),
- (3, ARRAY['X'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM jacob_simple_alt
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN (A | B)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags)
-);
--- Expected: 1-1 (A), 2-2 (B)
-
--- Test: A | B | C (three-way alternation)
-WITH jacob_three_alt AS (
- SELECT * FROM (VALUES
- (1, ARRAY['B']),
- (2, ARRAY['X'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM jacob_three_alt
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN (A | B | C)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags),
- C AS 'C' = ANY(flags)
-);
--- Expected: 1-1 (B)
-
--- Test: A B C (concatenation)
-WITH jacob_concat AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['B']),
- (3, ARRAY['C']),
- (4, ARRAY['X'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM jacob_concat
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN (A B C)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags),
- C AS 'C' = ANY(flags)
-);
--- Expected: 1-3
-
--- Test: A B? C (optional middle)
-WITH jacob_optional_mid AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['C']),
- (3, ARRAY['X'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM jacob_optional_mid
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN (A B? C)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags),
- C AS 'C' = ANY(flags)
-);
--- Expected: 1-2 (A C, B skipped)
-
--- Test: (A B){2} (nested group with quantifier)
-WITH jacob_nested_group AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['B']),
- (3, ARRAY['A']),
- (4, ARRAY['B']),
- (5, ARRAY['X'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM jacob_nested_group
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN ((A B){2})
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags)
-);
--- Expected: 1-4 (A B A B)
-
--- Test: (A){3} (quantifier on grouped single element)
-WITH jacob_group_quant AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['A']),
- (3, ARRAY['A']),
- (4, ARRAY['X'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM jacob_group_quant
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN ((A){3})
- DEFINE A AS 'A' = ANY(flags)
-);
--- Expected: 1-3
-
--- Test: A B C | A B C D E (lexical order - first alt wins)
-WITH jacob_lex_first AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['B']),
- (3, ARRAY['C']),
- (4, ARRAY['D']),
- (5, ARRAY['E'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM jacob_lex_first
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN (A B C | A B C D E)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags),
- C AS 'C' = ANY(flags),
- D AS 'D' = ANY(flags),
- E AS 'E' = ANY(flags)
-);
--- Expected: 1-3 (A B C wins by lexical order)
-
--- Test: A B C D E | A B C (lexical order - longer first wins)
-WITH jacob_lex_long AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['B']),
- (3, ARRAY['C']),
- (4, ARRAY['D']),
- (5, ARRAY['E'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM jacob_lex_long
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN (A B C D E | A B C)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags),
- C AS 'C' = ANY(flags),
- D AS 'D' = ANY(flags),
- E AS 'E' = ANY(flags)
-);
--- Expected: 1-5 (A B C D E wins by lexical order)
-
--- ============================================
--- Alternation with quantifiers (BUG cases from Jacob's tests)
--- ============================================
-
--- Test: (A | B)+ C - alternation inside quantified group followed by C
-WITH jacob_alt_quant AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['B']),
- (3, ARRAY['A']),
- (4, ARRAY['C'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM jacob_alt_quant
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN ((A | B)+ C)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags),
- C AS 'C' = ANY(flags)
-);
--- Expected: 1-4 (A B A C)
-
--- Test: ((A | B) C)+ - alternation inside group with outer quantifier
-WITH jacob_alt_group AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A']),
- (2, ARRAY['C']),
- (3, ARRAY['B']),
- (4, ARRAY['C']),
- (5, ARRAY['X'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM jacob_alt_group
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN (((A | B) C)+)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags),
- C AS 'C' = ANY(flags)
-);
--- Expected: 1-4 (A C B C)
-
--- ============================================
--- RELUCTANT quantifiers
--- ============================================
-
--- Test: A+? B (reluctant) - B available at row 3, A exits early
-WITH jacob_reluctant AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A','_']),
- (2, ARRAY['A','_']),
- (3, ARRAY['A','B']),
- (4, ARRAY['B','_'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM jacob_reluctant
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN (A+? B)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags)
-);
-
--- Test: A{1,3}? B (reluctant bounded) - same data, bounded quantifier
-WITH jacob_reluctant_bounded AS (
- SELECT * FROM (VALUES
- (1, ARRAY['A','_']),
- (2, ARRAY['A','_']),
- (3, ARRAY['A','B']),
- (4, ARRAY['B','_'])
- ) AS t(id, flags)
-)
-SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
-FROM jacob_reluctant_bounded
-WINDOW w AS (
- ORDER BY id
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN (A{1,3}? B)
- DEFINE
- A AS 'A' = ANY(flags),
- B AS 'B' = ANY(flags)
-);
-
--- ============================================
--- Nested quantifiers (pathological patterns)
--- ============================================
--- These patterns previously caused segfault or infinite loop.
--- Now they are either optimized at compile time or handled safely at runtime.
-
--- Test: (A*)* - nested unbounded quantifiers (optimized to A*)
-SELECT v, count(*) OVER w AS c
-FROM (SELECT generate_series(1, 5) v)
-WINDOW w AS (
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- INITIAL
- PATTERN ((A*)*)
- DEFINE A AS TRUE
-);
-
--- Test: (A*)+ - inner nullable, outer requires one (optimized to A*)
-SELECT v, count(*) OVER w AS c
-FROM (SELECT generate_series(1, 5) v)
-WINDOW w AS (
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- INITIAL
- PATTERN ((A*)+)
- DEFINE A AS TRUE
-);
-
--- Test: (A+)* - outer nullable (optimized to A*)
-SELECT v, count(*) OVER w AS c
-FROM (SELECT generate_series(1, 5) v)
-WINDOW w AS (
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- INITIAL
- PATTERN ((A+)*)
- DEFINE A AS TRUE
-);
-
--- Test: (A+)+ - both require match (optimized to A+)
-SELECT v, count(*) OVER w AS c
-FROM (SELECT generate_series(1, 5) v)
-WINDOW w AS (
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- INITIAL
- PATTERN ((A+)+)
- DEFINE A AS TRUE
-);
-
--- Test: (A* B*)* - complex nested pattern (runtime protection)
--- Not optimized but handled safely by empty-match loop prevention
-SELECT v, count(*) OVER w AS c
-FROM (SELECT generate_series(1, 5) v)
-WINDOW w AS (
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- INITIAL
- PATTERN ((A* B*)*)
- DEFINE A AS TRUE, B AS TRUE
-);
-
--- Test: (((A)*)*)* - triple nested (optimized through recursive optimization)
-SELECT v, count(*) OVER w AS c
-FROM (SELECT generate_series(1, 3) v)
-WINDOW w AS (
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- INITIAL
- PATTERN ((((A)*)*)*)
- DEFINE A AS TRUE
-);
+DROP TABLE rpr_consec_null;
diff --git a/src/test/regress/sql/rpr_base.sql b/src/test/regress/sql/rpr_base.sql
index 97b62a73a0e..da28de1a8f9 100644
--- a/src/test/regress/sql/rpr_base.sql
+++ b/src/test/regress/sql/rpr_base.sql
@@ -1050,7 +1050,7 @@ WINDOW w AS (
);
-- Expected: ERROR: syntax error at or near "*"
--- ? ? (invalid combination)
+-- ? ? (parsed as ?? reluctant quantifier)
SELECT COUNT(*) OVER w
FROM rpr_reluctant
WINDOW w AS (
@@ -1533,7 +1533,17 @@ SELECT pg_get_viewdef('rpr_serial_v8'::regclass);
DROP VIEW rpr_serial_v8;
-DROP TABLE rpr_serial;
+-- Reluctant {1}? quantifier deparse through ruleutils
+CREATE VIEW rpr_quant_reluctant_v AS
+SELECT id, val, count(*) OVER w
+FROM rpr_serial
+WINDOW w AS (ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ INITIAL
+ PATTERN (A{1}? B)
+ DEFINE A AS val > 0, B AS val > 0);
+SELECT pg_get_viewdef('rpr_quant_reluctant_v'::regclass);
+DROP VIEW rpr_quant_reluctant_v;
-- Materialized view (if supported)
@@ -1560,6 +1570,40 @@ SELECT * FROM rpr_mview_v1 ORDER BY id;
DROP MATERIALIZED VIEW rpr_mview_v1;
DROP TABLE rpr_mview;
+-- CREATE TABLE AS SELECT with RPR
+CREATE TABLE rpr_ctas (id INT, val INT);
+INSERT INTO rpr_ctas VALUES (1, 10), (2, 20), (3, 15), (4, 25);
+
+CREATE TABLE rpr_ctas_result AS
+SELECT id, val, count(*) OVER w AS cnt
+FROM rpr_ctas
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A B+)
+ DEFINE A AS TRUE, B AS val > PREV(val)
+);
+SELECT * FROM rpr_ctas_result ORDER BY id;
+
+-- INSERT INTO ... SELECT with RPR
+CREATE TABLE rpr_insert_target (id INT, val INT, cnt BIGINT);
+INSERT INTO rpr_insert_target
+SELECT id, val, count(*) OVER w
+FROM rpr_ctas
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A B+)
+ DEFINE A AS TRUE, B AS val > PREV(val)
+);
+SELECT * FROM rpr_insert_target ORDER BY id;
+
+DROP TABLE rpr_ctas_result;
+DROP TABLE rpr_insert_target;
+DROP TABLE rpr_ctas;
+
-- Prepared statements (tests outfuncs.c / readfuncs.c)
CREATE TABLE rpr_prep (id INT, val INT);
@@ -1822,6 +1866,32 @@ SELECT pg_get_viewdef('rpr_multiwin_v'::regclass);
DROP VIEW rpr_multiwin_v;
DROP TABLE rpr_multiwin;
+-- {n} quantifier display in view
+CREATE VIEW rpr_quant_n_v AS
+SELECT id, val, count(*) OVER w
+FROM rpr_serial
+WINDOW w AS (ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ INITIAL
+ PATTERN (A{3})
+ DEFINE A AS val > 0);
+SELECT pg_get_viewdef('rpr_quant_n_v'::regclass);
+DROP VIEW rpr_quant_n_v;
+
+-- {n,} quantifier display in view
+CREATE VIEW rpr_quant_n_plus_v AS
+SELECT id, val, count(*) OVER w
+FROM rpr_serial
+WINDOW w AS (ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ INITIAL
+ PATTERN (A{2,})
+ DEFINE A AS val > 0);
+SELECT pg_get_viewdef('rpr_quant_n_plus_v'::regclass);
+DROP VIEW rpr_quant_n_plus_v;
+
+DROP TABLE rpr_serial;
+
-- ============================================================
-- Error Cases Tests
-- ============================================================
@@ -1901,6 +1971,7 @@ WINDOW w AS (
PATTERN (A+)
DEFINE A AS A.val > 0
);
+-- Expected: ERROR: pattern variable qualified column reference "a.val" is not supported
-- PATTERN-only variable qualified name: not supported even without DEFINE entry
SELECT COUNT(*) OVER w
@@ -1911,6 +1982,7 @@ WINDOW w AS (
PATTERN (A+ B+)
DEFINE A AS B.val > 0
);
+-- Expected: ERROR: pattern variable qualified column reference "b.val" is not supported
-- DEFINE-only variable qualified name: still a pattern variable, not a range variable
SELECT COUNT(*) OVER w
@@ -1921,6 +1993,7 @@ WINDOW w AS (
PATTERN (A+)
DEFINE A AS val > 0, B AS B.val > 0
);
+-- Expected: ERROR: pattern variable qualified column reference "b.val" is not supported
-- FROM-clause range variable qualified name: not allowed (prohibited by §6.5)
SELECT COUNT(*) OVER w
@@ -1931,6 +2004,7 @@ WINDOW w AS (
PATTERN (A+)
DEFINE A AS rpr_err.val > 0
);
+-- Expected: ERROR: range variable qualified column reference "rpr_err.val" is not allowed
-- Semantic errors
@@ -1965,7 +2039,7 @@ WINDOW w AS (
PATTERN (A+)
DEFINE A AS COUNT(*) > 0
);
--- Expected: ERROR or works depending on implementation
+-- Expected: ERROR: aggregate functions are not allowed in DEFINE
-- Subquery in DEFINE (NOT SUPPORTED)
SELECT COUNT(*) OVER w
@@ -2108,6 +2182,13 @@ SELECT COUNT(*) OVER w FROM rpr_plan
WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
PATTERN (A ((B) (C))) DEFINE A AS val <= 30, B AS val <= 60, C AS val > 60);
+-- Data execution: SEQ flatten produces correct results
+SELECT id, val, count(*) OVER w AS cnt
+FROM rpr_plan
+WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP TO NEXT ROW
+ PATTERN (A ((B) (C))) DEFINE A AS val <= 30, B AS val <= 60, C AS val > 60);
+
-- ALT flatten: (A | (B | C))+ -> (a | b | c)+
EXPLAIN (COSTS OFF)
SELECT COUNT(*) OVER w FROM rpr_plan
@@ -2120,6 +2201,13 @@ SELECT COUNT(*) OVER w FROM rpr_plan
WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
PATTERN ((A | B | A)+) DEFINE A AS val <= 50, B AS val > 50);
+-- Data execution: ALT dedup produces correct results
+SELECT id, val, count(*) OVER w AS cnt
+FROM rpr_plan
+WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN ((A | B | A)+) DEFINE A AS val <= 50, B AS val > 50);
+
-- Quantifier multiply: (A{2}){3} -> a{6}
EXPLAIN (COSTS OFF)
SELECT COUNT(*) OVER w FROM rpr_plan
@@ -2305,6 +2393,13 @@ SELECT COUNT(*) OVER w FROM rpr_plan
WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
PATTERN ((A | B | C)) DEFINE A AS val <= 30, B AS val <= 60, C AS val > 60);
+-- Data execution: GROUP unwrap produces correct results
+SELECT id, val, count(*) OVER w AS cnt
+FROM rpr_plan
+WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP TO NEXT ROW
+ PATTERN ((A | B | C)) DEFINE A AS val <= 30, B AS val <= 60, C AS val > 60);
+
-- Reluctant optimization bypass: VAR merge
-- A+? A stays as a+? a (greedy A+ A merges to a{2,})
EXPLAIN (COSTS OFF)
@@ -2368,6 +2463,62 @@ SELECT COUNT(*) OVER w FROM rpr_plan
WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
AFTER MATCH SKIP PAST LAST ROW PATTERN (A+?) DEFINE A AS val > 0);
+-- Duplicate GROUP removal: ((A | B)+ | (A | B)+) -> (a | b)+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) OVER w FROM rpr_plan
+WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((A | B)+ | (A | B)+) DEFINE A AS val <= 50, B AS val > 50);
+
+-- Consecutive VAR merge with zero-min: A* A+ -> a+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) OVER w FROM rpr_plan
+WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A* A+) DEFINE A AS val > 0);
+
+-- Consecutive VAR merge (4-element): A A{2} A+ A{3} -> a{7,}
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) OVER w FROM rpr_plan
+WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A A{2} A+ A{3}) DEFINE A AS val > 0);
+
+-- PREFIX+SUFFIX merge (5-way): A B A B (A B)+ A B A B -> (a b){5,}
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) OVER w FROM rpr_plan
+WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A B A B (A B)+ A B A B)
+ DEFINE A AS val <= 50, B AS val > 50);
+
+-- Unwrap single-item ALT after dedup: (A | A)+ -> a+
+-- ALT dedup reduces to single-item, then GROUP unwrap
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) OVER w FROM rpr_plan
+WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN ((A | A)+) DEFINE A AS val > 0);
+
+-- GROUP{1,1} to SEQ with flatten: ((A B)(C D)) -> a b c d
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) OVER w FROM rpr_plan
+WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (((A B)(C D)))
+ DEFINE A AS val <= 25, B AS val > 25 AND val <= 50,
+ C AS val > 50 AND val <= 75, D AS val > 75);
+
+-- Nested ALT pattern: ((A B) | C) D | A B C
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) OVER w FROM rpr_plan
+WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (((A B) | C) D | A B C)
+ DEFINE A AS val <= 25, B AS val > 25 AND val <= 50,
+ C AS val > 50 AND val <= 75, D AS val > 75);
+
+-- Nested ALT with unbounded: ((A+ B) | C) D | A B C
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) OVER w FROM rpr_plan
+WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (((A+ B) | C) D | A B C)
+ DEFINE A AS val <= 25, B AS val > 25 AND val <= 50,
+ C AS val > 50 AND val <= 75, D AS val > 75);
+
-- ============================================================
-- Absorption Flag Display Tests
-- ============================================================
@@ -2450,6 +2601,19 @@ SELECT COUNT(*) OVER w FROM rpr_plan
WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND 10 FOLLOWING
AFTER MATCH SKIP PAST LAST ROW PATTERN (A+) DEFINE A AS val > 0);
+-- Reluctant {1}? quantifier deparse
+-- A{1}? is a reluctant {1,1} quantifier. The deparse code must
+-- output "{1}" explicitly to disambiguate from a bare "?" quantifier
+-- (which would mean {0,1}).
+EXPLAIN (COSTS OFF) SELECT count(*) OVER w
+FROM rpr_plan
+WINDOW w AS (
+ ORDER BY val
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A{1}? B)
+ DEFINE A AS val > 0, B AS val > 0
+);
+
-- ============================================================
-- Absorption Analysis Tests
-- ============================================================
@@ -2871,7 +3035,8 @@ WINDOW w AS (
)
GROUP BY category
ORDER BY category;
--- Expected: ERROR (GROUP BY with window RPR not supported)
+-- Expected: ERROR: syntax error at or near "GROUP"
+-- (GROUP BY after WINDOW clause is not valid SQL syntax)
-- ============================================================
-- Subquery and CTE Tests
@@ -3243,7 +3408,8 @@ INSERT INTO rpr_sort VALUES
(1, 'A', 30), (2, 'B', 20), (3, 'A', 10),
(4, 'B', 40), (5, 'A', 50), (6, 'B', 60);
--- RPR with GROUP BY
+-- RPR with GROUP BY (aggregate in DEFINE → ERROR before GROUP BY interaction)
+-- Expected: ERROR: aggregate functions are not allowed in DEFINE
SELECT category,
COUNT(*) as group_cnt,
@@ -3259,7 +3425,8 @@ WINDOW w AS (
)
ORDER BY category;
--- RPR with HAVING
+-- RPR with HAVING (same aggregate-in-DEFINE error)
+-- Expected: ERROR: aggregate functions are not allowed in DEFINE
SELECT category,
COUNT(*) as group_cnt,
@@ -3771,7 +3938,3 @@ FROM (SELECT id, val,
) s;
DROP TABLE rpr_plan;
-
--- ============================================================
--- End of rpr_base.sql
--- ============================================================
diff --git a/src/test/regress/sql/rpr_explain.sql b/src/test/regress/sql/rpr_explain.sql
index d017a2292d2..4bb49650bb7 100644
--- a/src/test/regress/sql/rpr_explain.sql
+++ b/src/test/regress/sql/rpr_explain.sql
@@ -6,8 +6,9 @@
-- This test suite validates EXPLAIN output for RPR queries,
-- including NFA statistics shown in EXPLAIN ANALYZE:
-- - NFA States: peak, total, merged
--- - NFA Contexts: peak, total, absorbed, skipped
+-- - NFA Contexts: peak, total, pruned
-- - NFA: matched (len min/max/avg), mismatched (len min/max/avg)
+-- - NFA: absorbed (len min/max/avg), skipped (len min/max/avg)
-- - Pattern deparse formatting
-- - Multiple output formats (text, JSON, XML)
--
@@ -310,7 +311,7 @@ WINDOW w AS (
);');
DROP VIEW rpr_v;
--- Grouped pattern with quantifier - state merging
+-- Grouped pattern with quantifier - state count with grouping
CREATE TEMP VIEW rpr_v AS
SELECT count(*) OVER w
FROM generate_series(1, 60) AS s(v)
@@ -428,7 +429,7 @@ WINDOW w AS (
);');
DROP VIEW rpr_v;
--- High state merging - alternation with plus quantifier
+-- High state count - alternation with plus quantifier
CREATE TEMP VIEW rpr_v AS
SELECT count(*) OVER w
FROM generate_series(1, 100) AS s(v)
@@ -499,7 +500,7 @@ WINDOW w AS (
DROP VIEW rpr_v;
-- ============================================================
--- Context Statistics Tests (peak, total, absorbed, skipped)
+-- Context Statistics Tests (peak, total, pruned + absorbed/skipped)
-- ============================================================
-- Context absorption with unbounded quantifier at start
@@ -671,7 +672,7 @@ WINDOW w AS (
);');
DROP VIEW rpr_v;
--- Mix of short and long matches
+-- Uniform match length with mismatches from gap rows (v%20 = 11..15)
CREATE TEMP VIEW rpr_v AS
SELECT count(*) OVER w
FROM generate_series(1, 100) AS s(v)
@@ -702,8 +703,8 @@ DROP VIEW rpr_v;
-- Mismatch Length Statistics Tests
-- ============================================================
--- Pattern that causes mismatches with length > 1
--- Mismatch happens when partial match fails after processing multiple rows
+-- Pattern with complete match every cycle: 0 mismatched
+-- A(1,2,3) B(4,5) C(6) repeats perfectly; X rows are pruned, not mismatched
CREATE TEMP VIEW rpr_v AS
SELECT count(*) OVER w
FROM (
@@ -837,33 +838,6 @@ WINDOW w AS (
)');
DROP VIEW rpr_v;
--- ============================================================
--- XML Format Tests
--- ============================================================
-
--- XML format output
-CREATE TEMP VIEW rpr_v AS
-SELECT count(*) OVER w
-FROM generate_series(1, 30) AS s(v)
-WINDOW w AS (
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN (A B)
- DEFINE A AS v % 2 = 1, B AS v % 2 = 0
-);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
-SELECT rpr_explain_filter('
-EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF, FORMAT XML)
-SELECT count(*) OVER w
-FROM generate_series(1, 30) AS s(v)
-WINDOW w AS (
- ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
- AFTER MATCH SKIP PAST LAST ROW
- PATTERN (A B)
- DEFINE A AS v % 2 = 1, B AS v % 2 = 0
-)');
-DROP VIEW rpr_v;
-
-- JSON format with mismatch statistics
-- Pattern A B C expects 1,2,3 but gets 1,2,4 twice causing mismatches
CREATE TEMP VIEW rpr_v AS
@@ -912,6 +886,33 @@ WINDOW w AS (
)');
DROP VIEW rpr_v;
+-- ============================================================
+-- XML Format Tests
+-- ============================================================
+
+-- XML format output
+CREATE TEMP VIEW rpr_v AS
+SELECT count(*) OVER w
+FROM generate_series(1, 30) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A B)
+ DEFINE A AS v % 2 = 1, B AS v % 2 = 0
+);
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT rpr_explain_filter('
+EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF, FORMAT XML)
+SELECT count(*) OVER w
+FROM generate_series(1, 30) AS s(v)
+WINDOW w AS (
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A B)
+ DEFINE A AS v % 2 = 1, B AS v % 2 = 0
+)');
+DROP VIEW rpr_v;
+
-- ============================================================
-- Multiple Partitions Tests
-- ============================================================
diff --git a/src/test/regress/sql/rpr_nfa.sql b/src/test/regress/sql/rpr_nfa.sql
index a3f01b60bc4..c0f4dfa1248 100644
--- a/src/test/regress/sql/rpr_nfa.sql
+++ b/src/test/regress/sql/rpr_nfa.sql
@@ -255,6 +255,119 @@ WINDOW w AS (
A AS 'A' = ANY(flags)
);
+-- Absorption with fixed suffix: A+ B
+WITH test_absorb_suffix AS (
+ SELECT * FROM (VALUES
+ (1, ARRAY['A']),
+ (2, ARRAY['A']),
+ (3, ARRAY['A']),
+ (4, ARRAY['B']),
+ (5, ARRAY['X'])
+ ) AS t(id, flags)
+)
+SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
+FROM test_absorb_suffix
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP TO NEXT ROW
+ PATTERN (A+ B)
+ DEFINE
+ A AS 'A' = ANY(flags),
+ B AS 'B' = ANY(flags)
+);
+
+-- Per-branch absorption with ALT: B+ C | B+ D
+WITH test_absorb_alt AS (
+ SELECT * FROM (VALUES
+ (1, ARRAY['B']),
+ (2, ARRAY['B']),
+ (3, ARRAY['B']),
+ (4, ARRAY['D']),
+ (5, ARRAY['X'])
+ ) AS t(id, flags)
+)
+SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
+FROM test_absorb_alt
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP TO NEXT ROW
+ PATTERN (B+ C | B+ D)
+ DEFINE
+ B AS 'B' = ANY(flags),
+ C AS 'C' = ANY(flags),
+ D AS 'D' = ANY(flags)
+);
+
+-- Non-absorbable: A B+ (unbounded not in first position)
+WITH test_no_absorb AS (
+ SELECT * FROM (VALUES
+ (1, ARRAY['A']),
+ (2, ARRAY['B']),
+ (3, ARRAY['B']),
+ (4, ARRAY['B']),
+ (5, ARRAY['X'])
+ ) AS t(id, flags)
+)
+SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
+FROM test_no_absorb
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP TO NEXT ROW
+ PATTERN (A B+)
+ DEFINE
+ A AS 'A' = ANY(flags),
+ B AS 'B' = ANY(flags)
+);
+
+-- GROUP merge enables absorption: (A B) (A B)+ optimized to (A B){2,}
+WITH test_absorb_group AS (
+ SELECT * FROM (VALUES
+ (1, ARRAY['A']),
+ (2, ARRAY['B']),
+ (3, ARRAY['A']),
+ (4, ARRAY['B']),
+ (5, ARRAY['A']),
+ (6, ARRAY['B']),
+ (7, ARRAY['X'])
+ ) AS t(id, flags)
+)
+SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
+FROM test_absorb_group
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP TO NEXT ROW
+ PATTERN ((A B) (A B)+)
+ DEFINE
+ A AS 'A' = ANY(flags),
+ B AS 'B' = ANY(flags)
+);
+
+-- Multiple unbounded: A+ B+ (first element unbounded enables absorption)
+WITH test_multi_unbounded AS (
+ SELECT * FROM (VALUES
+ (1, ARRAY['A']),
+ (2, ARRAY['A']),
+ (3, ARRAY['B']),
+ (4, ARRAY['B']),
+ (5, ARRAY['X'])
+ ) AS t(id, flags)
+)
+SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
+FROM test_multi_unbounded
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP TO NEXT ROW
+ PATTERN (A+ B+)
+ DEFINE
+ A AS 'A' = ANY(flags),
+ B AS 'B' = ANY(flags)
+);
+
-- ============================================================
-- Context Lifecycle
-- ============================================================
@@ -524,7 +637,8 @@ WINDOW w AS (
);
-- Optional reluctant group: (A B)?? C
--- nfa_advance_begin: reluctant prefers skip (0 iterations) over enter
+-- nfa_advance_begin: reluctant tries skip first, but skip path needs C
+-- at row 1 which is A → skip fails. Enter path succeeds: A(1) B(2) C(3).
WITH test_optional_reluctant AS (
SELECT * FROM (VALUES
(1, ARRAY['A']),
@@ -571,6 +685,36 @@ WINDOW w AS (
B AS 'B' = ANY(flags)
);
+-- Reluctant optional group skip-to-FIN
+-- When a reluctant optional group's skip path reaches FIN, the group
+-- entry path is abandoned (nodeWindowAgg.c nfa_advance_begin).
+-- Pattern: C (A B)?? -- after C matches, the reluctant group (A B)??
+-- prefers to skip. Skip goes to FIN (group is last element), so
+-- the match completes with just C.
+WITH test_begin_skip_fin AS (
+ SELECT * FROM (VALUES
+ (1, ARRAY['C']),
+ (2, ARRAY['A']),
+ (3, ARRAY['B']),
+ (4, ARRAY['C','A']),
+ (5, ARRAY['B'])
+ ) AS t(id, flags)
+)
+SELECT id, flags,
+ first_value(id) OVER w AS match_start,
+ last_value(id) OVER w AS match_end
+FROM test_begin_skip_fin
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP TO NEXT ROW
+ PATTERN (C (A B)??)
+ DEFINE
+ C AS 'C' = ANY(flags),
+ A AS 'A' = ANY(flags),
+ B AS 'B' = ANY(flags)
+);
+
-- ============================================================
-- Match Phase
-- ============================================================
@@ -1198,6 +1342,50 @@ WINDOW w AS (
B AS 'B' = ANY(flags)
);
+-- A+? B (reluctant plus): exits A at first B availability
+-- (Same scenario as greedy-vs-reluctant comparison above; retained for
+-- standalone quantifier coverage alongside A{1,3}? and A{2,3}? below)
+WITH test_reluctant_plus AS (
+ SELECT * FROM (VALUES
+ (1, ARRAY['A','_']),
+ (2, ARRAY['A','_']),
+ (3, ARRAY['A','B']),
+ (4, ARRAY['B','_'])
+ ) AS t(id, flags)
+)
+SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
+FROM test_reluctant_plus
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A+? B)
+ DEFINE
+ A AS 'A' = ANY(flags),
+ B AS 'B' = ANY(flags)
+);
+
+-- A{1,3}? B (reluctant bounded): same data, bounded quantifier
+WITH test_reluctant_bounded AS (
+ SELECT * FROM (VALUES
+ (1, ARRAY['A','_']),
+ (2, ARRAY['A','_']),
+ (3, ARRAY['A','B']),
+ (4, ARRAY['B','_'])
+ ) AS t(id, flags)
+)
+SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
+FROM test_reluctant_bounded
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A{1,3}? B)
+ DEFINE
+ A AS 'A' = ANY(flags),
+ B AS 'B' = ANY(flags)
+);
+
-- ============================================================
-- Pathological Pattern Runtime Protection
-- ============================================================
@@ -1481,6 +1669,211 @@ WINDOW w AS (
B AS 'B' = ANY(flags)
);
+-- Overlapping match: A B C D E | B C D | C D E F (SKIP PAST LAST ROW)
+WITH test_overlap1 AS (
+ SELECT * FROM (VALUES
+ (1, ARRAY['A']),
+ (2, ARRAY['B']),
+ (3, ARRAY['C']),
+ (4, ARRAY['D']),
+ (5, ARRAY['E']),
+ (6, ARRAY['F'])
+ ) AS t(id, flags)
+)
+SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
+FROM test_overlap1
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A B C D E | B C D | C D E F)
+ DEFINE
+ A AS 'A' = ANY(flags),
+ B AS 'B' = ANY(flags),
+ C AS 'C' = ANY(flags),
+ D AS 'D' = ANY(flags),
+ E AS 'E' = ANY(flags),
+ F AS 'F' = ANY(flags)
+);
+
+-- Same with SKIP TO NEXT ROW: three overlapping matches
+WITH test_overlap1 AS (
+ SELECT * FROM (VALUES
+ (1, ARRAY['A']),
+ (2, ARRAY['B']),
+ (3, ARRAY['C']),
+ (4, ARRAY['D']),
+ (5, ARRAY['E']),
+ (6, ARRAY['F'])
+ ) AS t(id, flags)
+)
+SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
+FROM test_overlap1
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP TO NEXT ROW
+ PATTERN (A B C D E | B C D | C D E F)
+ DEFINE
+ A AS 'A' = ANY(flags),
+ B AS 'B' = ANY(flags),
+ C AS 'C' = ANY(flags),
+ D AS 'D' = ANY(flags),
+ E AS 'E' = ANY(flags),
+ F AS 'F' = ANY(flags)
+);
+
+-- Longer pattern fails, shorter survives: A+ B C D E | B+ C
+WITH test_overlap1b AS (
+ SELECT * FROM (VALUES
+ (1, ARRAY['A']),
+ (2, ARRAY['B']),
+ (3, ARRAY['C']),
+ (4, ARRAY['D']),
+ (5, ARRAY['X'])
+ ) AS t(id, flags)
+)
+SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
+FROM test_overlap1b
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A+ B C D E | B+ C)
+ DEFINE
+ A AS 'A' = ANY(flags),
+ B AS 'B' = ANY(flags),
+ C AS 'C' = ANY(flags),
+ D AS 'D' = ANY(flags),
+ E AS 'E' = ANY(flags)
+);
+
+-- Long B sequence with different endings: A B+ C | B+ D
+WITH test_overlap2 AS (
+ SELECT * FROM (VALUES
+ (1, ARRAY['A']),
+ (2, ARRAY['B']),
+ (3, ARRAY['B']),
+ (4, ARRAY['B']),
+ (5, ARRAY['B']),
+ (6, ARRAY['C']),
+ (7, ARRAY['B']),
+ (8, ARRAY['B']),
+ (9, ARRAY['B']),
+ (10, ARRAY['D'])
+ ) AS t(id, flags)
+)
+SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
+FROM test_overlap2
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP TO NEXT ROW
+ PATTERN (A B+ C | B+ D)
+ DEFINE
+ A AS 'A' = ANY(flags),
+ B AS 'B' = ANY(flags),
+ C AS 'C' = ANY(flags),
+ D AS 'D' = ANY(flags)
+);
+
+-- Greedy with late failure ("betrayal"): A B C+ D | A B
+WITH test_betrayal AS (
+ SELECT * FROM (VALUES
+ (1, ARRAY['A']),
+ (2, ARRAY['B']),
+ (3, ARRAY['C']),
+ (4, ARRAY['C']),
+ (5, ARRAY['C']),
+ (6, ARRAY['E'])
+ ) AS t(id, flags)
+)
+SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
+FROM test_betrayal
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A B C+ D | A B)
+ DEFINE
+ A AS 'A' = ANY(flags),
+ B AS 'B' = ANY(flags),
+ C AS 'C' = ANY(flags),
+ D AS 'D' = ANY(flags)
+);
+
+-- Multiple TRUE per row: overlapping pattern variables
+WITH test_multi_true AS (
+ SELECT * FROM (VALUES
+ (1, ARRAY['A','B']),
+ (2, ARRAY['B','C']),
+ (3, ARRAY['C','D']),
+ (4, ARRAY['D','E']),
+ (5, ARRAY['E','_'])
+ ) AS t(id, flags)
+)
+SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
+FROM test_multi_true
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A B C D E)
+ DEFINE
+ A AS 'A' = ANY(flags),
+ B AS 'B' = ANY(flags),
+ C AS 'C' = ANY(flags),
+ D AS 'D' = ANY(flags),
+ E AS 'E' = ANY(flags)
+);
+
+-- Diagonal pattern with shifted multi-TRUE overlap
+WITH test_diagonal AS (
+ SELECT * FROM (VALUES
+ (1, ARRAY['A','_']),
+ (2, ARRAY['B','A']),
+ (3, ARRAY['C','B']),
+ (4, ARRAY['D','C']),
+ (5, ARRAY['_','D'])
+ ) AS t(id, flags)
+)
+SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
+FROM test_diagonal
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP TO NEXT ROW
+ PATTERN (A B C D)
+ DEFINE
+ A AS 'A' = ANY(flags),
+ B AS 'B' = ANY(flags),
+ C AS 'C' = ANY(flags),
+ D AS 'D' = ANY(flags)
+);
+
+-- ((A | B) C)+ - alternation inside group with outer quantifier
+WITH test_alt_group AS (
+ SELECT * FROM (VALUES
+ (1, ARRAY['A']),
+ (2, ARRAY['C']),
+ (3, ARRAY['B']),
+ (4, ARRAY['C']),
+ (5, ARRAY['X'])
+ ) AS t(id, flags)
+)
+SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
+FROM test_alt_group
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (((A | B) C)+)
+ DEFINE
+ A AS 'A' = ANY(flags),
+ B AS 'B' = ANY(flags),
+ C AS 'C' = ANY(flags)
+);
+
-- ============================================================
-- Deep Nested Groups
-- ============================================================
@@ -1625,6 +2018,55 @@ WINDOW w AS (
C AS 'C' = ANY(flags)
);
+-- (A B){2} - group with exact quantifier
+WITH test_group_exact AS (
+ SELECT * FROM (VALUES
+ (1, ARRAY['A']),
+ (2, ARRAY['B']),
+ (3, ARRAY['A']),
+ (4, ARRAY['B']),
+ (5, ARRAY['X'])
+ ) AS t(id, flags)
+)
+SELECT id, flags, first_value(id) OVER w AS match_start, last_value(id) OVER w AS match_end
+FROM test_group_exact
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN ((A B){2})
+ DEFINE
+ A AS 'A' = ANY(flags),
+ B AS 'B' = ANY(flags)
+);
+
+-- Nested END->END fast-forward
+-- When an inner group has a nullable body and count < min, the
+-- fast-forward path exits through the outer END, incrementing
+-- the outer group's count (nodeWindowAgg.c nfa_advance_end).
+-- Pattern: ((A?){2,3}){2,3} -- nested groups, neither collapses
+-- because the optimizer cannot safely multiply non-exact quantifiers.
+-- Data has no A rows, forcing all-empty iterations via fast-forward.
+WITH test_nested_ff AS (
+ SELECT * FROM (VALUES
+ (1, ARRAY['B']),
+ (2, ARRAY['B']),
+ (3, ARRAY['B'])
+ ) AS t(id, flags)
+)
+SELECT id, flags,
+ first_value(id) OVER w AS match_start,
+ last_value(id) OVER w AS match_end
+FROM test_nested_ff
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP TO NEXT ROW
+ PATTERN (((A?){2,3}){2,3})
+ DEFINE
+ A AS 'A' = ANY(flags)
+);
+
-- ============================================================
-- SKIP Options (Runtime)
-- ============================================================
@@ -1751,6 +2193,8 @@ ORDER BY mode, id;
-- ============================================================
-- INITIAL Mode (Runtime)
+-- Placeholder: INITIAL is not yet implemented (syntax error).
+-- Kept here so tests convert to runtime tests when implemented.
-- ============================================================
-- INITIAL mode (not yet supported - produces syntax error)
@@ -1917,6 +2361,62 @@ WINDOW w AS (
B AS 'B' = ANY(flags)
);
+-- N FOLLOWING + SKIP TO NEXT ROW: overlapping matches bounded by frame
+-- Row 1: frame [1,4], A(1-3) B(4) -> match
+-- Row 2: frame [2,5], A(2-3) B(4) -> match
+-- Row 3: frame [3,6], A(3) B(4) -> match
+-- Row 5: frame [5,6], A(5) B(6) -> match
+WITH test_n_skip_next AS (
+ SELECT * FROM (VALUES
+ (1, ARRAY['A']),
+ (2, ARRAY['A']),
+ (3, ARRAY['A']),
+ (4, ARRAY['B']),
+ (5, ARRAY['A']),
+ (6, ARRAY['B'])
+ ) AS t(id, flags)
+)
+SELECT id, flags,
+ first_value(id) OVER w AS match_start,
+ last_value(id) OVER w AS match_end
+FROM test_n_skip_next
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND 3 FOLLOWING
+ AFTER MATCH SKIP TO NEXT ROW
+ PATTERN (A+ B)
+ DEFINE
+ A AS 'A' = ANY(flags),
+ B AS 'B' = ANY(flags)
+);
+
+-- Frame exactly 1 row short of potential match
+-- From row 1: A A A B needs 4 rows but frame holds 3 -> no match
+-- From row 2: A A B fits in 3-row frame -> match
+WITH test_frame_one_short AS (
+ SELECT * FROM (VALUES
+ (1, ARRAY['A']),
+ (2, ARRAY['A']),
+ (3, ARRAY['A']),
+ (4, ARRAY['B']),
+ (5, ARRAY['A']),
+ (6, ARRAY['B'])
+ ) AS t(id, flags)
+)
+SELECT id, flags,
+ first_value(id) OVER w AS match_start,
+ last_value(id) OVER w AS match_end
+FROM test_frame_one_short
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING
+ AFTER MATCH SKIP TO NEXT ROW
+ PATTERN (A+ B)
+ DEFINE
+ A AS 'A' = ANY(flags),
+ B AS 'B' = ANY(flags)
+);
+
-- ============================================================
-- Special Partition Cases
-- ============================================================
@@ -2567,7 +3067,8 @@ WINDOW w AS (
-- (A?){0,3}: min=0, nullable inner.
-- A never matches. A? matches empty, min=0 satisfied immediately.
--- Expected: empty match (match_start = match_end) for every row
+-- Per standard: empty match expected for every row.
+-- XXX: visited bitmap blocks empty iteration → no match (same as {2,3})
WITH test_728_min0 AS (
SELECT * FROM (VALUES
(1, ARRAY['B']),
@@ -2590,7 +3091,8 @@ WINDOW w AS (
-- (A?){1,3}: min=1, nullable inner.
-- A never matches. Need 1 empty iteration to satisfy min=1.
--- Expected: empty match for every row
+-- Per standard: empty match expected for every row.
+-- XXX: visited bitmap blocks empty iteration → no match (same as {2,3})
WITH test_728_min1 AS (
SELECT * FROM (VALUES
(1, ARRAY['B']),
@@ -2639,7 +3141,8 @@ WINDOW w AS (
-- (A?){2,3} mixed: some rows match A, some don't
-- Rows 1-2: A matches, greedy takes 2 → min satisfied
-- Row 3: A doesn't match, needs 2 empty iterations for min=2
--- Expected: all rows produce matches
+-- XXX: Row 3 fails due to visited bitmap (same as pure empty {2,3})
+-- Row 4: A matches 1 real iter + 1 ff empty exit → match 4-4
WITH test_728_min2_mixed AS (
SELECT * FROM (VALUES
(1, ARRAY['A']),
@@ -2773,4 +3276,3 @@ WINDOW w AS (
A AS price > 100,
B AS TRUE
);
-
--
2.50.1 (Apple Git-155)
From f9c80e37bbc493737ed04d237abdd4115d5a412e Mon Sep 17 00:00:00 2001
From: Henson Choi <[email protected]>
Date: Tue, 3 Mar 2026 19:32:24 +0900
Subject: [PATCH 4/8] Keep RPR test objects for pg_upgrade/pg_dump testing
diff --git a/src/test/regress/expected/rpr_base.out b/src/test/regress/expected/rpr_base.out
index 3383b242ef0..ae6d9c9b937 100644
--- a/src/test/regress/expected/rpr_base.out
+++ b/src/test/regress/expected/rpr_base.out
@@ -12,7 +12,7 @@
-- Quantifiers Tests
-- Navigation Functions Tests
-- SKIP TO / INITIAL Tests
--- Serialization/Deserialization Tests
+-- Serialization/Deserialization Tests (objects kept for pg_upgrade/pg_dump)
-- Error Cases Tests
--
-- Planner Layer:
@@ -1930,7 +1930,6 @@ SELECT pg_get_viewdef('rpr_serial_v1'::regclass);
a AS (val > 0) );
(1 row)
-DROP VIEW rpr_serial_v1;
-- Complex pattern with alternation
CREATE VIEW rpr_serial_v2 AS
SELECT id, val, COUNT(*) OVER w as cnt
@@ -1967,7 +1966,6 @@ SELECT pg_get_viewdef('rpr_serial_v2'::regclass);
b AS (val <= 20) );
(1 row)
-DROP VIEW rpr_serial_v2;
-- Pattern with grouping and quantifiers
CREATE VIEW rpr_serial_v3 AS
SELECT id, val, COUNT(*) OVER w as cnt
@@ -2008,7 +2006,6 @@ SELECT pg_get_viewdef('rpr_serial_v3'::regclass);
c AS (val <= 10) );
(1 row)
-DROP VIEW rpr_serial_v3;
-- All features combined
CREATE VIEW rpr_serial_v4 AS
SELECT id, val, COUNT(*) OVER w as cnt
@@ -2053,7 +2050,6 @@ SELECT pg_get_viewdef('rpr_serial_v4'::regclass);
finish AS (val > 15) );
(1 row)
-DROP VIEW rpr_serial_v4;
-- Additional quantifiers for deparsing coverage
-- ? quantifier (zero or one)
CREATE VIEW rpr_serial_v5 AS
@@ -2091,7 +2087,6 @@ SELECT pg_get_viewdef('rpr_serial_v5'::regclass);
b AS (val > 20) );
(1 row)
-DROP VIEW rpr_serial_v5;
-- {n,} quantifier (n or more)
CREATE VIEW rpr_serial_v6 AS
SELECT id, val, COUNT(*) OVER w as cnt
@@ -2127,7 +2122,6 @@ SELECT pg_get_viewdef('rpr_serial_v6'::regclass);
a AS (val > 15) );
(1 row)
-DROP VIEW rpr_serial_v6;
-- {n} quantifier (exactly n)
CREATE VIEW rpr_serial_v7 AS
SELECT id, val, COUNT(*) OVER w as cnt
@@ -2163,7 +2157,6 @@ SELECT pg_get_viewdef('rpr_serial_v7'::regclass);
a AS (val > 0) );
(1 row)
-DROP VIEW rpr_serial_v7;
-- Nested ALT pattern (tests deparse of complex nested structure)
CREATE VIEW rpr_serial_v8 AS
SELECT id, val, COUNT(*) OVER w as cnt
@@ -2202,7 +2195,6 @@ SELECT pg_get_viewdef('rpr_serial_v8'::regclass);
d AS (val > 30) );
(1 row)
-DROP VIEW rpr_serial_v8;
-- Reluctant {1}? quantifier deparse through ruleutils
CREATE VIEW rpr_quant_reluctant_v AS
SELECT id, val, count(*) OVER w
@@ -2228,7 +2220,6 @@ SELECT pg_get_viewdef('rpr_quant_reluctant_v'::regclass);
b AS (val > 0) );
(1 row)
-DROP VIEW rpr_quant_reluctant_v;
-- Materialized view (if supported)
CREATE TABLE rpr_mview (id INT, val INT);
INSERT INTO rpr_mview VALUES (1, 10), (2, 20), (3, 30);
@@ -2274,8 +2265,6 @@ SELECT * FROM rpr_mview_v1 ORDER BY id;
3 | 30 | 0
(3 rows)
-DROP MATERIALIZED VIEW rpr_mview_v1;
-DROP TABLE rpr_mview;
-- CREATE TABLE AS SELECT with RPR
CREATE TABLE rpr_ctas (id INT, val INT);
INSERT INTO rpr_ctas VALUES (1, 10), (2, 20), (3, 15), (4, 25);
@@ -2677,8 +2666,6 @@ SELECT pg_get_viewdef('rpr_multiwin_v'::regclass);
b AS (val <= 15) );
(1 row)
-DROP VIEW rpr_multiwin_v;
-DROP TABLE rpr_multiwin;
-- {n} quantifier display in view
CREATE VIEW rpr_quant_n_v AS
SELECT id, val, count(*) OVER w
@@ -2703,7 +2690,6 @@ SELECT pg_get_viewdef('rpr_quant_n_v'::regclass);
a AS (val > 0) );
(1 row)
-DROP VIEW rpr_quant_n_v;
-- {n,} quantifier display in view
CREATE VIEW rpr_quant_n_plus_v AS
SELECT id, val, count(*) OVER w
@@ -2728,8 +2714,6 @@ SELECT pg_get_viewdef('rpr_quant_n_plus_v'::regclass);
a AS (val > 0) );
(1 row)
-DROP VIEW rpr_quant_n_plus_v;
-DROP TABLE rpr_serial;
-- ============================================================
-- Error Cases Tests
-- ============================================================
diff --git a/src/test/regress/expected/rpr_explain.out b/src/test/regress/expected/rpr_explain.out
index 817269021f4..3c70a12874a 100644
--- a/src/test/regress/expected/rpr_explain.out
+++ b/src/test/regress/expected/rpr_explain.out
@@ -3,6 +3,9 @@
-- Tests for Row Pattern Recognition EXPLAIN output
-- ============================================================
--
+-- Views and tables in this file are intentionally not dropped,
+-- so that pg_upgrade/pg_dump can test RPR syntax serialization.
+--
-- This test suite validates EXPLAIN output for RPR queries,
-- including NFA statistics shown in EXPLAIN ANALYZE:
-- - NFA States: peak, total, merged
@@ -74,13 +77,13 @@ begin
end;
$$;
-- Setup: Create test tables
-CREATE TEMP TABLE nfa_test (
+CREATE TABLE rpr_nfa_test (
id serial,
v int,
cat char(1)
);
-- Insert test data: 100 rows with predictable pattern
-INSERT INTO nfa_test (v, cat)
+INSERT INTO rpr_nfa_test (v, cat)
SELECT i,
CASE
WHEN i % 5 = 1 THEN 'A'
@@ -91,12 +94,12 @@ SELECT i,
END
FROM generate_series(1, 100) i;
-- Additional test table with more complex patterns
-CREATE TEMP TABLE nfa_complex (
+CREATE TABLE rpr_nfa_complex (
id serial,
price int,
trend char(1) -- U=up, D=down, S=stable
);
-INSERT INTO nfa_complex (price, trend)
+INSERT INTO rpr_nfa_complex (price, trend)
VALUES
(100, 'S'), (105, 'U'), (110, 'U'), (108, 'D'), (112, 'U'),
(115, 'U'), (113, 'D'), (111, 'D'), (109, 'D'), (110, 'U'),
@@ -108,16 +111,16 @@ VALUES
-- Basic NFA Statistics Tests
-- ============================================================
-- Simple pattern - should show basic statistics
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev01 AS
SELECT count(*) OVER w
-FROM nfa_test
+FROM rpr_nfa_test
WINDOW w AS (
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
AFTER MATCH SKIP PAST LAST ROW
PATTERN (A B)
DEFINE A AS cat = 'A', B AS cat = 'B'
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev01'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
------------------
PATTERN (a b)
@@ -126,7 +129,7 @@ SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line
SELECT rpr_explain_filter('
EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT count(*) OVER w
-FROM nfa_test
+FROM rpr_nfa_test
WINDOW w AS (
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
AFTER MATCH SKIP PAST LAST ROW
@@ -143,21 +146,20 @@ WINDOW w AS (
NFA Contexts: 2 peak, 101 total, 60 pruned
NFA: 20 matched (len 2/2/2.0), 0 mismatched
NFA: 0 absorbed, 20 skipped (len 1/1/1.0)
- -> Seq Scan on nfa_test (actual rows=100.00 loops=1)
+ -> Seq Scan on rpr_nfa_test (actual rows=100.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- Pattern with no matches - 0 matched
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev02 AS
SELECT count(*) OVER w
-FROM nfa_test
+FROM rpr_nfa_test
WINDOW w AS (
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
AFTER MATCH SKIP PAST LAST ROW
PATTERN (X Y Z)
DEFINE X AS cat = 'X', Y AS cat = 'Y', Z AS cat = 'Z'
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev02'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
--------------------
PATTERN (x y z)
@@ -166,7 +168,7 @@ SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line
SELECT rpr_explain_filter('
EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT count(*) OVER w
-FROM nfa_test
+FROM rpr_nfa_test
WINDOW w AS (
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
AFTER MATCH SKIP PAST LAST ROW
@@ -182,21 +184,20 @@ WINDOW w AS (
NFA States: 1 peak, 101 total, 0 merged
NFA Contexts: 2 peak, 101 total, 100 pruned
NFA: 0 matched, 0 mismatched
- -> Seq Scan on nfa_test (actual rows=100.00 loops=1)
+ -> Seq Scan on rpr_nfa_test (actual rows=100.00 loops=1)
(8 rows)
-DROP VIEW rpr_v;
-- Pattern matching every row - high match count
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev03 AS
SELECT count(*) OVER w
-FROM nfa_test
+FROM rpr_nfa_test
WINDOW w AS (
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
AFTER MATCH SKIP PAST LAST ROW
PATTERN (R)
DEFINE R AS TRUE
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev03'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
----------------
PATTERN (r)
@@ -205,7 +206,7 @@ SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line
SELECT rpr_explain_filter('
EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT count(*) OVER w
-FROM nfa_test
+FROM rpr_nfa_test
WINDOW w AS (
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
AFTER MATCH SKIP PAST LAST ROW
@@ -221,13 +222,12 @@ WINDOW w AS (
NFA States: 2 peak, 101 total, 0 merged
NFA Contexts: 2 peak, 101 total, 0 pruned
NFA: 100 matched (len 1/1/1.0), 0 mismatched
- -> Seq Scan on nfa_test (actual rows=100.00 loops=1)
+ -> Seq Scan on rpr_nfa_test (actual rows=100.00 loops=1)
(8 rows)
-DROP VIEW rpr_v;
-- Regression test: Space before parenthesis in pattern deparse
-- Verifies that "A (B | C)" correctly outputs as "a (b | c)" with space
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev04 AS
SELECT count(*) OVER w
FROM generate_series(1, 20) AS s(v)
WINDOW w AS (
@@ -235,7 +235,7 @@ WINDOW w AS (
PATTERN (A (B | C))
DEFINE A AS v % 3 = 1, B AS v % 3 = 2, C AS v % 3 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev04'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
------------------------
PATTERN (a (b | c))
@@ -263,11 +263,10 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=20.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- Regression test: Sequential alternations at same depth
-- Verifies that "((B | C) (D | E))" correctly outputs as "(b | c) (d | e)"
-- Previously failed due to missing parentheses on ALT depth decrease
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev05 AS
SELECT count(*) OVER w
FROM generate_series(1, 30) AS s(v)
WINDOW w AS (
@@ -275,7 +274,7 @@ WINDOW w AS (
PATTERN (A ((B | C) (D | E))*)
DEFINE A AS v % 5 = 1, B AS v % 5 = 2, C AS v % 5 = 3, D AS v % 5 = 4, E AS v % 5 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev05'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
-----------------------------------
PATTERN (a ((b | c) (d | e))*)
@@ -302,12 +301,11 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=30.00 loops=1)
(8 rows)
-DROP VIEW rpr_v;
-- ============================================================
-- State Statistics Tests (peak, total, merged)
-- ============================================================
-- Simple quantifier pattern - A+ with short matches (no merging)
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev06 AS
SELECT count(*) OVER w
FROM generate_series(1, 50) AS s(v)
WINDOW w AS (
@@ -316,7 +314,7 @@ WINDOW w AS (
PATTERN (A+)
DEFINE A AS v % 2 = 1
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev06'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
-----------------
PATTERN (a+)
@@ -344,11 +342,10 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=50.00 loops=1)
(8 rows)
-DROP VIEW rpr_v;
-- Alternation pattern - multiple state branches
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev07 AS
SELECT count(*) OVER w
-FROM nfa_test
+FROM rpr_nfa_test
WINDOW w AS (
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
AFTER MATCH SKIP PAST LAST ROW
@@ -357,7 +354,7 @@ WINDOW w AS (
A AS cat = 'A', B AS cat = 'B', C AS cat = 'C',
D AS cat = 'D', E AS cat = 'E'
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev07'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
----------------------------------
PATTERN ((a | b | c) (d | e))
@@ -366,7 +363,7 @@ SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line
SELECT rpr_explain_filter('
EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT count(*) OVER w
-FROM nfa_test
+FROM rpr_nfa_test
WINDOW w AS (
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
AFTER MATCH SKIP PAST LAST ROW
@@ -385,12 +382,11 @@ WINDOW w AS (
NFA Contexts: 3 peak, 101 total, 20 pruned
NFA: 20 matched (len 2/2/2.0), 40 mismatched (len 2/2/2.0)
NFA: 0 absorbed, 20 skipped (len 1/1/1.0)
- -> Seq Scan on nfa_test (actual rows=100.00 loops=1)
+ -> Seq Scan on rpr_nfa_test (actual rows=100.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- Complex pattern with high state count
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev08 AS
SELECT count(*) OVER w
FROM generate_series(1, 100) AS s(v)
WINDOW w AS (
@@ -402,7 +398,7 @@ WINDOW w AS (
B AS v % 3 = 2,
C AS v % 3 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev08'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
-----------------------
PATTERN (a+ b* c+)
@@ -434,9 +430,8 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=100.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- Grouped pattern with quantifier - state count with grouping
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev09 AS
SELECT count(*) OVER w
FROM generate_series(1, 60) AS s(v)
WINDOW w AS (
@@ -445,7 +440,7 @@ WINDOW w AS (
PATTERN ((A B)+)
DEFINE A AS v % 2 = 1, B AS v % 2 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev09'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
---------------------
PATTERN ((a b)+)
@@ -474,10 +469,9 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=60.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- State explosion pattern - many alternations
-- Pattern (A|B)(A|B)(A|B)(A|B) can create many parallel states
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev10 AS
SELECT count(*) OVER w
FROM generate_series(1, 100) AS s(v)
WINDOW w AS (
@@ -486,7 +480,7 @@ WINDOW w AS (
PATTERN ((A | B) (A | B) (A | B) (A | B) (A | B) (A | B) (A | B) (A | B))
DEFINE A AS v % 2 = 1, B AS v % 2 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev10'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
------------------------------------------------------------------------------
PATTERN ((a | b) (a | b) (a | b) (a | b) (a | b) (a | b) (a | b) (a | b))
@@ -515,10 +509,9 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=100.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- Consecutive ALT merge followed by different ALT
-- Tests mergeConsecutiveAlts flush on ALT change: (A|B){2} (C|D)
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev11 AS
SELECT count(*) OVER w
FROM generate_series(1, 40) AS s(v)
WINDOW w AS (
@@ -527,7 +520,7 @@ WINDOW w AS (
PATTERN ((A | B) (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_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev11'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
--------------------------------------
PATTERN ((a | b) (a | b) (c | d))
@@ -556,10 +549,9 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=40.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- Consecutive ALT merge followed by non-ALT element
-- Tests mergeConsecutiveAlts flush on non-ALT: (A|B){2} c
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev12 AS
SELECT count(*) OVER w
FROM generate_series(1, 40) AS s(v)
WINDOW w AS (
@@ -568,7 +560,7 @@ WINDOW w AS (
PATTERN ((A | B) (A | B) C)
DEFINE A AS v % 3 = 0, B AS v % 3 = 1, C AS v % 3 = 2
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev12'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
--------------------------------
PATTERN ((a | b) (a | b) c)
@@ -597,9 +589,8 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=40.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- ALT prefix/suffix absorbed into GROUP: (A|B) (A|B)+ (A|B) -> (A|B){3,}
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev13 AS
SELECT count(*) OVER w
FROM generate_series(1, 40) AS s(v)
WINDOW w AS (
@@ -608,7 +599,7 @@ WINDOW w AS (
PATTERN ((A | B) (A | B)+ (A | B))
DEFINE A AS v % 2 = 0, B AS v % 2 = 1
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev13'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
---------------------------------------
PATTERN ((a | b) (a | b)+ (a | b))
@@ -637,9 +628,8 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=40.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- High state count - alternation with plus quantifier
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev14 AS
SELECT count(*) OVER w
FROM generate_series(1, 100) AS s(v)
WINDOW w AS (
@@ -648,7 +638,7 @@ WINDOW w AS (
PATTERN ((A | B | C)+ D)
DEFINE A AS v % 4 = 1, B AS v % 4 = 2, C AS v % 4 = 3, D AS v % 4 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev14'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
-----------------------------
PATTERN ((a | b | c)+ d)
@@ -677,10 +667,9 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=100.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- Early termination: first ALT branch (A) reaches FIN immediately,
-- pruning second branch (A B+) before it can accumulate B repetitions.
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev15 AS
SELECT count(*) OVER w
FROM generate_series(1, 100) AS s(v)
WINDOW w AS (
@@ -689,7 +678,7 @@ WINDOW w AS (
PATTERN ((A | A B)+)
DEFINE A AS v = 1, B AS v > 1
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev15'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
-------------------------
PATTERN ((a | a b)+)
@@ -717,9 +706,8 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=100.00 loops=1)
(8 rows)
-DROP VIEW rpr_v;
-- Nested quantifiers causing state growth
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev16 AS
SELECT count(*) OVER w
FROM generate_series(1, 1000) AS s(v)
WINDOW w AS (
@@ -728,7 +716,7 @@ WINDOW w AS (
PATTERN (((A | B)+)+)
DEFINE A AS v % 3 = 1, B AS v % 3 = 2
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev16'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
--------------------------
PATTERN (((a | b)+)+)
@@ -757,12 +745,11 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=1000.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- ============================================================
-- Context Statistics Tests (peak, total, pruned + absorbed/skipped)
-- ============================================================
-- Context absorption with unbounded quantifier at start
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev17 AS
SELECT count(*) OVER w
FROM generate_series(1, 50) AS s(v)
WINDOW w AS (
@@ -771,7 +758,7 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS v % 5 <> 0, B AS v % 5 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev17'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
-------------------
PATTERN (a+ b)
@@ -800,9 +787,8 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=50.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- No absorption - bounded quantifier
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev18 AS
SELECT count(*) OVER w
FROM generate_series(1, 50) AS s(v)
WINDOW w AS (
@@ -811,7 +797,7 @@ WINDOW w AS (
PATTERN (A{2,4} B)
DEFINE A AS v % 5 <> 0, B AS v % 5 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev18'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
-----------------------
PATTERN (a{2,4} b)
@@ -840,9 +826,8 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=50.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- Contexts skipped by SKIP PAST LAST ROW
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev19 AS
SELECT count(*) OVER w
FROM generate_series(1, 100) AS s(v)
WINDOW w AS (
@@ -851,7 +836,7 @@ WINDOW w AS (
PATTERN (A B C)
DEFINE A AS v % 10 = 1, B AS v % 10 = 2, C AS v % 10 = 3
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev19'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
--------------------
PATTERN (a b c)
@@ -880,9 +865,8 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=100.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- High context absorption - unbounded group
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev20 AS
SELECT count(*) OVER w
FROM generate_series(1, 100) AS s(v)
WINDOW w AS (
@@ -891,7 +875,7 @@ WINDOW w AS (
PATTERN ((A B)+ C)
DEFINE A AS v % 3 = 1, B AS v % 3 = 2, C AS v % 3 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev20'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
-----------------------
PATTERN ((a b)+ c)
@@ -920,14 +904,13 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=100.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- ============================================================
-- Match Length Statistics Tests
-- ============================================================
-- Fixed length matches - all same length
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev21 AS
SELECT count(*) OVER w
-FROM nfa_test
+FROM rpr_nfa_test
WINDOW w AS (
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
AFTER MATCH SKIP PAST LAST ROW
@@ -936,7 +919,7 @@ WINDOW w AS (
A AS cat = 'A', B AS cat = 'B', C AS cat = 'C',
D AS cat = 'D', E AS cat = 'E'
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev21'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
------------------------
PATTERN (a b c d e)
@@ -945,7 +928,7 @@ SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line
SELECT rpr_explain_filter('
EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT count(*) OVER w
-FROM nfa_test
+FROM rpr_nfa_test
WINDOW w AS (
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
AFTER MATCH SKIP PAST LAST ROW
@@ -964,12 +947,11 @@ WINDOW w AS (
NFA Contexts: 3 peak, 101 total, 60 pruned
NFA: 20 matched (len 5/5/5.0), 0 mismatched
NFA: 0 absorbed, 20 skipped (len 1/1/1.0)
- -> Seq Scan on nfa_test (actual rows=100.00 loops=1)
+ -> Seq Scan on rpr_nfa_test (actual rows=100.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- Variable length matches - min/max/avg differ
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev22 AS
SELECT count(*) OVER w
FROM generate_series(1, 100) AS s(v)
WINDOW w AS (
@@ -978,7 +960,7 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS v % 10 <> 0, B AS v % 10 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev22'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
-------------------
PATTERN (a+ b)
@@ -1007,9 +989,8 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=100.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- Very long matches
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev23 AS
SELECT count(*) OVER w
FROM generate_series(1, 200) AS s(v)
WINDOW w AS (
@@ -1018,7 +999,7 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS v <= 195, B AS v > 195
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev23'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
-------------------
PATTERN (a+ b)
@@ -1047,9 +1028,8 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=200.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- Uniform match length with mismatches from gap rows (v%20 = 11..15)
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev24 AS
SELECT count(*) OVER w
FROM generate_series(1, 100) AS s(v)
WINDOW w AS (
@@ -1060,7 +1040,7 @@ WINDOW w AS (
A AS (v % 20 <> 0) AND (v % 20 <= 10 OR v % 20 > 15),
B AS v % 20 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev24'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
-------------------
PATTERN (a+ b)
@@ -1091,13 +1071,12 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=100.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- ============================================================
-- Mismatch Length Statistics Tests
-- ============================================================
-- Pattern with complete match every cycle: 0 mismatched
-- A(1,2,3) B(4,5) C(6) repeats perfectly; X rows are pruned, not mismatched
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev25 AS
SELECT count(*) OVER w
FROM (
SELECT v,
@@ -1113,7 +1092,7 @@ WINDOW w AS (
PATTERN (A+ B+ C)
DEFINE A AS cat = 'A', B AS cat = 'B', C AS cat = 'C'
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev25'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
----------------------
PATTERN (a+ b+ c)
@@ -1149,9 +1128,8 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=100.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- Long partial matches that fail
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev26 AS
SELECT count(*) OVER w
FROM (
SELECT i AS v,
@@ -1172,7 +1150,7 @@ WINDOW w AS (
PATTERN (A+ B+ C)
DEFINE A AS cat = 'A', B AS cat = 'B', C AS cat = 'C'
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev26'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
----------------------
PATTERN (a+ b+ c)
@@ -1213,12 +1191,11 @@ WINDOW w AS (
-> Function Scan on generate_series i (actual rows=60.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- ============================================================
-- JSON Format Tests
-- ============================================================
-- JSON format output with all statistics
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev27 AS
SELECT count(*) OVER w
FROM generate_series(1, 50) AS s(v)
WINDOW w AS (
@@ -1227,7 +1204,7 @@ WINDOW w AS (
PATTERN (A+ B+)
DEFINE A AS v % 3 = 1, B AS v % 3 = 2
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev27'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
--------------------
PATTERN (a+ b+)
@@ -1294,9 +1271,8 @@ WINDOW w AS (
]
(1 row)
-DROP VIEW rpr_v;
-- JSON format with match length statistics
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev28 AS
SELECT count(*) OVER w
FROM generate_series(1, 100) AS s(v)
WINDOW w AS (
@@ -1305,7 +1281,7 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS v % 10 <> 0, B AS v % 10 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev28'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
-------------------
PATTERN (a+ b)
@@ -1375,10 +1351,9 @@ WINDOW w AS (
]
(1 row)
-DROP VIEW rpr_v;
-- JSON format with mismatch statistics
-- Pattern A B C expects 1,2,3 but gets 1,2,4 twice causing mismatches
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev29 AS
SELECT count(*) OVER w
FROM (VALUES (1),(2),(4), (1),(2),(4), (1),(2),(3)) AS t(v)
WINDOW w AS (
@@ -1387,7 +1362,7 @@ WINDOW w AS (
PATTERN (A B C)
DEFINE A AS v = 1, B AS v = 2, C AS v = 3
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev29'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
--------------------
PATTERN (a b c)
@@ -1456,10 +1431,9 @@ WINDOW w AS (
]
(1 row)
-DROP VIEW rpr_v;
-- JSON format with skipped context statistics
-- Alternation pattern with SKIP PAST LAST ROW causes many contexts to be skipped
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev30 AS
SELECT count(*) OVER w
FROM generate_series(1, 100) AS s(v)
WINDOW w AS (
@@ -1468,7 +1442,7 @@ WINDOW w AS (
PATTERN ((A | B) (A | B) (A | B) (A | B) (A | B) (A | B) (A | B) (A | B))
DEFINE A AS v % 2 = 1, B AS v % 2 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev30'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
------------------------------------------------------------------------------
PATTERN ((a | b) (a | b) (a | b) (a | b) (a | b) (a | b) (a | b) (a | b))
@@ -1538,12 +1512,11 @@ WINDOW w AS (
]
(1 row)
-DROP VIEW rpr_v;
-- ============================================================
-- XML Format Tests
-- ============================================================
-- XML format output
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev31 AS
SELECT count(*) OVER w
FROM generate_series(1, 30) AS s(v)
WINDOW w AS (
@@ -1552,7 +1525,7 @@ WINDOW w AS (
PATTERN (A B)
DEFINE A AS v % 2 = 1, B AS v % 2 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev31'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
------------------
PATTERN (a b)
@@ -1619,12 +1592,11 @@ WINDOW w AS (
</explain>
(1 row)
-DROP VIEW rpr_v;
-- ============================================================
-- Multiple Partitions Tests
-- ============================================================
-- Statistics across multiple partitions
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev32 AS
SELECT count(*) OVER w
FROM (
SELECT p, v
@@ -1638,7 +1610,7 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS v % 5 <> 0, B AS v % 5 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev32'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
-------------------
PATTERN (a+ b)
@@ -1677,9 +1649,8 @@ WINDOW w AS (
-> Function Scan on generate_series v (actual rows=30.00 loops=3)
(14 rows)
-DROP VIEW rpr_v;
-- Different pattern behavior per partition
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev33 AS
SELECT count(*) OVER w
FROM (
SELECT
@@ -1694,7 +1665,7 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS val < 5, B AS val >= 5
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev33'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
-------------------
PATTERN (a+ b)
@@ -1732,12 +1703,11 @@ WINDOW w AS (
-> Function Scan on generate_series v (actual rows=50.00 loops=1)
(12 rows)
-DROP VIEW rpr_v;
-- ============================================================
-- Edge Cases
-- ============================================================
-- Empty result set
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev34 AS
SELECT count(*) OVER w
FROM generate_series(1, 0) AS s(v)
WINDOW w AS (
@@ -1746,7 +1716,7 @@ WINDOW w AS (
PATTERN (A B)
DEFINE A AS v = 1, B AS v = 2
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev34'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
------------------
PATTERN (a b)
@@ -1770,9 +1740,8 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=0.00 loops=1)
(4 rows)
-DROP VIEW rpr_v;
-- Single row
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev35 AS
SELECT count(*) OVER w
FROM generate_series(1, 1) AS s(v)
WINDOW w AS (
@@ -1781,7 +1750,7 @@ WINDOW w AS (
PATTERN (A)
DEFINE A AS TRUE
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev35'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
----------------
PATTERN (a)
@@ -1809,9 +1778,8 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=1.00 loops=1)
(8 rows)
-DROP VIEW rpr_v;
-- Pattern longer than data
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev36 AS
SELECT count(*) OVER w
FROM generate_series(1, 5) AS s(v)
WINDOW w AS (
@@ -1822,7 +1790,7 @@ WINDOW w AS (
A AS v = 1, B AS v = 2, C AS v = 3, D AS v = 4, E AS v = 5,
F AS v = 6, G AS v = 7, H AS v = 8, I AS v = 9, J AS v = 10
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev36'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
----------------------------------
PATTERN (a b c d e f g h i j)
@@ -1852,9 +1820,8 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=5.00 loops=1)
(8 rows)
-DROP VIEW rpr_v;
-- All rows match as single match
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev37 AS
SELECT count(*) OVER w
FROM generate_series(1, 50) AS s(v)
WINDOW w AS (
@@ -1863,7 +1830,7 @@ WINDOW w AS (
PATTERN (A+)
DEFINE A AS TRUE
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev37'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
-----------------
PATTERN (a+)
@@ -1892,12 +1859,11 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=50.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- ============================================================
-- Complex Pattern Tests
-- ============================================================
-- Nested groups
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev38 AS
SELECT count(*) OVER w
FROM generate_series(1, 60) AS s(v)
WINDOW w AS (
@@ -1906,7 +1872,7 @@ WINDOW w AS (
PATTERN (((A B) C)+)
DEFINE A AS v % 3 = 1, B AS v % 3 = 2, C AS v % 3 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev38'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
-------------------------
PATTERN (((a b) c)+)
@@ -1935,11 +1901,10 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=60.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- Multiple alternations
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev39 AS
SELECT count(*) OVER w
-FROM nfa_test
+FROM rpr_nfa_test
WINDOW w AS (
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
AFTER MATCH SKIP PAST LAST ROW
@@ -1948,7 +1913,7 @@ WINDOW w AS (
A AS cat = 'A', B AS cat = 'B', C AS cat = 'C',
D AS cat = 'D', E AS cat = 'E'
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev39'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
----------------------------------
PATTERN ((a | b) (c | d | e))
@@ -1957,7 +1922,7 @@ SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line
SELECT rpr_explain_filter('
EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT count(*) OVER w
-FROM nfa_test
+FROM rpr_nfa_test
WINDOW w AS (
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
AFTER MATCH SKIP PAST LAST ROW
@@ -1976,12 +1941,11 @@ WINDOW w AS (
NFA Contexts: 3 peak, 101 total, 40 pruned
NFA: 20 matched (len 2/2/2.0), 20 mismatched (len 2/2/2.0)
NFA: 0 absorbed, 20 skipped (len 1/1/1.0)
- -> Seq Scan on nfa_test (actual rows=100.00 loops=1)
+ -> Seq Scan on rpr_nfa_test (actual rows=100.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- Optional elements
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev40 AS
SELECT count(*) OVER w
FROM generate_series(1, 50) AS s(v)
WINDOW w AS (
@@ -1990,7 +1954,7 @@ WINDOW w AS (
PATTERN (A B? C)
DEFINE A AS v % 4 = 1, B AS v % 4 = 2, C AS v % 4 = 3
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev40'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
---------------------
PATTERN (a b? c)
@@ -2019,9 +1983,8 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=50.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- Bounded quantifiers
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev41 AS
SELECT count(*) OVER w
FROM generate_series(1, 100) AS s(v)
WINDOW w AS (
@@ -2030,7 +1993,7 @@ WINDOW w AS (
PATTERN (A{2,5} B)
DEFINE A AS v % 10 <> 0, B AS v % 10 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev41'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
-----------------------
PATTERN (a{2,5} b)
@@ -2059,9 +2022,8 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=100.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- Star quantifier
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev42 AS
SELECT count(*) OVER w
FROM generate_series(1, 50) AS s(v)
WINDOW w AS (
@@ -2070,7 +2032,7 @@ WINDOW w AS (
PATTERN (A B* C)
DEFINE A AS v % 10 = 1, B AS v % 10 IN (2,3,4,5,6,7,8), C AS v % 10 = 9
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev42'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
---------------------
PATTERN (a b* c)
@@ -2099,21 +2061,20 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=50.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- ============================================================
-- Real-world Pattern Examples
-- ============================================================
-- Stock price pattern - V-shape (down then up)
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev43 AS
SELECT count(*) OVER w
-FROM nfa_complex
+FROM rpr_nfa_complex
WINDOW w AS (
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
AFTER MATCH SKIP PAST LAST ROW
PATTERN (D+ U+)
DEFINE D AS trend = 'D', U AS trend = 'U'
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev43'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
--------------------
PATTERN (d+ u+)
@@ -2122,7 +2083,7 @@ SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line
SELECT rpr_explain_filter('
EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT count(*) OVER w
-FROM nfa_complex
+FROM rpr_nfa_complex
WINDOW w AS (
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
AFTER MATCH SKIP PAST LAST ROW
@@ -2139,21 +2100,20 @@ WINDOW w AS (
NFA Contexts: 3 peak, 31 total, 3 pruned
NFA: 3 matched (len 3/14/8.0), 1 mismatched (len 3/3/3.0)
NFA: 9 absorbed (len 1/1/1.0), 14 skipped (len 1/1/1.0)
- -> Seq Scan on nfa_complex (actual rows=30.00 loops=1)
+ -> Seq Scan on rpr_nfa_complex (actual rows=30.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- Stock price pattern - peak (up, stable, down)
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev44 AS
SELECT count(*) OVER w
-FROM nfa_complex
+FROM rpr_nfa_complex
WINDOW w AS (
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
AFTER MATCH SKIP PAST LAST ROW
PATTERN (U+ S* D+)
DEFINE U AS trend = 'U', S AS trend = 'S', D AS trend = 'D'
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev44'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
-----------------------
PATTERN (u+ s* d+)
@@ -2162,7 +2122,7 @@ SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line
SELECT rpr_explain_filter('
EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT count(*) OVER w
-FROM nfa_complex
+FROM rpr_nfa_complex
WINDOW w AS (
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
AFTER MATCH SKIP PAST LAST ROW
@@ -2179,12 +2139,11 @@ WINDOW w AS (
NFA Contexts: 3 peak, 31 total, 1 pruned
NFA: 4 matched (len 3/11/7.2), 0 mismatched
NFA: 12 absorbed (len 1/1/1.0), 13 skipped (len 1/1/1.0)
- -> Seq Scan on nfa_complex (actual rows=30.00 loops=1)
+ -> Seq Scan on rpr_nfa_complex (actual rows=30.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- Consecutive increasing values (using PREV)
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev45 AS
SELECT count(*) OVER w
FROM generate_series(1, 50) AS s(v)
WINDOW w AS (
@@ -2193,7 +2152,7 @@ WINDOW w AS (
PATTERN (A{3,})
DEFINE A AS v > PREV(v) OR PREV(v) IS NULL
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev45'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
--------------------
PATTERN (a{3,})
@@ -2222,12 +2181,11 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=50.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- ============================================================
-- Performance-oriented Tests
-- ============================================================
-- Large dataset with simple pattern
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev46 AS
SELECT count(*) OVER w
FROM generate_series(1, 1000) AS s(v)
WINDOW w AS (
@@ -2236,7 +2194,7 @@ WINDOW w AS (
PATTERN (A B)
DEFINE A AS v % 2 = 1, B AS v % 2 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev46'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
------------------
PATTERN (a b)
@@ -2265,9 +2223,8 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=1000.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- Large dataset with absorption
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev47 AS
SELECT count(*) OVER w
FROM generate_series(1, 1000) AS s(v)
WINDOW w AS (
@@ -2276,7 +2233,7 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS v % 100 <> 0, B AS v % 100 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev47'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
-------------------
PATTERN (a+ b)
@@ -2305,9 +2262,8 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=1000.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- High state merge ratio
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev48 AS
SELECT count(*) OVER w
FROM generate_series(1, 500) AS s(v)
WINDOW w AS (
@@ -2316,7 +2272,7 @@ WINDOW w AS (
PATTERN ((A | B)+ C)
DEFINE A AS v % 3 = 1, B AS v % 3 = 2, C AS v % 3 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev48'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
-------------------------
PATTERN ((a | b)+ c)
@@ -2345,12 +2301,11 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=500.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- ============================================================
-- INITIAL vs no INITIAL comparison
-- ============================================================
-- With INITIAL keyword
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev49 AS
SELECT count(*) OVER w
FROM generate_series(1, 50) AS s(v)
WINDOW w AS (
@@ -2360,7 +2315,7 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS v % 5 <> 0, B AS v % 5 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev49'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
-------------------
PATTERN (a+ b)
@@ -2390,9 +2345,8 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=50.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- Without INITIAL keyword (same behavior currently)
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev50 AS
SELECT count(*) OVER w
FROM generate_series(1, 50) AS s(v)
WINDOW w AS (
@@ -2401,7 +2355,7 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS v % 5 <> 0, B AS v % 5 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev50'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
-------------------
PATTERN (a+ b)
@@ -2430,12 +2384,11 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=50.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- ============================================================
-- Quantifier Variations
-- ============================================================
-- Plus quantifier
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev51 AS
SELECT count(*) OVER w
FROM generate_series(1, 40) AS s(v)
WINDOW w AS (
@@ -2444,7 +2397,7 @@ WINDOW w AS (
PATTERN (A+)
DEFINE A AS v % 4 <> 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev51'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
-----------------
PATTERN (a+)
@@ -2473,9 +2426,8 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=40.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- Star quantifier (zero or more)
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev52 AS
SELECT count(*) OVER w
FROM generate_series(1, 40) AS s(v)
WINDOW w AS (
@@ -2484,7 +2436,7 @@ WINDOW w AS (
PATTERN (A* B)
DEFINE A AS v % 4 IN (1, 2), B AS v % 4 = 3
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev52'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
-------------------
PATTERN (a* b)
@@ -2513,9 +2465,8 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=40.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- Question mark (zero or one)
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev53 AS
SELECT count(*) OVER w
FROM generate_series(1, 40) AS s(v)
WINDOW w AS (
@@ -2524,7 +2475,7 @@ WINDOW w AS (
PATTERN (A? B C)
DEFINE A AS v % 4 = 1, B AS v % 4 = 2, C AS v % 4 = 3
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev53'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
---------------------
PATTERN (a? b c)
@@ -2553,9 +2504,8 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=40.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- Exact count {n}
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev54 AS
SELECT count(*) OVER w
FROM generate_series(1, 50) AS s(v)
WINDOW w AS (
@@ -2564,7 +2514,7 @@ WINDOW w AS (
PATTERN (A{3} B)
DEFINE A AS v % 5 <> 0, B AS v % 5 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev54'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
---------------------
PATTERN (a{3} b)
@@ -2593,9 +2543,8 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=50.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- Range {n,m}
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev55 AS
SELECT count(*) OVER w
FROM generate_series(1, 50) AS s(v)
WINDOW w AS (
@@ -2604,7 +2553,7 @@ WINDOW w AS (
PATTERN (A{2,4} B)
DEFINE A AS v % 5 <> 0, B AS v % 5 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev55'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
-----------------------
PATTERN (a{2,4} b)
@@ -2633,9 +2582,8 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=50.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- At least {n,}
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev56 AS
SELECT count(*) OVER w
FROM generate_series(1, 50) AS s(v)
WINDOW w AS (
@@ -2644,7 +2592,7 @@ WINDOW w AS (
PATTERN (A{3,} B)
DEFINE A AS v % 10 <> 0, B AS v % 10 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev56'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
----------------------
PATTERN (a{3,} b)
@@ -2673,13 +2621,12 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=50.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- ============================================================
-- Regression Tests for Statistics Accuracy
-- ============================================================
-- Verify state count accuracy
-- Pattern A+ B with 20 rows should show predictable state behavior
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev57 AS
SELECT count(*) OVER w
FROM generate_series(1, 20) AS s(v)
WINDOW w AS (
@@ -2688,7 +2635,7 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS v % 5 <> 0, B AS v % 5 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev57'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
-------------------
PATTERN (a+ b)
@@ -2717,9 +2664,8 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=20.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- Verify context count with known absorption
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev58 AS
SELECT count(*) OVER w
FROM generate_series(1, 30) AS s(v)
WINDOW w AS (
@@ -2728,7 +2674,7 @@ WINDOW w AS (
PATTERN (A+ B C)
DEFINE A AS v % 10 IN (1,2,3,4,5,6,7), B AS v % 10 = 8, C AS v % 10 = 9
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev58'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
---------------------
PATTERN (a+ b c)
@@ -2757,9 +2703,8 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=30.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- Verify match length with fixed-length pattern
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev59 AS
SELECT count(*) OVER w
FROM generate_series(1, 30) AS s(v)
WINDOW w AS (
@@ -2768,7 +2713,7 @@ WINDOW w AS (
PATTERN (A B C)
DEFINE A AS v % 3 = 1, B AS v % 3 = 2, C AS v % 3 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev59'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
--------------------
PATTERN (a b c)
@@ -2797,21 +2742,20 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=30.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- ============================================================
-- Alternation Pattern Tests
-- ============================================================
-- Simple alternation
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev60 AS
SELECT count(*) OVER w
-FROM nfa_test
+FROM rpr_nfa_test
WINDOW w AS (
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
AFTER MATCH SKIP PAST LAST ROW
PATTERN ((A | B) C)
DEFINE A AS cat = 'A', B AS cat = 'B', C AS cat = 'C'
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev60'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
------------------------
PATTERN ((a | b) c)
@@ -2820,7 +2764,7 @@ SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line
SELECT rpr_explain_filter('
EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT count(*) OVER w
-FROM nfa_test
+FROM rpr_nfa_test
WINDOW w AS (
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
AFTER MATCH SKIP PAST LAST ROW
@@ -2837,14 +2781,13 @@ WINDOW w AS (
NFA Contexts: 3 peak, 101 total, 40 pruned
NFA: 20 matched (len 2/2/2.0), 20 mismatched (len 2/2/2.0)
NFA: 0 absorbed, 20 skipped (len 1/1/1.0)
- -> Seq Scan on nfa_test (actual rows=100.00 loops=1)
+ -> Seq Scan on rpr_nfa_test (actual rows=100.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- Multiple items in alternation
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev61 AS
SELECT count(*) OVER w
-FROM nfa_test
+FROM rpr_nfa_test
WINDOW w AS (
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
AFTER MATCH SKIP PAST LAST ROW
@@ -2853,7 +2796,7 @@ WINDOW w AS (
A AS cat = 'A', B AS cat = 'B', C AS cat = 'C',
D AS cat = 'D', E AS cat = 'E'
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev61'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
--------------------------------
PATTERN ((a | b | c | d) e)
@@ -2862,7 +2805,7 @@ SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line
SELECT rpr_explain_filter('
EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT count(*) OVER w
-FROM nfa_test
+FROM rpr_nfa_test
WINDOW w AS (
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
AFTER MATCH SKIP PAST LAST ROW
@@ -2881,12 +2824,11 @@ WINDOW w AS (
NFA Contexts: 3 peak, 101 total, 0 pruned
NFA: 20 matched (len 2/2/2.0), 60 mismatched (len 2/2/2.0)
NFA: 0 absorbed, 20 skipped (len 1/1/1.0)
- -> Seq Scan on nfa_test (actual rows=100.00 loops=1)
+ -> Seq Scan on rpr_nfa_test (actual rows=100.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- Alternation with quantifiers
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev62 AS
SELECT count(*) OVER w
FROM generate_series(1, 50) AS s(v)
WINDOW w AS (
@@ -2895,7 +2837,7 @@ WINDOW w AS (
PATTERN ((A | B)+ C)
DEFINE A AS v % 3 = 1, B AS v % 3 = 2, C AS v % 3 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev62'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
-------------------------
PATTERN ((a | b)+ c)
@@ -2924,9 +2866,8 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=50.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- Multiple alternatives (4+)
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev63 AS
SELECT count(*) OVER w
FROM generate_series(1, 100) AS s(v)
WINDOW w AS (
@@ -2934,7 +2875,7 @@ WINDOW w AS (
PATTERN (A | B | C | D | E)
DEFINE A AS v % 5 = 0, B AS v % 5 = 1, C AS v % 5 = 2, D AS v % 5 = 3, E AS v % 5 = 4
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev63'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
--------------------------------
PATTERN (a | b | c | d | e)
@@ -2961,9 +2902,8 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=100.00 loops=1)
(8 rows)
-DROP VIEW rpr_v;
-- Alternation at start
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev64 AS
SELECT count(*) OVER w
FROM generate_series(1, 60) AS s(v)
WINDOW w AS (
@@ -2971,7 +2911,7 @@ WINDOW w AS (
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_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev64'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
--------------------------
PATTERN ((a | b) c d)
@@ -2999,9 +2939,8 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=60.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- Multiple sequential alternations
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev65 AS
SELECT count(*) OVER w
FROM generate_series(1, 100) AS s(v)
WINDOW w AS (
@@ -3009,7 +2948,7 @@ WINDOW w AS (
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_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev65'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
----------------------------------
PATTERN ((a | b) c (d | e) f)
@@ -3036,9 +2975,8 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=100.00 loops=1)
(8 rows)
-DROP VIEW rpr_v;
-- Quantified alternatives
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev66 AS
SELECT count(*) OVER w
FROM generate_series(1, 60) AS s(v)
WINDOW w AS (
@@ -3046,7 +2984,7 @@ WINDOW w AS (
PATTERN ((A+ | B+) C)
DEFINE A AS v % 3 = 0, B AS v % 3 = 1, C AS v % 3 = 2
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev66'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
--------------------------
PATTERN ((a+ | b+) c)
@@ -3074,9 +3012,8 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=60.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- Alternation at end
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev67 AS
SELECT count(*) OVER w
FROM generate_series(1, 60) AS s(v)
WINDOW w AS (
@@ -3084,7 +3021,7 @@ WINDOW w AS (
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_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev67'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
--------------------------
PATTERN (a b (c | d))
@@ -3112,10 +3049,9 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=60.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- Nested ALT at start of branch inside outer ALT
-- Pattern: (A ((B | C) D | E)) - preceding VAR + inner ALT as first branch element
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev68 AS
SELECT count(*) OVER w
FROM generate_series(1, 20) AS s(v)
WINDOW w AS (
@@ -3123,7 +3059,7 @@ WINDOW w AS (
PATTERN (A ((B | C) D | E))
DEFINE A AS v % 5 = 0, B AS v % 5 = 1, C AS v % 5 = 2, D AS v % 5 = 3, E AS v % 5 = 4
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev68'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
--------------------------------
PATTERN (a ((b | c) d | e))
@@ -3150,10 +3086,9 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=20.00 loops=1)
(8 rows)
-DROP VIEW rpr_v;
-- Nested ALT at end of branch inside outer ALT
-- Pattern: (C (A | B) | D) - inner ALT is last element in outer branch
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev69 AS
SELECT count(*) OVER w
FROM generate_series(1, 20) AS s(v)
WINDOW w AS (
@@ -3161,7 +3096,7 @@ WINDOW w AS (
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_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev69'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
----------------------------
PATTERN (c (a | b) | d)
@@ -3188,12 +3123,11 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=20.00 loops=1)
(8 rows)
-DROP VIEW rpr_v;
-- ============================================================
-- Group Pattern Tests
-- ============================================================
-- Simple group
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev70 AS
SELECT count(*) OVER w
FROM generate_series(1, 40) AS s(v)
WINDOW w AS (
@@ -3202,7 +3136,7 @@ WINDOW w AS (
PATTERN ((A B)+)
DEFINE A AS v % 2 = 1, B AS v % 2 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev70'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
---------------------
PATTERN ((a b)+)
@@ -3231,9 +3165,8 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=40.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- Group with bounded quantifier
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev71 AS
SELECT count(*) OVER w
FROM generate_series(1, 40) AS s(v)
WINDOW w AS (
@@ -3242,7 +3175,7 @@ WINDOW w AS (
PATTERN ((A B){2,4})
DEFINE A AS v % 2 = 1, B AS v % 2 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev71'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
-------------------------
PATTERN ((a b){2,4})
@@ -3271,9 +3204,8 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=40.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- Nested groups
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev72 AS
SELECT count(*) OVER w
FROM generate_series(1, 60) AS s(v)
WINDOW w AS (
@@ -3282,7 +3214,7 @@ WINDOW w AS (
PATTERN (((A B){2})+)
DEFINE A AS v % 2 = 1, B AS v % 2 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev72'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
--------------------------
PATTERN (((a b){2})+)
@@ -3311,9 +3243,8 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=60.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- Deep nesting (3+ levels)
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev73 AS
SELECT count(*) OVER w
FROM generate_series(1, 40) AS s(v)
WINDOW w AS (
@@ -3321,7 +3252,7 @@ WINDOW w AS (
PATTERN ((((A | B)+)+)+)
DEFINE A AS v % 2 = 0, B AS v % 2 = 1
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev73'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
-----------------------------
PATTERN ((((a | b)+)+)+)
@@ -3349,9 +3280,8 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=40.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- Bounded quantifier on alternation
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev74 AS
SELECT count(*) OVER w
FROM generate_series(1, 60) AS s(v)
WINDOW w AS (
@@ -3359,7 +3289,7 @@ WINDOW w AS (
PATTERN ((A | B){2,3} C)
DEFINE A AS v % 3 = 0, B AS v % 3 = 1, C AS v % 3 = 2
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev74'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
-----------------------------
PATTERN ((a | b){2,3} c)
@@ -3387,9 +3317,8 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=60.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- Nested groups with quantifiers
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev75 AS
SELECT count(*) OVER w
FROM generate_series(1, 60) AS s(v)
WINDOW w AS (
@@ -3397,7 +3326,7 @@ WINDOW w AS (
PATTERN (((A B)+ C)*)
DEFINE A AS v % 3 = 0, B AS v % 3 = 1, C AS v % 3 = 2
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev75'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
--------------------------
PATTERN (((a b)+ c)*)
@@ -3425,9 +3354,8 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=60.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- Partial nested quantification
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev76 AS
SELECT count(*) OVER w
FROM generate_series(1, 60) AS s(v)
WINDOW w AS (
@@ -3435,7 +3363,7 @@ WINDOW w AS (
PATTERN ((A (B C)+)*)
DEFINE A AS v % 3 = 0, B AS v % 3 = 1, C AS v % 3 = 2
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev76'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
--------------------------
PATTERN ((a (b c)+)*)
@@ -3463,12 +3391,11 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=60.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- ============================================================
-- Window Function Combinations
-- ============================================================
-- count(*) with pattern
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev77 AS
SELECT count(*) OVER w
FROM generate_series(1, 30) AS s(v)
WINDOW w AS (
@@ -3477,7 +3404,7 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS v % 5 <> 0, B AS v % 5 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev77'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
-------------------
PATTERN (a+ b)
@@ -3506,9 +3433,8 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=30.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- first_value with pattern
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev78 AS
SELECT first_value(v) OVER w
FROM generate_series(1, 30) AS s(v)
WINDOW w AS (
@@ -3517,7 +3443,7 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS v % 5 <> 0, B AS v % 5 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev78'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
-------------------
PATTERN (a+ b)
@@ -3546,9 +3472,8 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=30.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- last_value with pattern
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev79 AS
SELECT last_value(v) OVER w
FROM generate_series(1, 30) AS s(v)
WINDOW w AS (
@@ -3557,7 +3482,7 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS v % 5 <> 0, B AS v % 5 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev79'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
-------------------
PATTERN (a+ b)
@@ -3586,9 +3511,8 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=30.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- Multiple window functions
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev80 AS
SELECT
count(*) OVER w,
first_value(v) OVER w,
@@ -3600,7 +3524,7 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS v % 5 <> 0, B AS v % 5 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev80'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
-------------------
PATTERN (a+ b)
@@ -3632,12 +3556,11 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=30.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- ============================================================
-- DEFINE Expression Variations
-- ============================================================
-- Complex boolean expressions
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev81 AS
SELECT count(*) OVER w
FROM generate_series(1, 50) AS s(v)
WINDOW w AS (
@@ -3648,7 +3571,7 @@ WINDOW w AS (
A AS (v % 5 <> 0) AND (v % 3 <> 0),
B AS (v % 5 = 0) OR (v % 3 = 0)
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev81'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
-------------------
PATTERN (a+ b)
@@ -3679,9 +3602,8 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=50.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- Using PREV function
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev82 AS
SELECT count(*) OVER w
FROM generate_series(1, 30) AS s(v)
WINDOW w AS (
@@ -3693,7 +3615,7 @@ WINDOW w AS (
U AS v > PREV(v),
D AS v < PREV(v)
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev82'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
----------------------
PATTERN (s u+ d+)
@@ -3724,9 +3646,8 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=30.00 loops=1)
(8 rows)
-DROP VIEW rpr_v;
-- Using NULL comparisons
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev83 AS
SELECT count(*) OVER w
FROM (
SELECT CASE WHEN v % 5 = 0 THEN NULL ELSE v END AS v
@@ -3738,7 +3659,7 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS v IS NOT NULL, B AS v IS NULL
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev83'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
-------------------
PATTERN (a+ b)
@@ -3770,12 +3691,11 @@ WINDOW w AS (
-> Function Scan on generate_series v (actual rows=30.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- ============================================================
-- Large Scale Statistics Verification
-- ============================================================
-- 500 rows - verify statistics scale correctly
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev84 AS
SELECT count(*) OVER w
FROM generate_series(1, 500) AS s(v)
WINDOW w AS (
@@ -3784,7 +3704,7 @@ WINDOW w AS (
PATTERN (A+ B C)
DEFINE A AS v % 10 < 7, B AS v % 10 = 7, C AS v % 10 = 8
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev84'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
---------------------
PATTERN (a+ b c)
@@ -3813,9 +3733,8 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=500.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- High match count scenario
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev85 AS
SELECT count(*) OVER w
FROM generate_series(1, 500) AS s(v)
WINDOW w AS (
@@ -3824,7 +3743,7 @@ WINDOW w AS (
PATTERN (A B)
DEFINE A AS v % 2 = 1, B AS v % 2 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev85'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
------------------
PATTERN (a b)
@@ -3853,9 +3772,8 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=500.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
-- High skip count scenario
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev86 AS
SELECT count(*) OVER w
FROM generate_series(1, 500) AS s(v)
WINDOW w AS (
@@ -3869,7 +3787,7 @@ WINDOW w AS (
D AS v % 100 = 4,
E AS v % 100 = 5
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev86'), E'\n')) AS line WHERE line ~ 'PATTERN';
line
------------------------
PATTERN (a b c d e)
@@ -3903,7 +3821,3 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=500.00 loops=1)
(9 rows)
-DROP VIEW rpr_v;
--- Cleanup
-DROP TABLE nfa_test;
-DROP TABLE nfa_complex;
diff --git a/src/test/regress/sql/rpr_base.sql b/src/test/regress/sql/rpr_base.sql
index da28de1a8f9..453860c1499 100644
--- a/src/test/regress/sql/rpr_base.sql
+++ b/src/test/regress/sql/rpr_base.sql
@@ -12,7 +12,7 @@
-- Quantifiers Tests
-- Navigation Functions Tests
-- SKIP TO / INITIAL Tests
--- Serialization/Deserialization Tests
+-- Serialization/Deserialization Tests (objects kept for pg_upgrade/pg_dump)
-- Error Cases Tests
--
-- Planner Layer:
@@ -1408,8 +1408,6 @@ SELECT * FROM rpr_serial_v1 ORDER BY id;
-- Verify deparsing
SELECT pg_get_viewdef('rpr_serial_v1'::regclass);
-DROP VIEW rpr_serial_v1;
-
-- Complex pattern with alternation
CREATE VIEW rpr_serial_v2 AS
SELECT id, val, COUNT(*) OVER w as cnt
@@ -1424,8 +1422,6 @@ WINDOW w AS (
SELECT * FROM rpr_serial_v2 ORDER BY id;
SELECT pg_get_viewdef('rpr_serial_v2'::regclass);
-DROP VIEW rpr_serial_v2;
-
-- Pattern with grouping and quantifiers
CREATE VIEW rpr_serial_v3 AS
SELECT id, val, COUNT(*) OVER w as cnt
@@ -1443,8 +1439,6 @@ WINDOW w AS (
SELECT * FROM rpr_serial_v3 ORDER BY id;
SELECT pg_get_viewdef('rpr_serial_v3'::regclass);
-DROP VIEW rpr_serial_v3;
-
-- All features combined
CREATE VIEW rpr_serial_v4 AS
SELECT id, val, COUNT(*) OVER w as cnt
@@ -1465,8 +1459,6 @@ WINDOW w AS (
SELECT * FROM rpr_serial_v4 ORDER BY id;
SELECT pg_get_viewdef('rpr_serial_v4'::regclass);
-DROP VIEW rpr_serial_v4;
-
-- Additional quantifiers for deparsing coverage
-- ? quantifier (zero or one)
@@ -1483,8 +1475,6 @@ WINDOW w AS (
SELECT * FROM rpr_serial_v5 ORDER BY id;
SELECT pg_get_viewdef('rpr_serial_v5'::regclass);
-DROP VIEW rpr_serial_v5;
-
-- {n,} quantifier (n or more)
CREATE VIEW rpr_serial_v6 AS
SELECT id, val, COUNT(*) OVER w as cnt
@@ -1499,8 +1489,6 @@ WINDOW w AS (
SELECT * FROM rpr_serial_v6 ORDER BY id;
SELECT pg_get_viewdef('rpr_serial_v6'::regclass);
-DROP VIEW rpr_serial_v6;
-
-- {n} quantifier (exactly n)
CREATE VIEW rpr_serial_v7 AS
SELECT id, val, COUNT(*) OVER w as cnt
@@ -1515,8 +1503,6 @@ WINDOW w AS (
SELECT * FROM rpr_serial_v7 ORDER BY id;
SELECT pg_get_viewdef('rpr_serial_v7'::regclass);
-DROP VIEW rpr_serial_v7;
-
-- Nested ALT pattern (tests deparse of complex nested structure)
CREATE VIEW rpr_serial_v8 AS
SELECT id, val, COUNT(*) OVER w as cnt
@@ -1531,8 +1517,6 @@ WINDOW w AS (
SELECT * FROM rpr_serial_v8 ORDER BY id;
SELECT pg_get_viewdef('rpr_serial_v8'::regclass);
-DROP VIEW rpr_serial_v8;
-
-- Reluctant {1}? quantifier deparse through ruleutils
CREATE VIEW rpr_quant_reluctant_v AS
SELECT id, val, count(*) OVER w
@@ -1543,7 +1527,6 @@ WINDOW w AS (ORDER BY id
PATTERN (A{1}? B)
DEFINE A AS val > 0, B AS val > 0);
SELECT pg_get_viewdef('rpr_quant_reluctant_v'::regclass);
-DROP VIEW rpr_quant_reluctant_v;
-- Materialized view (if supported)
@@ -1567,9 +1550,6 @@ SELECT pg_get_viewdef('rpr_mview_v1'::regclass);
REFRESH MATERIALIZED VIEW rpr_mview_v1;
SELECT * FROM rpr_mview_v1 ORDER BY id;
-DROP MATERIALIZED VIEW rpr_mview_v1;
-DROP TABLE rpr_mview;
-
-- CREATE TABLE AS SELECT with RPR
CREATE TABLE rpr_ctas (id INT, val INT);
INSERT INTO rpr_ctas VALUES (1, 10), (2, 20), (3, 15), (4, 25);
@@ -1863,9 +1843,6 @@ WINDOW
SELECT * FROM rpr_multiwin_v ORDER BY id;
SELECT pg_get_viewdef('rpr_multiwin_v'::regclass);
-DROP VIEW rpr_multiwin_v;
-DROP TABLE rpr_multiwin;
-
-- {n} quantifier display in view
CREATE VIEW rpr_quant_n_v AS
SELECT id, val, count(*) OVER w
@@ -1876,7 +1853,6 @@ WINDOW w AS (ORDER BY id
PATTERN (A{3})
DEFINE A AS val > 0);
SELECT pg_get_viewdef('rpr_quant_n_v'::regclass);
-DROP VIEW rpr_quant_n_v;
-- {n,} quantifier display in view
CREATE VIEW rpr_quant_n_plus_v AS
@@ -1888,9 +1864,6 @@ WINDOW w AS (ORDER BY id
PATTERN (A{2,})
DEFINE A AS val > 0);
SELECT pg_get_viewdef('rpr_quant_n_plus_v'::regclass);
-DROP VIEW rpr_quant_n_plus_v;
-
-DROP TABLE rpr_serial;
-- ============================================================
-- Error Cases Tests
diff --git a/src/test/regress/sql/rpr_explain.sql b/src/test/regress/sql/rpr_explain.sql
index 4bb49650bb7..8e22382a68e 100644
--- a/src/test/regress/sql/rpr_explain.sql
+++ b/src/test/regress/sql/rpr_explain.sql
@@ -3,6 +3,9 @@
-- Tests for Row Pattern Recognition EXPLAIN output
-- ============================================================
--
+-- Views and tables in this file are intentionally not dropped,
+-- so that pg_upgrade/pg_dump can test RPR syntax serialization.
+--
-- This test suite validates EXPLAIN output for RPR queries,
-- including NFA statistics shown in EXPLAIN ANALYZE:
-- - NFA States: peak, total, merged
@@ -76,14 +79,14 @@ end;
$$;
-- Setup: Create test tables
-CREATE TEMP TABLE nfa_test (
+CREATE TABLE rpr_nfa_test (
id serial,
v int,
cat char(1)
);
-- Insert test data: 100 rows with predictable pattern
-INSERT INTO nfa_test (v, cat)
+INSERT INTO rpr_nfa_test (v, cat)
SELECT i,
CASE
WHEN i % 5 = 1 THEN 'A'
@@ -95,13 +98,13 @@ SELECT i,
FROM generate_series(1, 100) i;
-- Additional test table with more complex patterns
-CREATE TEMP TABLE nfa_complex (
+CREATE TABLE rpr_nfa_complex (
id serial,
price int,
trend char(1) -- U=up, D=down, S=stable
);
-INSERT INTO nfa_complex (price, trend)
+INSERT INTO rpr_nfa_complex (price, trend)
VALUES
(100, 'S'), (105, 'U'), (110, 'U'), (108, 'D'), (112, 'U'),
(115, 'U'), (113, 'D'), (111, 'D'), (109, 'D'), (110, 'U'),
@@ -115,77 +118,74 @@ VALUES
-- ============================================================
-- Simple pattern - should show basic statistics
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev01 AS
SELECT count(*) OVER w
-FROM nfa_test
+FROM rpr_nfa_test
WINDOW w AS (
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
AFTER MATCH SKIP PAST LAST ROW
PATTERN (A B)
DEFINE A AS cat = 'A', B AS cat = 'B'
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev01'), 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 nfa_test
+FROM rpr_nfa_test
WINDOW w AS (
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
AFTER MATCH SKIP PAST LAST ROW
PATTERN (A B)
DEFINE A AS cat = ''A'', B AS cat = ''B''
)');
-DROP VIEW rpr_v;
-- Pattern with no matches - 0 matched
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev02 AS
SELECT count(*) OVER w
-FROM nfa_test
+FROM rpr_nfa_test
WINDOW w AS (
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
AFTER MATCH SKIP PAST LAST ROW
PATTERN (X Y Z)
DEFINE X AS cat = 'X', Y AS cat = 'Y', Z AS cat = 'Z'
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev02'), 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 nfa_test
+FROM rpr_nfa_test
WINDOW w AS (
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
AFTER MATCH SKIP PAST LAST ROW
PATTERN (X Y Z)
DEFINE X AS cat = ''X'', Y AS cat = ''Y'', Z AS cat = ''Z''
);');
-DROP VIEW rpr_v;
-- Pattern matching every row - high match count
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev03 AS
SELECT count(*) OVER w
-FROM nfa_test
+FROM rpr_nfa_test
WINDOW w AS (
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
AFTER MATCH SKIP PAST LAST ROW
PATTERN (R)
DEFINE R AS TRUE
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev03'), 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 nfa_test
+FROM rpr_nfa_test
WINDOW w AS (
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
AFTER MATCH SKIP PAST LAST ROW
PATTERN (R)
DEFINE R AS TRUE
);');
-DROP VIEW rpr_v;
-- Regression test: Space before parenthesis in pattern deparse
-- Verifies that "A (B | C)" correctly outputs as "a (b | c)" with space
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev04 AS
SELECT count(*) OVER w
FROM generate_series(1, 20) AS s(v)
WINDOW w AS (
@@ -193,7 +193,7 @@ WINDOW w AS (
PATTERN (A (B | C))
DEFINE A AS v % 3 = 1, B AS v % 3 = 2, C AS v % 3 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev04'), 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
@@ -203,12 +203,11 @@ WINDOW w AS (
PATTERN (A (B | C))
DEFINE A AS v % 3 = 1, B AS v % 3 = 2, C AS v % 3 = 0
);');
-DROP VIEW rpr_v;
-- Regression test: Sequential alternations at same depth
-- Verifies that "((B | C) (D | E))" correctly outputs as "(b | c) (d | e)"
-- Previously failed due to missing parentheses on ALT depth decrease
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev05 AS
SELECT count(*) OVER w
FROM generate_series(1, 30) AS s(v)
WINDOW w AS (
@@ -216,7 +215,7 @@ WINDOW w AS (
PATTERN (A ((B | C) (D | E))*)
DEFINE A AS v % 5 = 1, B AS v % 5 = 2, C AS v % 5 = 3, D AS v % 5 = 4, E AS v % 5 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev05'), 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
@@ -226,14 +225,13 @@ WINDOW w AS (
PATTERN (A ((B | C) (D | E))*)
DEFINE A AS v % 5 = 1, B AS v % 5 = 2, C AS v % 5 = 3, D AS v % 5 = 4, E AS v % 5 = 0
);');
-DROP VIEW rpr_v;
-- ============================================================
-- State Statistics Tests (peak, total, merged)
-- ============================================================
-- Simple quantifier pattern - A+ with short matches (no merging)
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev06 AS
SELECT count(*) OVER w
FROM generate_series(1, 50) AS s(v)
WINDOW w AS (
@@ -242,7 +240,7 @@ WINDOW w AS (
PATTERN (A+)
DEFINE A AS v % 2 = 1
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev06'), 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
@@ -253,12 +251,11 @@ WINDOW w AS (
PATTERN (A+)
DEFINE A AS v % 2 = 1
);');
-DROP VIEW rpr_v;
-- Alternation pattern - multiple state branches
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev07 AS
SELECT count(*) OVER w
-FROM nfa_test
+FROM rpr_nfa_test
WINDOW w AS (
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
AFTER MATCH SKIP PAST LAST ROW
@@ -267,11 +264,11 @@ WINDOW w AS (
A AS cat = 'A', B AS cat = 'B', C AS cat = 'C',
D AS cat = 'D', E AS cat = 'E'
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev07'), 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 nfa_test
+FROM rpr_nfa_test
WINDOW w AS (
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
AFTER MATCH SKIP PAST LAST ROW
@@ -280,10 +277,9 @@ WINDOW w AS (
A AS cat = ''A'', B AS cat = ''B'', C AS cat = ''C'',
D AS cat = ''D'', E AS cat = ''E''
);');
-DROP VIEW rpr_v;
-- Complex pattern with high state count
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev08 AS
SELECT count(*) OVER w
FROM generate_series(1, 100) AS s(v)
WINDOW w AS (
@@ -295,7 +291,7 @@ WINDOW w AS (
B AS v % 3 = 2,
C AS v % 3 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev08'), 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
@@ -309,10 +305,9 @@ WINDOW w AS (
B AS v % 3 = 2,
C AS v % 3 = 0
);');
-DROP VIEW rpr_v;
-- Grouped pattern with quantifier - state count with grouping
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev09 AS
SELECT count(*) OVER w
FROM generate_series(1, 60) AS s(v)
WINDOW w AS (
@@ -321,7 +316,7 @@ WINDOW w AS (
PATTERN ((A B)+)
DEFINE A AS v % 2 = 1, B AS v % 2 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev09'), 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
@@ -332,11 +327,10 @@ WINDOW w AS (
PATTERN ((A B)+)
DEFINE A AS v % 2 = 1, B AS v % 2 = 0
);');
-DROP VIEW rpr_v;
-- State explosion pattern - many alternations
-- Pattern (A|B)(A|B)(A|B)(A|B) can create many parallel states
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev10 AS
SELECT count(*) OVER w
FROM generate_series(1, 100) AS s(v)
WINDOW w AS (
@@ -345,7 +339,7 @@ WINDOW w AS (
PATTERN ((A | B) (A | B) (A | B) (A | B) (A | B) (A | B) (A | B) (A | B))
DEFINE A AS v % 2 = 1, B AS v % 2 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev10'), 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
@@ -356,11 +350,10 @@ WINDOW w AS (
PATTERN ((A | B) (A | B) (A | B) (A | B) (A | B) (A | B) (A | B) (A | B))
DEFINE A AS v % 2 = 1, B AS v % 2 = 0
);');
-DROP VIEW rpr_v;
-- Consecutive ALT merge followed by different ALT
-- Tests mergeConsecutiveAlts flush on ALT change: (A|B){2} (C|D)
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev11 AS
SELECT count(*) OVER w
FROM generate_series(1, 40) AS s(v)
WINDOW w AS (
@@ -369,7 +362,7 @@ WINDOW w AS (
PATTERN ((A | B) (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_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev11'), 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
@@ -380,11 +373,10 @@ WINDOW w AS (
PATTERN ((A | B) (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
);');
-DROP VIEW rpr_v;
-- Consecutive ALT merge followed by non-ALT element
-- Tests mergeConsecutiveAlts flush on non-ALT: (A|B){2} c
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev12 AS
SELECT count(*) OVER w
FROM generate_series(1, 40) AS s(v)
WINDOW w AS (
@@ -393,7 +385,7 @@ WINDOW w AS (
PATTERN ((A | B) (A | B) C)
DEFINE A AS v % 3 = 0, B AS v % 3 = 1, C AS v % 3 = 2
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev12'), 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
@@ -404,10 +396,9 @@ WINDOW w AS (
PATTERN ((A | B) (A | B) C)
DEFINE A AS v % 3 = 0, B AS v % 3 = 1, C AS v % 3 = 2
);');
-DROP VIEW rpr_v;
-- ALT prefix/suffix absorbed into GROUP: (A|B) (A|B)+ (A|B) -> (A|B){3,}
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev13 AS
SELECT count(*) OVER w
FROM generate_series(1, 40) AS s(v)
WINDOW w AS (
@@ -416,7 +407,7 @@ WINDOW w AS (
PATTERN ((A | B) (A | B)+ (A | B))
DEFINE A AS v % 2 = 0, B AS v % 2 = 1
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev13'), 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
@@ -427,10 +418,9 @@ WINDOW w AS (
PATTERN ((A | B) (A | B)+ (A | B))
DEFINE A AS v % 2 = 0, B AS v % 2 = 1
);');
-DROP VIEW rpr_v;
-- High state count - alternation with plus quantifier
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev14 AS
SELECT count(*) OVER w
FROM generate_series(1, 100) AS s(v)
WINDOW w AS (
@@ -439,7 +429,7 @@ WINDOW w AS (
PATTERN ((A | B | C)+ D)
DEFINE A AS v % 4 = 1, B AS v % 4 = 2, C AS v % 4 = 3, D AS v % 4 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev14'), 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
@@ -450,11 +440,10 @@ WINDOW w AS (
PATTERN ((A | B | C)+ D)
DEFINE A AS v % 4 = 1, B AS v % 4 = 2, C AS v % 4 = 3, D AS v % 4 = 0
);');
-DROP VIEW rpr_v;
-- Early termination: first ALT branch (A) reaches FIN immediately,
-- pruning second branch (A B+) before it can accumulate B repetitions.
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev15 AS
SELECT count(*) OVER w
FROM generate_series(1, 100) AS s(v)
WINDOW w AS (
@@ -463,7 +452,7 @@ WINDOW w AS (
PATTERN ((A | A B)+)
DEFINE A AS v = 1, B AS v > 1
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev15'), 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
@@ -474,10 +463,9 @@ WINDOW w AS (
PATTERN ((A | A B)+)
DEFINE A AS v = 1, B AS v > 1
);');
-DROP VIEW rpr_v;
-- Nested quantifiers causing state growth
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev16 AS
SELECT count(*) OVER w
FROM generate_series(1, 1000) AS s(v)
WINDOW w AS (
@@ -486,7 +474,7 @@ WINDOW w AS (
PATTERN (((A | B)+)+)
DEFINE A AS v % 3 = 1, B AS v % 3 = 2
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev16'), 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
@@ -497,14 +485,13 @@ WINDOW w AS (
PATTERN (((A | B)+)+)
DEFINE A AS v % 3 = 1, B AS v % 3 = 2
);');
-DROP VIEW rpr_v;
-- ============================================================
-- Context Statistics Tests (peak, total, pruned + absorbed/skipped)
-- ============================================================
-- Context absorption with unbounded quantifier at start
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev17 AS
SELECT count(*) OVER w
FROM generate_series(1, 50) AS s(v)
WINDOW w AS (
@@ -513,7 +500,7 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS v % 5 <> 0, B AS v % 5 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev17'), 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
@@ -524,10 +511,9 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS v % 5 <> 0, B AS v % 5 = 0
);');
-DROP VIEW rpr_v;
-- No absorption - bounded quantifier
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev18 AS
SELECT count(*) OVER w
FROM generate_series(1, 50) AS s(v)
WINDOW w AS (
@@ -536,7 +522,7 @@ WINDOW w AS (
PATTERN (A{2,4} B)
DEFINE A AS v % 5 <> 0, B AS v % 5 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev18'), 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
@@ -547,10 +533,9 @@ WINDOW w AS (
PATTERN (A{2,4} B)
DEFINE A AS v % 5 <> 0, B AS v % 5 = 0
);');
-DROP VIEW rpr_v;
-- Contexts skipped by SKIP PAST LAST ROW
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev19 AS
SELECT count(*) OVER w
FROM generate_series(1, 100) AS s(v)
WINDOW w AS (
@@ -559,7 +544,7 @@ WINDOW w AS (
PATTERN (A B C)
DEFINE A AS v % 10 = 1, B AS v % 10 = 2, C AS v % 10 = 3
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev19'), 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
@@ -570,10 +555,9 @@ WINDOW w AS (
PATTERN (A B C)
DEFINE A AS v % 10 = 1, B AS v % 10 = 2, C AS v % 10 = 3
);');
-DROP VIEW rpr_v;
-- High context absorption - unbounded group
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev20 AS
SELECT count(*) OVER w
FROM generate_series(1, 100) AS s(v)
WINDOW w AS (
@@ -582,7 +566,7 @@ WINDOW w AS (
PATTERN ((A B)+ C)
DEFINE A AS v % 3 = 1, B AS v % 3 = 2, C AS v % 3 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev20'), 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
@@ -593,16 +577,15 @@ WINDOW w AS (
PATTERN ((A B)+ C)
DEFINE A AS v % 3 = 1, B AS v % 3 = 2, C AS v % 3 = 0
);');
-DROP VIEW rpr_v;
-- ============================================================
-- Match Length Statistics Tests
-- ============================================================
-- Fixed length matches - all same length
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev21 AS
SELECT count(*) OVER w
-FROM nfa_test
+FROM rpr_nfa_test
WINDOW w AS (
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
AFTER MATCH SKIP PAST LAST ROW
@@ -611,11 +594,11 @@ WINDOW w AS (
A AS cat = 'A', B AS cat = 'B', C AS cat = 'C',
D AS cat = 'D', E AS cat = 'E'
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev21'), 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 nfa_test
+FROM rpr_nfa_test
WINDOW w AS (
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
AFTER MATCH SKIP PAST LAST ROW
@@ -624,10 +607,9 @@ WINDOW w AS (
A AS cat = ''A'', B AS cat = ''B'', C AS cat = ''C'',
D AS cat = ''D'', E AS cat = ''E''
);');
-DROP VIEW rpr_v;
-- Variable length matches - min/max/avg differ
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev22 AS
SELECT count(*) OVER w
FROM generate_series(1, 100) AS s(v)
WINDOW w AS (
@@ -636,7 +618,7 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS v % 10 <> 0, B AS v % 10 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev22'), 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
@@ -647,10 +629,9 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS v % 10 <> 0, B AS v % 10 = 0
);');
-DROP VIEW rpr_v;
-- Very long matches
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev23 AS
SELECT count(*) OVER w
FROM generate_series(1, 200) AS s(v)
WINDOW w AS (
@@ -659,7 +640,7 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS v <= 195, B AS v > 195
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev23'), 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
@@ -670,10 +651,9 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS v <= 195, B AS v > 195
);');
-DROP VIEW rpr_v;
-- Uniform match length with mismatches from gap rows (v%20 = 11..15)
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev24 AS
SELECT count(*) OVER w
FROM generate_series(1, 100) AS s(v)
WINDOW w AS (
@@ -684,7 +664,7 @@ WINDOW w AS (
A AS (v % 20 <> 0) AND (v % 20 <= 10 OR v % 20 > 15),
B AS v % 20 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev24'), 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
@@ -697,7 +677,6 @@ WINDOW w AS (
A AS (v % 20 <> 0) AND (v % 20 <= 10 OR v % 20 > 15),
B AS v % 20 = 0
);');
-DROP VIEW rpr_v;
-- ============================================================
-- Mismatch Length Statistics Tests
@@ -705,7 +684,7 @@ DROP VIEW rpr_v;
-- Pattern with complete match every cycle: 0 mismatched
-- A(1,2,3) B(4,5) C(6) repeats perfectly; X rows are pruned, not mismatched
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev25 AS
SELECT count(*) OVER w
FROM (
SELECT v,
@@ -721,7 +700,7 @@ WINDOW w AS (
PATTERN (A+ B+ C)
DEFINE A AS cat = 'A', B AS cat = 'B', C AS cat = 'C'
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev25'), 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
@@ -739,10 +718,9 @@ WINDOW w AS (
PATTERN (A+ B+ C)
DEFINE A AS cat = ''A'', B AS cat = ''B'', C AS cat = ''C''
);');
-DROP VIEW rpr_v;
-- Long partial matches that fail
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev26 AS
SELECT count(*) OVER w
FROM (
SELECT i AS v,
@@ -763,7 +741,7 @@ WINDOW w AS (
PATTERN (A+ B+ C)
DEFINE A AS cat = 'A', B AS cat = 'B', C AS cat = 'C'
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev26'), 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
@@ -786,14 +764,13 @@ WINDOW w AS (
PATTERN (A+ B+ C)
DEFINE A AS cat = ''A'', B AS cat = ''B'', C AS cat = ''C''
);');
-DROP VIEW rpr_v;
-- ============================================================
-- JSON Format Tests
-- ============================================================
-- JSON format output with all statistics
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev27 AS
SELECT count(*) OVER w
FROM generate_series(1, 50) AS s(v)
WINDOW w AS (
@@ -802,7 +779,7 @@ WINDOW w AS (
PATTERN (A+ B+)
DEFINE A AS v % 3 = 1, B AS v % 3 = 2
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev27'), E'\n')) AS line WHERE line ~ 'PATTERN';
SELECT rpr_explain_filter('
EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF, FORMAT JSON)
SELECT count(*) OVER w
@@ -813,10 +790,9 @@ WINDOW w AS (
PATTERN (A+ B+)
DEFINE A AS v % 3 = 1, B AS v % 3 = 2
)');
-DROP VIEW rpr_v;
-- JSON format with match length statistics
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev28 AS
SELECT count(*) OVER w
FROM generate_series(1, 100) AS s(v)
WINDOW w AS (
@@ -825,7 +801,7 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS v % 10 <> 0, B AS v % 10 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev28'), E'\n')) AS line WHERE line ~ 'PATTERN';
SELECT rpr_explain_filter('
EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF, FORMAT JSON)
SELECT count(*) OVER w
@@ -836,11 +812,10 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS v % 10 <> 0, B AS v % 10 = 0
)');
-DROP VIEW rpr_v;
-- JSON format with mismatch statistics
-- Pattern A B C expects 1,2,3 but gets 1,2,4 twice causing mismatches
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev29 AS
SELECT count(*) OVER w
FROM (VALUES (1),(2),(4), (1),(2),(4), (1),(2),(3)) AS t(v)
WINDOW w AS (
@@ -849,7 +824,7 @@ WINDOW w AS (
PATTERN (A B C)
DEFINE A AS v = 1, B AS v = 2, C AS v = 3
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev29'), E'\n')) AS line WHERE line ~ 'PATTERN';
SELECT rpr_explain_filter('
EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF, FORMAT JSON)
SELECT count(*) OVER w
@@ -860,11 +835,10 @@ WINDOW w AS (
PATTERN (A B C)
DEFINE A AS v = 1, B AS v = 2, C AS v = 3
)');
-DROP VIEW rpr_v;
-- JSON format with skipped context statistics
-- Alternation pattern with SKIP PAST LAST ROW causes many contexts to be skipped
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev30 AS
SELECT count(*) OVER w
FROM generate_series(1, 100) AS s(v)
WINDOW w AS (
@@ -873,7 +847,7 @@ WINDOW w AS (
PATTERN ((A | B) (A | B) (A | B) (A | B) (A | B) (A | B) (A | B) (A | B))
DEFINE A AS v % 2 = 1, B AS v % 2 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev30'), E'\n')) AS line WHERE line ~ 'PATTERN';
SELECT rpr_explain_filter('
EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF, FORMAT JSON)
SELECT count(*) OVER w
@@ -884,14 +858,13 @@ WINDOW w AS (
PATTERN ((A | B) (A | B) (A | B) (A | B) (A | B) (A | B) (A | B) (A | B))
DEFINE A AS v % 2 = 1, B AS v % 2 = 0
)');
-DROP VIEW rpr_v;
-- ============================================================
-- XML Format Tests
-- ============================================================
-- XML format output
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev31 AS
SELECT count(*) OVER w
FROM generate_series(1, 30) AS s(v)
WINDOW w AS (
@@ -900,7 +873,7 @@ WINDOW w AS (
PATTERN (A B)
DEFINE A AS v % 2 = 1, B AS v % 2 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev31'), E'\n')) AS line WHERE line ~ 'PATTERN';
SELECT rpr_explain_filter('
EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF, FORMAT XML)
SELECT count(*) OVER w
@@ -911,14 +884,13 @@ WINDOW w AS (
PATTERN (A B)
DEFINE A AS v % 2 = 1, B AS v % 2 = 0
)');
-DROP VIEW rpr_v;
-- ============================================================
-- Multiple Partitions Tests
-- ============================================================
-- Statistics across multiple partitions
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev32 AS
SELECT count(*) OVER w
FROM (
SELECT p, v
@@ -932,7 +904,7 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS v % 5 <> 0, B AS v % 5 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev32'), 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
@@ -948,10 +920,9 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS v % 5 <> 0, B AS v % 5 = 0
);');
-DROP VIEW rpr_v;
-- Different pattern behavior per partition
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev33 AS
SELECT count(*) OVER w
FROM (
SELECT
@@ -966,7 +937,7 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS val < 5, B AS val >= 5
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev33'), 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
@@ -983,14 +954,13 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS val < 5, B AS val >= 5
);');
-DROP VIEW rpr_v;
-- ============================================================
-- Edge Cases
-- ============================================================
-- Empty result set
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev34 AS
SELECT count(*) OVER w
FROM generate_series(1, 0) AS s(v)
WINDOW w AS (
@@ -999,7 +969,7 @@ WINDOW w AS (
PATTERN (A B)
DEFINE A AS v = 1, B AS v = 2
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev34'), 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
@@ -1010,10 +980,9 @@ WINDOW w AS (
PATTERN (A B)
DEFINE A AS v = 1, B AS v = 2
);');
-DROP VIEW rpr_v;
-- Single row
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev35 AS
SELECT count(*) OVER w
FROM generate_series(1, 1) AS s(v)
WINDOW w AS (
@@ -1022,7 +991,7 @@ WINDOW w AS (
PATTERN (A)
DEFINE A AS TRUE
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev35'), 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
@@ -1033,10 +1002,9 @@ WINDOW w AS (
PATTERN (A)
DEFINE A AS TRUE
);');
-DROP VIEW rpr_v;
-- Pattern longer than data
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev36 AS
SELECT count(*) OVER w
FROM generate_series(1, 5) AS s(v)
WINDOW w AS (
@@ -1047,7 +1015,7 @@ WINDOW w AS (
A AS v = 1, B AS v = 2, C AS v = 3, D AS v = 4, E AS v = 5,
F AS v = 6, G AS v = 7, H AS v = 8, I AS v = 9, J AS v = 10
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev36'), 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
@@ -1060,10 +1028,9 @@ WINDOW w AS (
A AS v = 1, B AS v = 2, C AS v = 3, D AS v = 4, E AS v = 5,
F AS v = 6, G AS v = 7, H AS v = 8, I AS v = 9, J AS v = 10
);');
-DROP VIEW rpr_v;
-- All rows match as single match
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev37 AS
SELECT count(*) OVER w
FROM generate_series(1, 50) AS s(v)
WINDOW w AS (
@@ -1072,7 +1039,7 @@ WINDOW w AS (
PATTERN (A+)
DEFINE A AS TRUE
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev37'), 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
@@ -1083,14 +1050,13 @@ WINDOW w AS (
PATTERN (A+)
DEFINE A AS TRUE
);');
-DROP VIEW rpr_v;
-- ============================================================
-- Complex Pattern Tests
-- ============================================================
-- Nested groups
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev38 AS
SELECT count(*) OVER w
FROM generate_series(1, 60) AS s(v)
WINDOW w AS (
@@ -1099,7 +1065,7 @@ WINDOW w AS (
PATTERN (((A B) C)+)
DEFINE A AS v % 3 = 1, B AS v % 3 = 2, C AS v % 3 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev38'), 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
@@ -1110,12 +1076,11 @@ WINDOW w AS (
PATTERN (((A B) C)+)
DEFINE A AS v % 3 = 1, B AS v % 3 = 2, C AS v % 3 = 0
);');
-DROP VIEW rpr_v;
-- Multiple alternations
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev39 AS
SELECT count(*) OVER w
-FROM nfa_test
+FROM rpr_nfa_test
WINDOW w AS (
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
AFTER MATCH SKIP PAST LAST ROW
@@ -1124,11 +1089,11 @@ WINDOW w AS (
A AS cat = 'A', B AS cat = 'B', C AS cat = 'C',
D AS cat = 'D', E AS cat = 'E'
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev39'), 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 nfa_test
+FROM rpr_nfa_test
WINDOW w AS (
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
AFTER MATCH SKIP PAST LAST ROW
@@ -1137,10 +1102,9 @@ WINDOW w AS (
A AS cat = ''A'', B AS cat = ''B'', C AS cat = ''C'',
D AS cat = ''D'', E AS cat = ''E''
);');
-DROP VIEW rpr_v;
-- Optional elements
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev40 AS
SELECT count(*) OVER w
FROM generate_series(1, 50) AS s(v)
WINDOW w AS (
@@ -1149,7 +1113,7 @@ WINDOW w AS (
PATTERN (A B? C)
DEFINE A AS v % 4 = 1, B AS v % 4 = 2, C AS v % 4 = 3
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev40'), 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
@@ -1160,10 +1124,9 @@ WINDOW w AS (
PATTERN (A B? C)
DEFINE A AS v % 4 = 1, B AS v % 4 = 2, C AS v % 4 = 3
);');
-DROP VIEW rpr_v;
-- Bounded quantifiers
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev41 AS
SELECT count(*) OVER w
FROM generate_series(1, 100) AS s(v)
WINDOW w AS (
@@ -1172,7 +1135,7 @@ WINDOW w AS (
PATTERN (A{2,5} B)
DEFINE A AS v % 10 <> 0, B AS v % 10 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev41'), 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
@@ -1183,10 +1146,9 @@ WINDOW w AS (
PATTERN (A{2,5} B)
DEFINE A AS v % 10 <> 0, B AS v % 10 = 0
);');
-DROP VIEW rpr_v;
-- Star quantifier
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev42 AS
SELECT count(*) OVER w
FROM generate_series(1, 50) AS s(v)
WINDOW w AS (
@@ -1195,7 +1157,7 @@ WINDOW w AS (
PATTERN (A B* C)
DEFINE A AS v % 10 = 1, B AS v % 10 IN (2,3,4,5,6,7,8), C AS v % 10 = 9
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev42'), 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
@@ -1206,60 +1168,57 @@ WINDOW w AS (
PATTERN (A B* C)
DEFINE A AS v % 10 = 1, B AS v % 10 IN (2,3,4,5,6,7,8), C AS v % 10 = 9
);');
-DROP VIEW rpr_v;
-- ============================================================
-- Real-world Pattern Examples
-- ============================================================
-- Stock price pattern - V-shape (down then up)
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev43 AS
SELECT count(*) OVER w
-FROM nfa_complex
+FROM rpr_nfa_complex
WINDOW w AS (
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
AFTER MATCH SKIP PAST LAST ROW
PATTERN (D+ U+)
DEFINE D AS trend = 'D', U AS trend = 'U'
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev43'), 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 nfa_complex
+FROM rpr_nfa_complex
WINDOW w AS (
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
AFTER MATCH SKIP PAST LAST ROW
PATTERN (D+ U+)
DEFINE D AS trend = ''D'', U AS trend = ''U''
);');
-DROP VIEW rpr_v;
-- Stock price pattern - peak (up, stable, down)
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev44 AS
SELECT count(*) OVER w
-FROM nfa_complex
+FROM rpr_nfa_complex
WINDOW w AS (
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
AFTER MATCH SKIP PAST LAST ROW
PATTERN (U+ S* D+)
DEFINE U AS trend = 'U', S AS trend = 'S', D AS trend = 'D'
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev44'), 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 nfa_complex
+FROM rpr_nfa_complex
WINDOW w AS (
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
AFTER MATCH SKIP PAST LAST ROW
PATTERN (U+ S* D+)
DEFINE U AS trend = ''U'', S AS trend = ''S'', D AS trend = ''D''
);');
-DROP VIEW rpr_v;
-- Consecutive increasing values (using PREV)
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev45 AS
SELECT count(*) OVER w
FROM generate_series(1, 50) AS s(v)
WINDOW w AS (
@@ -1268,7 +1227,7 @@ WINDOW w AS (
PATTERN (A{3,})
DEFINE A AS v > PREV(v) OR PREV(v) IS NULL
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev45'), 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
@@ -1279,14 +1238,13 @@ WINDOW w AS (
PATTERN (A{3,})
DEFINE A AS v > PREV(v) OR PREV(v) IS NULL
);');
-DROP VIEW rpr_v;
-- ============================================================
-- Performance-oriented Tests
-- ============================================================
-- Large dataset with simple pattern
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev46 AS
SELECT count(*) OVER w
FROM generate_series(1, 1000) AS s(v)
WINDOW w AS (
@@ -1295,7 +1253,7 @@ WINDOW w AS (
PATTERN (A B)
DEFINE A AS v % 2 = 1, B AS v % 2 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev46'), 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
@@ -1306,10 +1264,9 @@ WINDOW w AS (
PATTERN (A B)
DEFINE A AS v % 2 = 1, B AS v % 2 = 0
);');
-DROP VIEW rpr_v;
-- Large dataset with absorption
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev47 AS
SELECT count(*) OVER w
FROM generate_series(1, 1000) AS s(v)
WINDOW w AS (
@@ -1318,7 +1275,7 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS v % 100 <> 0, B AS v % 100 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev47'), 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
@@ -1329,10 +1286,9 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS v % 100 <> 0, B AS v % 100 = 0
);');
-DROP VIEW rpr_v;
-- High state merge ratio
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev48 AS
SELECT count(*) OVER w
FROM generate_series(1, 500) AS s(v)
WINDOW w AS (
@@ -1341,7 +1297,7 @@ WINDOW w AS (
PATTERN ((A | B)+ C)
DEFINE A AS v % 3 = 1, B AS v % 3 = 2, C AS v % 3 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev48'), 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
@@ -1352,14 +1308,13 @@ WINDOW w AS (
PATTERN ((A | B)+ C)
DEFINE A AS v % 3 = 1, B AS v % 3 = 2, C AS v % 3 = 0
);');
-DROP VIEW rpr_v;
-- ============================================================
-- INITIAL vs no INITIAL comparison
-- ============================================================
-- With INITIAL keyword
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev49 AS
SELECT count(*) OVER w
FROM generate_series(1, 50) AS s(v)
WINDOW w AS (
@@ -1369,7 +1324,7 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS v % 5 <> 0, B AS v % 5 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev49'), 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
@@ -1381,10 +1336,9 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS v % 5 <> 0, B AS v % 5 = 0
);');
-DROP VIEW rpr_v;
-- Without INITIAL keyword (same behavior currently)
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev50 AS
SELECT count(*) OVER w
FROM generate_series(1, 50) AS s(v)
WINDOW w AS (
@@ -1393,7 +1347,7 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS v % 5 <> 0, B AS v % 5 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev50'), 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
@@ -1404,14 +1358,13 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS v % 5 <> 0, B AS v % 5 = 0
);');
-DROP VIEW rpr_v;
-- ============================================================
-- Quantifier Variations
-- ============================================================
-- Plus quantifier
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev51 AS
SELECT count(*) OVER w
FROM generate_series(1, 40) AS s(v)
WINDOW w AS (
@@ -1420,7 +1373,7 @@ WINDOW w AS (
PATTERN (A+)
DEFINE A AS v % 4 <> 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev51'), 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
@@ -1431,10 +1384,9 @@ WINDOW w AS (
PATTERN (A+)
DEFINE A AS v % 4 <> 0
);');
-DROP VIEW rpr_v;
-- Star quantifier (zero or more)
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev52 AS
SELECT count(*) OVER w
FROM generate_series(1, 40) AS s(v)
WINDOW w AS (
@@ -1443,7 +1395,7 @@ WINDOW w AS (
PATTERN (A* B)
DEFINE A AS v % 4 IN (1, 2), B AS v % 4 = 3
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev52'), 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
@@ -1454,10 +1406,9 @@ WINDOW w AS (
PATTERN (A* B)
DEFINE A AS v % 4 IN (1, 2), B AS v % 4 = 3
);');
-DROP VIEW rpr_v;
-- Question mark (zero or one)
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev53 AS
SELECT count(*) OVER w
FROM generate_series(1, 40) AS s(v)
WINDOW w AS (
@@ -1466,7 +1417,7 @@ WINDOW w AS (
PATTERN (A? B C)
DEFINE A AS v % 4 = 1, B AS v % 4 = 2, C AS v % 4 = 3
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev53'), 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
@@ -1477,10 +1428,9 @@ WINDOW w AS (
PATTERN (A? B C)
DEFINE A AS v % 4 = 1, B AS v % 4 = 2, C AS v % 4 = 3
);');
-DROP VIEW rpr_v;
-- Exact count {n}
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev54 AS
SELECT count(*) OVER w
FROM generate_series(1, 50) AS s(v)
WINDOW w AS (
@@ -1489,7 +1439,7 @@ WINDOW w AS (
PATTERN (A{3} B)
DEFINE A AS v % 5 <> 0, B AS v % 5 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev54'), 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
@@ -1500,10 +1450,9 @@ WINDOW w AS (
PATTERN (A{3} B)
DEFINE A AS v % 5 <> 0, B AS v % 5 = 0
);');
-DROP VIEW rpr_v;
-- Range {n,m}
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev55 AS
SELECT count(*) OVER w
FROM generate_series(1, 50) AS s(v)
WINDOW w AS (
@@ -1512,7 +1461,7 @@ WINDOW w AS (
PATTERN (A{2,4} B)
DEFINE A AS v % 5 <> 0, B AS v % 5 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev55'), 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
@@ -1523,10 +1472,9 @@ WINDOW w AS (
PATTERN (A{2,4} B)
DEFINE A AS v % 5 <> 0, B AS v % 5 = 0
);');
-DROP VIEW rpr_v;
-- At least {n,}
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev56 AS
SELECT count(*) OVER w
FROM generate_series(1, 50) AS s(v)
WINDOW w AS (
@@ -1535,7 +1483,7 @@ WINDOW w AS (
PATTERN (A{3,} B)
DEFINE A AS v % 10 <> 0, B AS v % 10 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev56'), 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
@@ -1546,7 +1494,6 @@ WINDOW w AS (
PATTERN (A{3,} B)
DEFINE A AS v % 10 <> 0, B AS v % 10 = 0
);');
-DROP VIEW rpr_v;
-- ============================================================
-- Regression Tests for Statistics Accuracy
@@ -1554,7 +1501,7 @@ DROP VIEW rpr_v;
-- Verify state count accuracy
-- Pattern A+ B with 20 rows should show predictable state behavior
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev57 AS
SELECT count(*) OVER w
FROM generate_series(1, 20) AS s(v)
WINDOW w AS (
@@ -1563,7 +1510,7 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS v % 5 <> 0, B AS v % 5 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev57'), 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
@@ -1574,10 +1521,9 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS v % 5 <> 0, B AS v % 5 = 0
);');
-DROP VIEW rpr_v;
-- Verify context count with known absorption
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev58 AS
SELECT count(*) OVER w
FROM generate_series(1, 30) AS s(v)
WINDOW w AS (
@@ -1586,7 +1532,7 @@ WINDOW w AS (
PATTERN (A+ B C)
DEFINE A AS v % 10 IN (1,2,3,4,5,6,7), B AS v % 10 = 8, C AS v % 10 = 9
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev58'), 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
@@ -1597,10 +1543,9 @@ WINDOW w AS (
PATTERN (A+ B C)
DEFINE A AS v % 10 IN (1,2,3,4,5,6,7), B AS v % 10 = 8, C AS v % 10 = 9
);');
-DROP VIEW rpr_v;
-- Verify match length with fixed-length pattern
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev59 AS
SELECT count(*) OVER w
FROM generate_series(1, 30) AS s(v)
WINDOW w AS (
@@ -1609,7 +1554,7 @@ WINDOW w AS (
PATTERN (A B C)
DEFINE A AS v % 3 = 1, B AS v % 3 = 2, C AS v % 3 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev59'), 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
@@ -1620,39 +1565,37 @@ WINDOW w AS (
PATTERN (A B C)
DEFINE A AS v % 3 = 1, B AS v % 3 = 2, C AS v % 3 = 0
);');
-DROP VIEW rpr_v;
-- ============================================================
-- Alternation Pattern Tests
-- ============================================================
-- Simple alternation
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev60 AS
SELECT count(*) OVER w
-FROM nfa_test
+FROM rpr_nfa_test
WINDOW w AS (
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
AFTER MATCH SKIP PAST LAST ROW
PATTERN ((A | B) C)
DEFINE A AS cat = 'A', B AS cat = 'B', C AS cat = 'C'
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev60'), 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 nfa_test
+FROM rpr_nfa_test
WINDOW w AS (
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
AFTER MATCH SKIP PAST LAST ROW
PATTERN ((A | B) C)
DEFINE A AS cat = ''A'', B AS cat = ''B'', C AS cat = ''C''
);');
-DROP VIEW rpr_v;
-- Multiple items in alternation
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev61 AS
SELECT count(*) OVER w
-FROM nfa_test
+FROM rpr_nfa_test
WINDOW w AS (
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
AFTER MATCH SKIP PAST LAST ROW
@@ -1661,11 +1604,11 @@ WINDOW w AS (
A AS cat = 'A', B AS cat = 'B', C AS cat = 'C',
D AS cat = 'D', E AS cat = 'E'
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev61'), 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 nfa_test
+FROM rpr_nfa_test
WINDOW w AS (
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
AFTER MATCH SKIP PAST LAST ROW
@@ -1674,10 +1617,9 @@ WINDOW w AS (
A AS cat = ''A'', B AS cat = ''B'', C AS cat = ''C'',
D AS cat = ''D'', E AS cat = ''E''
);');
-DROP VIEW rpr_v;
-- Alternation with quantifiers
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev62 AS
SELECT count(*) OVER w
FROM generate_series(1, 50) AS s(v)
WINDOW w AS (
@@ -1686,7 +1628,7 @@ WINDOW w AS (
PATTERN ((A | B)+ C)
DEFINE A AS v % 3 = 1, B AS v % 3 = 2, C AS v % 3 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev62'), 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
@@ -1697,10 +1639,9 @@ WINDOW w AS (
PATTERN ((A | B)+ C)
DEFINE A AS v % 3 = 1, B AS v % 3 = 2, C AS v % 3 = 0
);');
-DROP VIEW rpr_v;
-- Multiple alternatives (4+)
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev63 AS
SELECT count(*) OVER w
FROM generate_series(1, 100) AS s(v)
WINDOW w AS (
@@ -1708,7 +1649,7 @@ WINDOW w AS (
PATTERN (A | B | C | D | E)
DEFINE A AS v % 5 = 0, B AS v % 5 = 1, C AS v % 5 = 2, D AS v % 5 = 3, E AS v % 5 = 4
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev63'), 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
@@ -1718,10 +1659,9 @@ WINDOW w AS (
PATTERN (A | B | C | D | E)
DEFINE A AS v % 5 = 0, B AS v % 5 = 1, C AS v % 5 = 2, D AS v % 5 = 3, E AS v % 5 = 4
);');
-DROP VIEW rpr_v;
-- Alternation at start
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev64 AS
SELECT count(*) OVER w
FROM generate_series(1, 60) AS s(v)
WINDOW w AS (
@@ -1729,7 +1669,7 @@ WINDOW w AS (
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_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev64'), 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
@@ -1739,10 +1679,9 @@ WINDOW w AS (
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
);');
-DROP VIEW rpr_v;
-- Multiple sequential alternations
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev65 AS
SELECT count(*) OVER w
FROM generate_series(1, 100) AS s(v)
WINDOW w AS (
@@ -1750,7 +1689,7 @@ WINDOW w AS (
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_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev65'), 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
@@ -1760,10 +1699,9 @@ WINDOW w AS (
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
);');
-DROP VIEW rpr_v;
-- Quantified alternatives
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev66 AS
SELECT count(*) OVER w
FROM generate_series(1, 60) AS s(v)
WINDOW w AS (
@@ -1771,7 +1709,7 @@ WINDOW w AS (
PATTERN ((A+ | B+) C)
DEFINE A AS v % 3 = 0, B AS v % 3 = 1, C AS v % 3 = 2
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev66'), 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
@@ -1781,10 +1719,9 @@ WINDOW w AS (
PATTERN ((A+ | B+) C)
DEFINE A AS v % 3 = 0, B AS v % 3 = 1, C AS v % 3 = 2
);');
-DROP VIEW rpr_v;
-- Alternation at end
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev67 AS
SELECT count(*) OVER w
FROM generate_series(1, 60) AS s(v)
WINDOW w AS (
@@ -1792,7 +1729,7 @@ WINDOW w AS (
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_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev67'), 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
@@ -1802,11 +1739,10 @@ WINDOW w AS (
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
);');
-DROP VIEW rpr_v;
-- Nested ALT at start of branch inside outer ALT
-- Pattern: (A ((B | C) D | E)) - preceding VAR + inner ALT as first branch element
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev68 AS
SELECT count(*) OVER w
FROM generate_series(1, 20) AS s(v)
WINDOW w AS (
@@ -1814,7 +1750,7 @@ WINDOW w AS (
PATTERN (A ((B | C) D | E))
DEFINE A AS v % 5 = 0, B AS v % 5 = 1, C AS v % 5 = 2, D AS v % 5 = 3, E AS v % 5 = 4
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev68'), 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
@@ -1824,11 +1760,10 @@ WINDOW w AS (
PATTERN (A ((B | C) D | E))
DEFINE A AS v % 5 = 0, B AS v % 5 = 1, C AS v % 5 = 2, D AS v % 5 = 3, E AS v % 5 = 4
);');
-DROP VIEW rpr_v;
-- Nested ALT at end of branch inside outer ALT
-- Pattern: (C (A | B) | D) - inner ALT is last element in outer branch
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev69 AS
SELECT count(*) OVER w
FROM generate_series(1, 20) AS s(v)
WINDOW w AS (
@@ -1836,7 +1771,7 @@ WINDOW w AS (
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_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev69'), 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
@@ -1846,14 +1781,13 @@ WINDOW w AS (
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
);');
-DROP VIEW rpr_v;
-- ============================================================
-- Group Pattern Tests
-- ============================================================
-- Simple group
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev70 AS
SELECT count(*) OVER w
FROM generate_series(1, 40) AS s(v)
WINDOW w AS (
@@ -1862,7 +1796,7 @@ WINDOW w AS (
PATTERN ((A B)+)
DEFINE A AS v % 2 = 1, B AS v % 2 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev70'), 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
@@ -1873,10 +1807,9 @@ WINDOW w AS (
PATTERN ((A B)+)
DEFINE A AS v % 2 = 1, B AS v % 2 = 0
);');
-DROP VIEW rpr_v;
-- Group with bounded quantifier
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev71 AS
SELECT count(*) OVER w
FROM generate_series(1, 40) AS s(v)
WINDOW w AS (
@@ -1885,7 +1818,7 @@ WINDOW w AS (
PATTERN ((A B){2,4})
DEFINE A AS v % 2 = 1, B AS v % 2 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev71'), 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
@@ -1896,10 +1829,9 @@ WINDOW w AS (
PATTERN ((A B){2,4})
DEFINE A AS v % 2 = 1, B AS v % 2 = 0
);');
-DROP VIEW rpr_v;
-- Nested groups
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev72 AS
SELECT count(*) OVER w
FROM generate_series(1, 60) AS s(v)
WINDOW w AS (
@@ -1908,7 +1840,7 @@ WINDOW w AS (
PATTERN (((A B){2})+)
DEFINE A AS v % 2 = 1, B AS v % 2 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev72'), 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
@@ -1919,10 +1851,9 @@ WINDOW w AS (
PATTERN (((A B){2})+)
DEFINE A AS v % 2 = 1, B AS v % 2 = 0
);');
-DROP VIEW rpr_v;
-- Deep nesting (3+ levels)
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev73 AS
SELECT count(*) OVER w
FROM generate_series(1, 40) AS s(v)
WINDOW w AS (
@@ -1930,7 +1861,7 @@ WINDOW w AS (
PATTERN ((((A | B)+)+)+)
DEFINE A AS v % 2 = 0, B AS v % 2 = 1
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev73'), 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
@@ -1940,10 +1871,9 @@ WINDOW w AS (
PATTERN ((((A | B)+)+)+)
DEFINE A AS v % 2 = 0, B AS v % 2 = 1
);');
-DROP VIEW rpr_v;
-- Bounded quantifier on alternation
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev74 AS
SELECT count(*) OVER w
FROM generate_series(1, 60) AS s(v)
WINDOW w AS (
@@ -1951,7 +1881,7 @@ WINDOW w AS (
PATTERN ((A | B){2,3} C)
DEFINE A AS v % 3 = 0, B AS v % 3 = 1, C AS v % 3 = 2
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev74'), 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
@@ -1961,10 +1891,9 @@ WINDOW w AS (
PATTERN ((A | B){2,3} C)
DEFINE A AS v % 3 = 0, B AS v % 3 = 1, C AS v % 3 = 2
);');
-DROP VIEW rpr_v;
-- Nested groups with quantifiers
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev75 AS
SELECT count(*) OVER w
FROM generate_series(1, 60) AS s(v)
WINDOW w AS (
@@ -1972,7 +1901,7 @@ WINDOW w AS (
PATTERN (((A B)+ C)*)
DEFINE A AS v % 3 = 0, B AS v % 3 = 1, C AS v % 3 = 2
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev75'), 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
@@ -1982,10 +1911,9 @@ WINDOW w AS (
PATTERN (((A B)+ C)*)
DEFINE A AS v % 3 = 0, B AS v % 3 = 1, C AS v % 3 = 2
);');
-DROP VIEW rpr_v;
-- Partial nested quantification
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev76 AS
SELECT count(*) OVER w
FROM generate_series(1, 60) AS s(v)
WINDOW w AS (
@@ -1993,7 +1921,7 @@ WINDOW w AS (
PATTERN ((A (B C)+)*)
DEFINE A AS v % 3 = 0, B AS v % 3 = 1, C AS v % 3 = 2
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev76'), 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
@@ -2003,14 +1931,13 @@ WINDOW w AS (
PATTERN ((A (B C)+)*)
DEFINE A AS v % 3 = 0, B AS v % 3 = 1, C AS v % 3 = 2
);');
-DROP VIEW rpr_v;
-- ============================================================
-- Window Function Combinations
-- ============================================================
-- count(*) with pattern
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev77 AS
SELECT count(*) OVER w
FROM generate_series(1, 30) AS s(v)
WINDOW w AS (
@@ -2019,7 +1946,7 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS v % 5 <> 0, B AS v % 5 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev77'), 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
@@ -2030,10 +1957,9 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS v % 5 <> 0, B AS v % 5 = 0
);');
-DROP VIEW rpr_v;
-- first_value with pattern
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev78 AS
SELECT first_value(v) OVER w
FROM generate_series(1, 30) AS s(v)
WINDOW w AS (
@@ -2042,7 +1968,7 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS v % 5 <> 0, B AS v % 5 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev78'), E'\n')) AS line WHERE line ~ 'PATTERN';
SELECT rpr_explain_filter('
EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT first_value(v) OVER w
@@ -2053,10 +1979,9 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS v % 5 <> 0, B AS v % 5 = 0
);');
-DROP VIEW rpr_v;
-- last_value with pattern
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev79 AS
SELECT last_value(v) OVER w
FROM generate_series(1, 30) AS s(v)
WINDOW w AS (
@@ -2065,7 +1990,7 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS v % 5 <> 0, B AS v % 5 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev79'), E'\n')) AS line WHERE line ~ 'PATTERN';
SELECT rpr_explain_filter('
EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT last_value(v) OVER w
@@ -2076,10 +2001,9 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS v % 5 <> 0, B AS v % 5 = 0
);');
-DROP VIEW rpr_v;
-- Multiple window functions
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev80 AS
SELECT
count(*) OVER w,
first_value(v) OVER w,
@@ -2091,7 +2015,7 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS v % 5 <> 0, B AS v % 5 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev80'), E'\n')) AS line WHERE line ~ 'PATTERN';
SELECT rpr_explain_filter('
EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, TIMING OFF, SUMMARY OFF)
SELECT
@@ -2105,14 +2029,13 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS v % 5 <> 0, B AS v % 5 = 0
);');
-DROP VIEW rpr_v;
-- ============================================================
-- DEFINE Expression Variations
-- ============================================================
-- Complex boolean expressions
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev81 AS
SELECT count(*) OVER w
FROM generate_series(1, 50) AS s(v)
WINDOW w AS (
@@ -2123,7 +2046,7 @@ WINDOW w AS (
A AS (v % 5 <> 0) AND (v % 3 <> 0),
B AS (v % 5 = 0) OR (v % 3 = 0)
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev81'), 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
@@ -2136,10 +2059,9 @@ WINDOW w AS (
A AS (v % 5 <> 0) AND (v % 3 <> 0),
B AS (v % 5 = 0) OR (v % 3 = 0)
);');
-DROP VIEW rpr_v;
-- Using PREV function
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev82 AS
SELECT count(*) OVER w
FROM generate_series(1, 30) AS s(v)
WINDOW w AS (
@@ -2151,7 +2073,7 @@ WINDOW w AS (
U AS v > PREV(v),
D AS v < PREV(v)
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev82'), 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
@@ -2165,10 +2087,9 @@ WINDOW w AS (
U AS v > PREV(v),
D AS v < PREV(v)
);');
-DROP VIEW rpr_v;
-- Using NULL comparisons
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev83 AS
SELECT count(*) OVER w
FROM (
SELECT CASE WHEN v % 5 = 0 THEN NULL ELSE v END AS v
@@ -2180,7 +2101,7 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS v IS NOT NULL, B AS v IS NULL
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev83'), 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
@@ -2194,14 +2115,13 @@ WINDOW w AS (
PATTERN (A+ B)
DEFINE A AS v IS NOT NULL, B AS v IS NULL
);');
-DROP VIEW rpr_v;
-- ============================================================
-- Large Scale Statistics Verification
-- ============================================================
-- 500 rows - verify statistics scale correctly
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev84 AS
SELECT count(*) OVER w
FROM generate_series(1, 500) AS s(v)
WINDOW w AS (
@@ -2210,7 +2130,7 @@ WINDOW w AS (
PATTERN (A+ B C)
DEFINE A AS v % 10 < 7, B AS v % 10 = 7, C AS v % 10 = 8
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev84'), 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
@@ -2221,10 +2141,9 @@ WINDOW w AS (
PATTERN (A+ B C)
DEFINE A AS v % 10 < 7, B AS v % 10 = 7, C AS v % 10 = 8
);');
-DROP VIEW rpr_v;
-- High match count scenario
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev85 AS
SELECT count(*) OVER w
FROM generate_series(1, 500) AS s(v)
WINDOW w AS (
@@ -2233,7 +2152,7 @@ WINDOW w AS (
PATTERN (A B)
DEFINE A AS v % 2 = 1, B AS v % 2 = 0
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev85'), 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
@@ -2244,10 +2163,9 @@ WINDOW w AS (
PATTERN (A B)
DEFINE A AS v % 2 = 1, B AS v % 2 = 0
);');
-DROP VIEW rpr_v;
-- High skip count scenario
-CREATE TEMP VIEW rpr_v AS
+CREATE VIEW rpr_ev86 AS
SELECT count(*) OVER w
FROM generate_series(1, 500) AS s(v)
WINDOW w AS (
@@ -2261,7 +2179,7 @@ WINDOW w AS (
D AS v % 100 = 4,
E AS v % 100 = 5
);
-SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_v'), E'\n')) AS line WHERE line ~ 'PATTERN';
+SELECT line FROM unnest(string_to_array(pg_get_viewdef('rpr_ev86'), 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
@@ -2277,8 +2195,4 @@ WINDOW w AS (
D AS v % 100 = 4,
E AS v % 100 = 5
);');
-DROP VIEW rpr_v;
--- Cleanup
-DROP TABLE nfa_test;
-DROP TABLE nfa_complex;
--
2.50.1 (Apple Git-155)
From 81a9b83b9d61704e85d97bc79b10ac6a00d73def Mon Sep 17 00:00:00 2001
From: Henson Choi <[email protected]>
Date: Wed, 4 Mar 2026 15:53:33 +0900
Subject: [PATCH 5/8] Disable run condition pushdown for RPR windows
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 90275e25872..b67c35af39a 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -2453,6 +2453,17 @@ find_window_run_conditions(Query *subquery, AttrNumber attno,
wclause = (WindowClause *) list_nth(subquery->windowClause,
wfunc->winref - 1);
+ /*
+ * If a DEFINE clause exists, we cannot push down a run condition. In the
+ * case, a window partition (or frame) is divided into multiple reduced
+ * frames and each frame should be evaluated to the end of the partition
+ * (or full frame end). This means we cannot apply the run condition
+ * optimization because it stops evaluation window functions in certain
+ * cases.
+ */
+ if (wclause->defineClause != NIL)
+ return false;
+
req.type = T_SupportRequestWFuncMonotonic;
req.window_func = wfunc;
req.window_clause = wclause;
diff --git a/src/test/regress/expected/rpr_base.out b/src/test/regress/expected/rpr_base.out
index ae6d9c9b937..a7c536625f1 100644
--- a/src/test/regress/expected/rpr_base.out
+++ b/src/test/regress/expected/rpr_base.out
@@ -2465,7 +2465,9 @@ WHERE cnt > 0
ORDER BY id;
id | val | cnt
----+-----+-----
-(0 rows)
+ 2 | 20 | 2
+ 4 | 40 | 1
+(2 rows)
-- Nested subqueries
SELECT *
--
2.50.1 (Apple Git-155)
From 08910348e4c3770880fa098b50596805b15e38f7 Mon Sep 17 00:00:00 2001
From: Henson Choi <[email protected]>
Date: Wed, 4 Mar 2026 15:59:45 +0900
Subject: [PATCH 6/8] Disable frame optimization for RPR windows
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 04faf919033..29a02f3affb 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -5907,6 +5907,14 @@ optimize_window_clauses(PlannerInfo *root, WindowFuncLists *wflists)
if (wflists->windowFuncs[wc->winref] == NIL)
continue;
+ /*
+ * If a DEFINE clause exists, do not let support functions replace the
+ * frame with a non-RPR-compatible one. RPR windows require ROWS
+ * BETWEEN CURRENT ROW AND ...
+ */
+ if (wc->defineClause != NIL)
+ continue;
+
foreach(lc2, wflists->windowFuncs[wc->winref])
{
SupportRequestOptimizeWindowClause req;
diff --git a/src/test/regress/expected/rpr_explain.out b/src/test/regress/expected/rpr_explain.out
index 3c70a12874a..ef184b7950b 100644
--- a/src/test/regress/expected/rpr_explain.out
+++ b/src/test/regress/expected/rpr_explain.out
@@ -3821,3 +3821,117 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=500.00 loops=1)
(9 rows)
+--
+-- Planner optimization: optimize_window_clauses must not alter RPR frame
+--
+-- optimize_window_clauses() replaces frame options via prosupport functions.
+-- Affected functions: row_number, rank, dense_rank, percent_rank, cume_dist,
+-- ntile. All would change the frame to ROWS UNBOUNDED PRECEDING, breaking
+-- RPR's required ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING.
+-- Test with row_number() as representative case.
+--
+-- Without RPR: row_number() frame is optimized to ROWS UNBOUNDED PRECEDING
+CREATE VIEW rpr_ev87 AS
+SELECT row_number() OVER w
+FROM generate_series(1, 10) AS s(v)
+WINDOW w AS (
+ ORDER BY v
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+);
+EXPLAIN (COSTS OFF) SELECT * FROM rpr_ev87;
+ QUERY PLAN
+--------------------------------------------------------------
+ Subquery Scan on rpr_ev87
+ -> WindowAgg
+ Window: w AS (ORDER BY s.v ROWS UNBOUNDED PRECEDING)
+ -> Sort
+ Sort Key: s.v
+ -> Function Scan on generate_series s
+(6 rows)
+
+-- With RPR: frame must remain ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+CREATE VIEW rpr_ev88 AS
+SELECT row_number() OVER w
+FROM generate_series(1, 10) AS s(v)
+WINDOW w AS (
+ ORDER BY v
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A B+)
+ DEFINE
+ B AS v > PREV(v)
+);
+EXPLAIN (COSTS OFF) SELECT * FROM rpr_ev88;
+ QUERY PLAN
+--------------------------------------------------------------------------------------
+ Subquery Scan on rpr_ev88
+ -> WindowAgg
+ Window: w AS (ORDER BY s.v ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+ Pattern: a b+
+ -> Sort
+ Sort Key: s.v
+ -> Function Scan on generate_series s
+(7 rows)
+
+--
+-- Planner optimization: find_window_run_conditions must not push down
+-- RPR window function results as Run Conditions.
+--
+-- find_window_run_conditions() pushes WHERE filters on monotonic window
+-- functions into WindowAgg as Run Conditions for early termination.
+-- With RPR's required frame (ROWS BETWEEN CURRENT ROW AND UNBOUNDED
+-- FOLLOWING), the monotonic direction determines which operators trigger
+-- Run Condition pushdown:
+-- INCREASING (<=): row_number, rank, dense_rank, percent_rank,
+-- cume_dist, ntile
+-- DECREASING (>): count(*) (via int8inc, END_UNBOUNDED_FOLLOWING)
+-- RPR window function results are match-dependent, not monotonic.
+-- Test with count(*) > 0 as representative case.
+--
+-- Without RPR: count(*) > 0 is pushed down as Run Condition
+EXPLAIN (COSTS OFF)
+SELECT * FROM (
+ SELECT count(*) OVER w AS cnt
+ FROM generate_series(1, 10) AS s(v)
+ WINDOW w AS (
+ ORDER BY v
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ )
+) t WHERE cnt > 0;
+ QUERY PLAN
+--------------------------------------------------------------------------------------
+ Subquery Scan on t
+ -> WindowAgg
+ Window: w AS (ORDER BY s.v ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+ Run Condition: (count(*) OVER w > 0)
+ -> Sort
+ Sort Key: s.v
+ -> Function Scan on generate_series s
+(7 rows)
+
+-- With RPR: count(*) > 0 must not be pushed down as Run Condition
+EXPLAIN (COSTS OFF)
+SELECT * FROM (
+ SELECT count(*) OVER w AS cnt
+ FROM generate_series(1, 10) AS s(v)
+ WINDOW w AS (
+ ORDER BY v
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A B+)
+ DEFINE
+ B AS v > PREV(v)
+ )
+) t WHERE cnt > 0;
+ QUERY PLAN
+--------------------------------------------------------------------------------------
+ Subquery Scan on t
+ Filter: (t.cnt > 0)
+ -> WindowAgg
+ Window: w AS (ORDER BY s.v ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+ Pattern: a b+
+ -> Sort
+ Sort Key: s.v
+ -> Function Scan on generate_series s
+(8 rows)
+
diff --git a/src/test/regress/sql/rpr_explain.sql b/src/test/regress/sql/rpr_explain.sql
index 8e22382a68e..640d328957b 100644
--- a/src/test/regress/sql/rpr_explain.sql
+++ b/src/test/regress/sql/rpr_explain.sql
@@ -2196,3 +2196,81 @@ WINDOW w AS (
E AS v % 100 = 5
);');
+--
+-- Planner optimization: optimize_window_clauses must not alter RPR frame
+--
+-- optimize_window_clauses() replaces frame options via prosupport functions.
+-- Affected functions: row_number, rank, dense_rank, percent_rank, cume_dist,
+-- ntile. All would change the frame to ROWS UNBOUNDED PRECEDING, breaking
+-- RPR's required ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING.
+-- Test with row_number() as representative case.
+--
+
+-- Without RPR: row_number() frame is optimized to ROWS UNBOUNDED PRECEDING
+CREATE VIEW rpr_ev87 AS
+SELECT row_number() OVER w
+FROM generate_series(1, 10) AS s(v)
+WINDOW w AS (
+ ORDER BY v
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+);
+
+EXPLAIN (COSTS OFF) SELECT * FROM rpr_ev87;
+
+-- With RPR: frame must remain ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+CREATE VIEW rpr_ev88 AS
+SELECT row_number() OVER w
+FROM generate_series(1, 10) AS s(v)
+WINDOW w AS (
+ ORDER BY v
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A B+)
+ DEFINE
+ B AS v > PREV(v)
+);
+
+EXPLAIN (COSTS OFF) SELECT * FROM rpr_ev88;
+
+--
+-- Planner optimization: find_window_run_conditions must not push down
+-- RPR window function results as Run Conditions.
+--
+-- find_window_run_conditions() pushes WHERE filters on monotonic window
+-- functions into WindowAgg as Run Conditions for early termination.
+-- With RPR's required frame (ROWS BETWEEN CURRENT ROW AND UNBOUNDED
+-- FOLLOWING), the monotonic direction determines which operators trigger
+-- Run Condition pushdown:
+-- INCREASING (<=): row_number, rank, dense_rank, percent_rank,
+-- cume_dist, ntile
+-- DECREASING (>): count(*) (via int8inc, END_UNBOUNDED_FOLLOWING)
+-- RPR window function results are match-dependent, not monotonic.
+-- Test with count(*) > 0 as representative case.
+--
+
+-- Without RPR: count(*) > 0 is pushed down as Run Condition
+EXPLAIN (COSTS OFF)
+SELECT * FROM (
+ SELECT count(*) OVER w AS cnt
+ FROM generate_series(1, 10) AS s(v)
+ WINDOW w AS (
+ ORDER BY v
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ )
+) t WHERE cnt > 0;
+
+-- With RPR: count(*) > 0 must not be pushed down as Run Condition
+EXPLAIN (COSTS OFF)
+SELECT * FROM (
+ SELECT count(*) OVER w AS cnt
+ FROM generate_series(1, 10) AS s(v)
+ WINDOW w AS (
+ ORDER BY v
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A B+)
+ DEFINE
+ B AS v > PREV(v)
+ )
+) t WHERE cnt > 0;
+
--
2.50.1 (Apple Git-155)
From a276a393cae83a4839ff7482fa5a6d3e9fde49cb Mon Sep 17 00:00:00 2001
From: Henson Choi <[email protected]>
Date: Tue, 3 Mar 2026 22:17:29 +0900
Subject: [PATCH] Add stock scenario tests for RPR pattern matching
diff --git a/src/test/regress/data/stock.data b/src/test/regress/data/stock.data
new file mode 100644
index 00000000000..645bb643df7
--- /dev/null
+++ b/src/test/regress/data/stock.data
@@ -0,0 +1,1632 @@
+1 1 364.750 15800 0.000 0.000 0.000
+1 2 368.250 21200 0.000 0.000 0.000
+1 3 368.500 24400 0.000 0.000 0.000
+1 4 368.750 22900 0.000 0.000 0.000
+1 5 369.500 35400 0.000 0.000 0.000
+1 6 369.000 29300 0.000 0.000 0.000
+1 7 367.750 19000 0.000 0.000 0.000
+1 8 374.125 37200 0.000 0.000 0.000
+2 9 343.250 56800 0.000 0.000 0.000
+2 10 338.500 45200 0.000 0.000 0.000
+2 11 338.750 40900 0.000 0.000 0.000
+2 12 344.750 35200 0.000 0.000 0.000
+2 13 349.750 28500 0.000 0.000 0.000
+2 14 346.000 25600 0.000 0.000 0.000
+3 15 349.000 24900 0.000 0.000 0.000
+3 16 349.250 20700 0.000 0.000 0.000
+3 17 349.750 22200 0.000 0.000 0.000
+3 18 351.250 19600 0.000 0.000 0.000
+3 19 352.000 32500 0.000 0.000 0.000
+3 20 349.500 32200 0.000 0.000 0.000
+3 21 344.500 18600 0.000 0.000 0.000
+3 22 345.000 61200 0.000 0.000 0.000
+4 23 238.250 37100 0.000 0.000 0.000
+4 24 230.625 61500 0.000 0.000 0.000
+4 25 223.000 161700 0.000 0.000 0.000
+4 26 229.875 55300 0.000 0.000 0.000
+4 27 235.250 38500 0.000 0.000 0.000
+5 28 310.000 36000 0.000 0.000 0.000
+5 29 311.750 26800 0.000 0.000 0.000
+5 30 312.750 31900 0.000 0.000 0.000
+5 31 314.750 21100 0.000 0.000 0.000
+5 32 315.500 27400 0.000 0.000 0.000
+5 33 316.500 40700 0.000 0.000 0.000
+5 34 321.750 53500 0.000 0.000 0.000
+5 35 326.000 48700 0.000 0.000 0.000
+5 36 322.500 46000 0.000 0.000 0.000
+5 37 317.500 35600 0.000 0.000 0.000
+5 38 325.500 34000 0.000 0.000 0.000
+5 39 327.250 30700 0.000 0.000 0.000
+5 40 327.750 36700 0.000 0.000 0.000
+5 41 328.625 23200 0.000 0.000 0.000
+5 42 330.500 29700 0.000 0.000 0.000
+5 43 336.500 39200 0.000 0.000 0.000
+5 44 340.750 34400 0.000 0.000 0.000
+5 45 338.500 32200 0.000 0.000 0.000
+6 46 353.000 27800 0.000 0.000 0.000
+6 47 352.000 55600 0.000 0.000 0.000
+6 48 355.000 31300 0.000 0.000 0.000
+6 49 356.000 26900 0.000 0.000 0.000
+6 50 358.000 18900 0.000 0.000 0.000
+6 51 361.250 26400 0.000 0.000 0.000
+7 52 311.750 25100 0.000 0.000 0.000
+7 53 314.000 37900 0.000 0.000 0.000
+7 54 317.000 34000 0.000 0.000 0.000
+7 55 317.750 23700 0.000 0.000 0.000
+7 56 319.000 23400 0.000 0.000 0.000
+7 57 316.500 24200 0.000 0.000 0.000
+8 58 316.000 37200 0.000 0.000 0.000
+8 59 315.000 31500 0.000 0.000 0.000
+8 60 302.000 124700 0.000 0.000 0.000
+8 61 300.000 196100 0.000 0.000 0.000
+8 62 298.000 70000 0.000 0.000 0.000
+8 63 294.500 88200 0.000 0.000 0.000
+8 64 296.500 53800 0.000 0.000 0.000
+8 65 299.000 47900 0.000 0.000 0.000
+8 66 301.750 33300 0.000 0.000 0.000
+8 67 299.500 31800 0.000 0.000 0.000
+9 68 299.500 76700 0.000 0.000 0.000
+9 69 296.750 41900 0.000 0.000 0.000
+9 70 291.250 46700 0.000 0.000 0.000
+9 71 293.750 31100 0.000 0.000 0.000
+9 72 290.000 48900 0.000 0.000 0.000
+9 73 287.000 56300 0.000 0.000 0.000
+9 74 287.000 40700 0.000 0.000 0.000
+9 75 285.500 27900 0.000 0.000 0.000
+9 76 284.000 27400 0.000 0.000 0.000
+9 77 286.000 23500 0.000 0.000 0.000
+9 78 291.250 38000 0.000 0.000 0.000
+9 79 296.750 42900 0.000 0.000 0.000
+9 80 295.000 19500 0.000 0.000 0.000
+9 81 314.000 102900 0.000 0.000 0.000
+9 82 315.750 84100 0.000 0.000 0.000
+10 83 302.000 44600 0.000 0.000 0.000
+10 84 301.000 45000 0.000 0.000 0.000
+10 85 294.750 27900 0.000 0.000 0.000
+10 86 293.500 17600 0.000 0.000 0.000
+10 87 291.750 21600 0.000 0.000 0.000
+10 88 290.500 30300 0.000 0.000 0.000
+10 89 291.750 22700 0.000 0.000 0.000
+10 90 298.750 16200 0.000 0.000 0.000
+10 91 302.250 49100 0.000 0.000 0.000
+10 92 305.500 35500 0.000 0.000 0.000
+10 93 309.750 63200 0.000 0.000 0.000
+10 94 309.500 43200 0.000 0.000 0.000
+11 95 383.750 19300 0.000 0.000 0.000
+11 96 389.500 28900 0.000 0.000 0.000
+11 97 394.500 41700 0.000 0.000 0.000
+11 98 396.250 28200 0.000 0.000 0.000
+11 99 401.000 55000 0.000 0.000 0.000
+11 100 402.000 41100 0.000 0.000 0.000
+11 101 403.000 38500 0.000 0.000 0.000
+11 102 404.000 33100 0.000 0.000 0.000
+11 103 404.000 33800 0.000 0.000 0.000
+12 104 388.000 25800 0.000 0.000 0.000
+12 105 388.500 44900 0.000 0.000 0.000
+12 106 390.000 38400 0.000 0.000 0.000
+12 107 390.750 23200 0.000 0.000 0.000
+12 108 396.500 25100 0.000 0.000 0.000
+12 109 398.750 38000 0.000 0.000 0.000
+12 110 398.000 33600 0.000 0.000 0.000
+12 111 394.500 54000 0.000 0.000 0.000
+12 112 397.000 20900 0.000 0.000 0.000
+13 113 398.125 28500 0.000 396.000 401.000
+13 114 394.500 39900 0.000 392.500 397.500
+13 115 390.000 37600 0.000 390.000 393.750
+13 116 389.500 31000 0.000 386.000 392.750
+13 117 385.500 25800 0.000 385.500 389.750
+13 118 390.500 18000 0.000 385.000 390.500
+13 119 391.750 14600 0.000 391.500 395.500
+13 120 399.000 27800 0.000 392.250 399.000
+13 121 402.000 46500 0.000 399.000 403.000
+13 122 409.000 36300 0.000 404.500 409.500
+13 123 409.250 54500 0.000 409.250 413.750
+13 124 411.750 46200 0.000 406.000 412.500
+13 125 418.750 60200 0.000 412.750 419.500
+13 126 418.750 32700 0.000 416.000 420.000
+14 127 411.500 24800 0.000 411.250 415.500
+14 128 415.250 53000 0.000 412.000 423.000
+14 129 420.000 49700 0.000 417.250 425.000
+14 130 423.250 68700 0.000 422.750 429.000
+14 131 422.500 45400 0.000 420.750 424.750
+14 132 424.000 20900 0.000 420.750 424.250
+14 133 428.875 37600 0.000 427.000 430.000
+14 134 439.000 59800 0.000 427.250 439.000
+15 135 448.750 29300 0.000 444.750 449.500
+15 136 444.000 27400 0.000 444.000 449.750
+15 137 443.500 30900 0.000 440.500 448.500
+15 138 437.875 26500 0.000 437.500 442.875
+15 139 437.750 29000 0.000 433.500 440.000
+15 140 430.500 27500 0.000 430.250 441.250
+15 141 425.500 52300 0.000 424.250 428.880
+15 142 428.000 59900 0.000 419.500 429.000
+15 143 432.500 34600 0.000 427.250 435.000
+15 144 437.500 33100 0.000 432.000 438.500
+15 145 434.500 24300 0.000 434.000 437.750
+15 146 431.500 28500 0.000 430.500 437.500
+15 147 428.000 15500 0.000 427.000 432.750
+15 148 424.250 44900 0.000 424.000 428.750
+15 149 423.000 34000 0.000 420.000 426.500
+15 150 422.875 37800 0.000 420.000 424.000
+15 151 423.500 24000 0.000 423.250 428.000
+15 152 429.500 23200 0.000 420.500 429.500
+15 153 434.250 31800 0.000 429.750 435.000
+15 154 430.250 36500 0.000 426.500 436.250
+16 155 309.750 41400 0.000 307.125 310.750
+16 156 317.000 44500 0.000 309.750 317.000
+16 157 315.000 39900 0.000 313.125 319.750
+16 158 322.000 40600 0.000 318.500 322.500
+16 159 324.250 35300 0.000 321.000 325.250
+16 160 323.000 32300 0.000 322.500 327.000
+16 161 329.250 47700 0.000 323.250 330.000
+16 162 329.500 56700 0.000 328.500 333.500
+16 163 321.000 45400 0.000 320.000 328.500
+17 164 298.000 31900 0.000 290.500 298.000
+17 165 272.000 165000 0.000 270.750 285.125
+17 166 259.500 290400 0.000 252.750 261.000
+17 167 270.750 136000 0.000 266.000 270.750
+17 168 268.750 96600 0.000 266.000 271.750
+17 169 260.000 99100 0.000 258.000 264.500
+17 170 251.500 100900 0.000 250.500 260.750
+17 171 259.500 78100 0.000 253.000 261.000
+17 172 260.500 64600 0.000 260.500 267.500
+17 173 260.000 51400 0.000 259.000 264.500
+17 174 258.000 40800 0.000 257.000 261.000
+17 175 257.750 39400 0.000 255.000 257.750
+17 176 255.500 47500 0.000 253.500 257.500
+17 177 248.500 147400 0.000 247.250 254.000
+17 178 251.500 107100 0.000 245.000 251.500
+17 179 259.500 70100 0.000 252.500 262.000
+17 180 261.750 49800 0.000 255.000 263.500
+17 181 274.000 146700 0.000 270.500 284.500
+17 182 281.000 82800 0.000 277.500 283.750
+17 183 282.000 52000 0.000 281.000 284.500
+17 184 279.250 39600 0.000 278.000 280.250
+18 185 248.000 38000 0.000 247.750 250.000
+18 186 246.500 25100 0.000 244.000 248.250
+18 187 247.000 27100 0.000 245.000 248.750
+18 188 244.375 30500 0.000 244.000 248.000
+18 189 243.250 31600 0.000 240.500 244.250
+18 190 246.250 34900 0.000 245.250 248.500
+18 191 245.250 35300 0.000 245.130 249.750
+18 192 241.250 30700 0.000 240.250 244.500
+19 193 240.500 34800 0.000 237.500 243.250
+19 194 246.500 43600 0.000 236.500 246.500
+19 195 249.500 68300 0.000 246.500 251.500
+19 196 250.000 35100 0.000 249.125 254.000
+19 197 248.750 56400 0.000 247.000 251.500
+19 198 249.250 32100 0.000 246.750 250.500
+19 199 247.500 29300 0.000 247.375 250.750
+19 200 245.750 20000 0.000 245.250 247.500
+19 201 246.250 22500 0.000 245.125 246.750
+19 202 243.000 38800 0.000 242.500 247.750
+20 203 182.250 84300 0.000 176.500 183.500
+20 204 179.000 63300 0.000 179.000 185.500
+20 205 173.000 59200 0.000 171.750 178.500
+20 206 171.500 69600 0.000 170.250 175.250
+20 207 165.125 70500 0.000 165.125 172.250
+20 208 156.500 113200 0.000 155.250 165.000
+20 209 152.000 131000 0.000 150.500 157.250
+20 210 159.625 109100 0.000 150.500 159.750
+20 211 161.880 87800 0.000 161.750 168.500
+20 212 166.500 75800 0.000 158.000 166.500
+20 213 170.500 95900 0.000 169.250 173.750
+20 214 171.500 80600 0.000 166.500 173.500
+20 215 169.250 59500 0.000 168.250 175.000
+20 216 166.750 47000 0.000 164.750 168.000
+20 217 165.000 59700 0.000 165.000 171.000
+20 218 164.000 25800 0.000 160.250 164.000
+20 219 160.875 51900 0.000 160.000 166.750
+20 220 159.000 84100 0.000 153.500 159.500
+20 221 160.250 125800 0.000 154.250 161.750
+20 222 159.500 63900 0.000 158.500 163.500
+20 223 157.750 56100 0.000 155.625 160.500
+20 224 156.250 74200 0.000 154.250 159.500
+20 225 164.125 60900 0.000 159.250 164.750
+20 226 162.000 87000 0.000 160.750 164.500
+21 227 162.250 51800 0.000 161.750 166.000
+21 228 164.250 65300 0.000 160.130 164.250
+21 229 168.000 48900 0.000 166.000 168.875
+21 230 168.875 45100 0.000 168.250 171.375
+21 231 167.500 78000 0.000 166.750 171.250
+21 232 166.625 53000 0.000 165.500 170.250
+21 233 165.750 49000 0.000 163.500 166.500
+21 234 163.250 58700 0.000 163.250 167.125
+21 235 168.500 56700 0.000 162.630 168.500
+22 236 212.000 89200 0.000 212.000 217.750
+22 237 207.500 117700 0.000 206.000 209.750
+22 238 212.000 95100 0.000 207.500 212.500
+22 239 212.250 74000 0.000 211.500 214.000
+22 240 215.500 68400 0.000 211.880 215.750
+22 241 220.000 79200 0.000 216.000 220.000
+23 242 222.500 45700 0.000 222.250 224.630
+23 243 223.250 43000 0.000 221.500 223.500
+23 244 224.250 47700 0.000 223.500 225.250
+23 245 226.500 43800 0.000 223.380 226.500
+23 246 230.000 88800 0.000 226.250 230.000
+23 247 229.380 118000 0.000 229.000 230.880
+23 248 228.500 127100 0.000 228.375 231.000
+23 249 230.380 136400 0.000 228.500 230.750
+24 250 249.880 97500 0.000 249.630 255.000
+24 251 251.000 90400 0.000 248.130 252.250
+24 252 256.500 90400 0.000 251.000 257.000
+24 253 257.750 111000 0.000 257.625 259.750
+24 254 259.250 87200 0.000 256.000 259.250
+24 255 261.000 111100 0.000 258.250 261.250
+24 256 263.750 75900 0.000 261.250 264.500
+24 257 258.000 121100 0.000 257.750 263.250
+24 258 254.000 125500 0.000 254.000 257.250
+24 259 253.500 78400 0.000 252.750 257.750
+24 260 258.000 74700 0.000 253.000 258.000
+25 261 279.750 62700 0.000 277.130 280.000
+25 262 277.625 85500 0.000 276.750 280.375
+25 263 276.630 99800 0.000 276.630 280.380
+25 264 276.250 113600 0.000 276.000 279.500
+25 265 274.250 187100 0.000 272.750 276.380
+25 266 272.000 105300 0.000 272.000 275.500
+25 267 270.500 111000 0.000 268.000 272.750
+25 268 271.000 66000 0.000 270.500 272.500
+25 269 272.500 43000 0.000 270.250 272.500
+25 270 273.000 56200 0.000 272.000 273.880
+25 271 275.500 55200 0.000 273.750 275.500
+25 272 272.250 73400 0.000 272.000 275.500
+26 273 262.250 89000 0.000 262.000 267.750
+26 274 259.500 146100 0.000 257.500 261.750
+26 275 260.250 90300 0.000 257.500 260.625
+26 276 261.000 74100 0.000 258.880 261.880
+26 277 264.500 44400 0.000 260.250 264.880
+26 278 262.000 78900 0.000 261.250 264.500
+27 279 272.500 132000 0.000 270.130 272.500
+27 280 272.000 79800 0.000 271.375 272.625
+27 281 271.130 75600 0.000 270.750 272.880
+27 282 270.000 113800 0.000 269.880 271.500
+27 283 264.000 179500 0.000 263.500 270.500
+27 284 258.000 215300 0.000 257.500 263.250
+27 285 258.250 154200 0.000 256.500 259.625
+27 286 259.500 95600 0.000 257.500 260.500
+27 287 261.250 96500 0.000 259.250 262.000
+27 288 259.750 119000 0.000 259.250 261.625
+27 289 258.750 81500 0.000 257.130 259.750
+28 290 257.380 53700 0.000 257.250 259.250
+28 291 256.000 52200 0.000 255.375 257.500
+28 292 257.750 76200 0.000 255.630 258.630
+28 293 258.750 93400 0.000 257.380 259.500
+28 294 258.750 99000 0.000 258.500 259.630
+28 295 257.250 123100 0.000 255.750 257.500
+28 296 260.500 188300 0.000 257.000 261.000
+28 297 260.250 103300 0.000 259.000 261.250
+29 298 264.250 110100 0.000 261.630 264.380
+29 299 266.000 98100 0.000 264.250 266.750
+29 300 267.500 146100 0.000 266.625 268.000
+29 301 270.250 199400 0.000 267.500 270.250
+29 302 270.250 89800 0.000 269.000 270.625
+29 303 271.000 78600 0.000 269.250 271.250
+29 304 272.750 107600 0.000 269.880 273.500
+29 305 273.500 119200 0.000 272.750 275.250
+29 306 273.500 0 0.000 0.000 0.000
+30 307 236.250 146100 0.000 236.250 242.000
+30 308 239.500 358100 0.000 235.500 239.500
+30 309 243.500 241500 0.000 240.500 244.000
+30 310 251.125 304800 0.000 248.375 253.625
+30 311 251.250 231000 0.000 248.875 253.000
+30 312 253.000 152800 0.000 249.625 254.000
+30 313 253.500 175100 0.000 252.500 257.880
+30 314 254.000 141800 0.000 252.125 255.500
+30 315 258.000 110000 0.000 253.000 258.250
+30 316 261.750 374200 0.000 261.250 264.000
+30 317 262.380 275100 0.000 261.380 265.000
+30 318 261.500 169700 0.000 259.875 261.750
+31 319 262.500 87100 0.000 262.250 266.500
+31 320 258.250 91100 0.000 257.750 262.500
+31 321 260.880 105600 0.000 258.250 262.000
+31 322 261.750 85000 0.000 258.500 261.880
+31 323 259.630 68400 0.000 259.000 262.250
+31 324 257.250 45500 0.000 257.250 260.250
+31 325 256.750 39900 0.000 255.000 257.500
+31 326 258.000 102400 0.000 255.250 259.500
+32 327 268.125 180700 0.000 267.750 272.000
+32 328 270.250 80900 0.000 267.000 270.250
+32 329 271.000 107200 0.000 267.750 271.000
+32 330 273.000 149400 0.000 269.250 273.000
+32 331 273.500 262900 0.000 273.000 276.750
+32 332 276.250 150300 0.000 273.000 276.250
+32 333 278.630 172800 0.000 275.500 279.500
+32 334 281.000 132700 0.000 278.750 281.500
+32 335 279.125 178400 0.000 279.000 281.750
+33 336 311.000 96900 0.000 308.130 311.000
+33 337 308.250 85000 0.000 307.625 311.000
+33 338 305.750 119200 0.000 304.500 306.625
+33 339 303.500 133200 0.000 303.000 306.500
+33 340 298.500 168400 0.000 297.130 299.750
+33 341 299.630 77900 0.000 299.130 300.630
+33 342 301.750 91100 0.000 300.130 301.880
+33 343 306.630 78700 0.000 299.750 307.000
+33 344 308.125 132600 0.000 306.750 310.000
+33 345 305.500 82400 0.000 304.000 308.250
+34 346 308.875 80600 0.000 308.250 312.875
+34 347 306.750 124000 0.000 306.250 309.250
+34 348 310.000 123300 0.000 307.000 310.250
+34 349 310.250 102600 0.000 309.750 312.875
+34 350 312.000 80400 0.000 309.880 312.250
+34 351 308.880 115900 0.000 308.500 312.000
+34 352 310.500 98300 0.000 307.500 310.500
+34 353 314.250 116900 0.000 309.750 314.250
+34 354 315.625 118600 0.000 314.250 317.500
+34 355 318.000 98700 0.000 315.000 318.500
+34 356 319.750 149000 0.000 318.000 321.500
+34 357 311.250 121900 0.000 311.000 317.500
+34 358 309.000 84400 0.000 308.130 309.880
+34 359 312.000 126500 0.000 308.625 312.250
+35 360 78.000 512100 0.000 77.500 78.500
+35 361 75.375 607700 0.000 75.250 78.000
+35 362 74.625 628300 0.000 73.750 75.250
+35 363 74.250 517300 0.000 73.875 75.000
+35 364 72.750 358300 0.000 72.625 74.000
+35 365 72.630 394600 0.000 72.250 73.500
+35 366 73.000 599200 0.000 72.130 73.750
+35 367 73.500 450200 0.000 72.875 74.500
+35 368 74.130 304400 0.000 73.880 74.250
+35 369 73.875 289200 0.000 73.375 74.500
+36 370 72.250 498300 0.000 69.875 72.375
+36 371 71.630 476800 0.000 71.000 72.500
+36 372 71.250 282600 0.000 71.000 71.880
+36 373 71.630 350300 0.000 70.880 71.880
+36 374 70.880 261500 0.000 70.500 71.630
+36 375 70.500 531700 0.000 70.380 71.130
+36 376 69.625 391900 0.000 69.500 70.750
+36 377 69.625 337000 0.000 69.125 70.500
+36 378 70.000 338900 0.000 69.500 70.625
+36 379 70.000 393400 0.000 69.625 70.125
+36 380 69.630 293200 0.000 69.630 70.250
+36 381 69.500 205300 0.000 69.500 70.000
+36 382 70.000 268300 0.000 69.750 70.000
+36 383 68.630 381500 0.000 68.500 69.880
+36 384 68.250 693200 0.000 67.250 68.500
+36 385 67.630 316100 0.000 67.500 68.250
+36 386 67.750 406600 0.000 67.380 68.000
+36 387 67.500 305800 0.000 67.375 67.875
+36 388 66.500 466500 0.000 66.125 67.750
+36 389 66.250 652800 0.000 65.750 66.625
+36 390 66.750 399300 0.000 66.125 66.875
+36 391 67.880 502100 0.000 66.500 68.380
+36 392 68.500 538900 0.000 68.380 69.250
+36 393 67.880 407700 0.000 67.750 68.500
+36 394 67.750 396500 0.000 67.750 68.630
+36 395 71.250 439900 0.000 67.625 71.375
+36 396 70.000 626100 0.000 69.380 71.000
+37 397 65.000 208900 0.000 64.500 65.125
+37 398 65.380 291800 0.000 64.880 65.380
+37 399 65.750 377600 0.000 65.500 66.380
+37 400 67.250 283500 0.000 65.380 67.250
+37 401 66.630 582400 0.000 66.380 67.630
+37 402 66.375 257200 0.000 66.125 66.625
+37 403 65.625 393900 0.000 65.625 66.750
+37 404 65.250 295500 0.000 65.130 65.750
+37 405 65.000 329700 0.000 64.630 65.250
+37 406 66.000 412400 0.000 64.880 66.130
+38 407 64.875 176300 0.000 64.000 64.875
+38 408 64.875 163300 0.000 64.375 65.125
+38 409 64.750 253800 0.000 64.250 65.000
+38 410 64.250 279200 0.000 64.000 64.630
+38 411 64.380 308600 0.000 64.000 64.630
+38 412 62.500 412000 0.000 62.500 64.500
+38 413 63.500 610300 0.000 61.250 63.500
+38 414 63.380 472800 0.000 63.000 63.750
+39 415 54.625 512900 0.000 53.375 54.750
+39 416 54.250 504900 0.000 54.000 55.250
+39 417 53.500 651700 0.000 53.500 55.000
+39 418 53.000 334100 0.000 52.750 53.750
+39 419 52.000 579900 0.000 51.875 53.500
+39 420 51.125 672300 0.000 51.000 52.875
+39 421 51.250 451000 0.000 50.380 51.630
+39 422 51.250 411900 0.000 50.875 51.750
+39 423 51.250 663800 0.000 50.500 51.625
+39 424 53.875 821800 0.000 52.000 54.500
+39 425 53.500 761400 0.000 53.250 54.375
+40 426 66.000 437200 0.000 64.250 66.000
+40 427 65.130 553000 0.000 64.750 66.130
+40 428 64.880 449000 0.000 64.380 65.380
+40 429 64.750 397400 0.000 64.250 65.000
+40 430 64.380 375800 0.000 63.880 64.630
+40 431 65.000 270800 0.000 64.000 65.250
+40 432 65.125 410600 0.000 64.375 65.500
+40 433 65.500 555700 0.000 65.130 66.000
+40 434 65.250 644100 0.000 64.380 65.380
+41 435 56.750 492500 0.000 56.250 56.880
+41 436 56.630 616200 0.000 55.750 56.630
+41 437 56.380 348000 0.000 56.130 56.630
+41 438 55.625 461800 0.000 55.500 56.375
+41 439 55.500 438400 0.000 55.250 55.875
+41 440 56.130 514900 0.000 55.130 56.380
+41 441 57.880 673900 0.000 55.880 58.130
+41 442 58.000 700200 0.000 57.250 58.500
+41 443 58.750 796900 0.000 57.880 59.130
+41 444 60.250 1089200 0.000 59.750 60.750
+41 445 59.880 747600 0.000 59.880 60.500
+42 446 55.875 387300 0.000 55.000 56.750
+42 447 55.500 396600 0.000 55.500 56.500
+42 448 54.880 372500 0.000 54.500 55.380
+42 449 54.130 551600 0.000 53.500 55.130
+42 450 54.000 346100 0.000 53.875 54.500
+42 451 54.375 427300 0.000 54.125 55.000
+42 452 54.500 389800 0.000 53.380 54.630
+42 453 54.625 497600 0.000 54.250 55.250
+42 454 53.625 524200 0.000 53.250 54.250
+43 455 64.625 1058800 0.000 63.375 64.625
+43 456 64.500 689400 0.000 64.250 64.880
+43 457 63.750 1140600 0.000 63.625 65.125
+43 458 63.250 892800 0.000 63.000 63.880
+43 459 63.875 512200 0.000 63.250 63.875
+43 460 64.375 645800 0.000 64.000 64.750
+43 461 65.750 1214100 0.000 64.500 65.750
+43 462 66.000 911700 0.000 65.000 66.130
+43 463 65.125 685600 0.000 64.625 65.750
+43 464 64.880 717200 0.000 64.500 65.250
+43 465 64.125 491600 0.000 64.125 64.625
+43 466 64.250 759200 0.000 64.000 64.750
+43 467 64.500 384600 0.000 64.000 64.750
+43 468 64.750 497500 0.000 64.500 65.250
+43 469 65.125 855600 0.000 64.750 65.250
+43 470 64.875 777200 0.000 64.750 65.125
+43 471 64.750 487900 0.000 64.750 65.130
+43 472 63.875 569800 0.000 63.875 64.875
+43 473 64.380 372400 0.000 64.000 64.750
+44 474 60.000 290100 0.000 60.000 60.630
+44 475 60.380 358800 0.000 59.380 60.750
+44 476 61.000 638300 0.000 60.380 61.250
+44 477 61.500 963900 0.000 59.750 61.630
+44 478 62.500 1247100 0.000 61.380 62.500
+44 479 63.500 985100 0.000 62.880 63.630
+44 480 64.500 1556300 0.000 63.125 65.000
+44 481 66.875 1688100 0.000 64.750 66.875
+44 482 66.750 1578700 0.000 66.375 67.500
+45 483 76.750 1321700 0.000 74.750 76.750
+45 484 75.500 1604100 0.000 75.500 77.630
+45 485 76.380 1254000 0.000 75.250 76.630
+45 486 76.000 887100 0.000 75.630 76.500
+45 487 76.630 490500 0.000 75.750 77.000
+45 488 76.250 868300 0.000 76.250 77.250
+45 489 74.750 831000 0.000 74.500 76.375
+45 490 73.375 997900 0.000 73.250 74.875
+45 491 74.130 747500 0.000 73.380 74.630
+45 492 74.500 614900 0.000 73.250 74.630
+45 493 75.130 752300 0.000 74.630 76.130
+45 494 79.000 1346100 0.000 75.375 79.000
+45 495 80.380 2207600 0.000 78.380 80.380
+45 496 81.130 1342500 0.000 79.250 81.130
+45 497 83.500 1439300 0.000 81.380 84.500
+45 498 82.750 1500400 0.000 82.375 84.375
+46 499 96.130 725400 0.000 95.750 98.000
+46 500 96.250 373700 0.000 96.125 97.125
+46 501 93.000 760600 0.000 92.500 97.000
+46 502 95.625 915100 0.000 92.375 95.625
+46 503 96.000 950900 0.000 95.000 96.750
+46 504 97.625 1024200 0.000 95.500 98.500
+46 505 96.630 1060600 0.000 96.500 98.380
+46 506 98.000 1114200 0.000 96.000 99.250
+46 507 98.000 850900 0.000 97.000 98.380
+47 508 102.000 711800 0.000 101.880 103.250
+47 509 102.625 858000 0.000 101.250 102.875
+47 510 103.250 607100 0.000 102.000 103.375
+47 511 103.875 894200 0.000 102.625 104.000
+47 512 106.625 1651200 0.000 104.625 106.750
+47 513 107.000 1049000 0.000 106.000 107.380
+47 514 108.750 978800 0.000 107.375 109.000
+47 515 109.500 1007500 0.000 107.250 110.000
+47 516 110.125 794100 0.000 109.625 110.500
+47 517 112.630 1020400 0.000 110.000 112.630
+47 518 111.500 929400 0.000 111.000 112.130
+48 519 116.500 424700 0.000 115.625 116.625
+48 520 115.250 668900 0.000 114.000 115.250
+48 521 114.000 722000 0.000 113.125 115.000
+48 522 111.750 802300 0.000 110.750 114.750
+48 523 111.250 673500 0.000 110.500 112.500
+48 524 110.750 538900 0.000 110.250 111.750
+48 525 112.500 813000 0.000 109.750 112.875
+48 526 114.630 665400 0.000 113.000 115.000
+48 527 115.630 843600 0.000 113.380 115.630
+48 528 114.380 647800 0.000 114.250 115.880
+48 529 113.000 404400 0.000 112.875 115.000
+48 530 111.250 734800 0.000 110.250 112.880
+48 531 111.875 890300 0.000 110.250 111.875
+48 532 113.500 803000 0.000 111.250 113.875
+48 533 114.000 662100 0.000 113.000 114.880
+48 534 115.750 573900 0.000 113.630 116.130
+48 535 113.130 791400 0.000 113.000 116.250
+48 536 113.375 831700 0.000 112.625 114.750
+48 537 113.750 672300 0.000 113.130 114.500
+48 538 114.250 419600 0.000 113.630 114.630
+48 539 117.250 737800 0.000 114.125 117.250
+48 540 118.000 971500 0.000 117.000 119.750
+48 541 121.000 1170300 0.000 118.130 121.000
+48 542 121.750 1233400 0.000 121.500 122.875
+48 543 121.130 625500 0.000 120.250 122.250
+49 544 120.875 542900 0.000 120.250 122.750
+49 545 118.000 865300 0.000 117.750 121.380
+49 546 120.000 669800 0.000 117.880 120.880
+49 547 120.250 556200 0.000 119.750 121.000
+49 548 121.000 373100 0.000 120.375 121.250
+49 549 119.250 943300 0.000 118.625 119.625
+50 550 122.880 814300 0.000 121.630 123.500
+50 551 121.750 691100 0.000 121.500 123.250
+50 552 123.125 819700 0.000 121.500 124.250
+50 553 123.750 911500 0.000 122.625 124.875
+50 554 123.875 942300 0.000 123.125 124.500
+50 555 123.000 1070900 0.000 122.875 124.125
+50 556 124.625 1099300 0.000 122.750 124.750
+50 557 126.625 1111900 0.000 124.250 126.625
+50 558 128.625 2097500 0.000 126.000 129.500
+50 559 127.875 1271500 0.000 126.500 128.375
+51 560 129.875 1190200 0.000 128.875 130.125
+51 561 131.130 1391200 0.000 129.130 131.250
+51 562 132.130 1104600 0.000 131.000 132.250
+51 563 132.250 803300 0.000 131.750 132.500
+51 564 134.250 658400 0.000 131.500 134.250
+51 565 133.000 687100 0.000 132.750 134.000
+52 566 126.750 1413200 0.000 125.000 127.250
+52 567 126.500 946400 0.000 125.500 127.000
+52 568 123.880 922600 0.000 123.500 126.500
+52 569 122.250 1474500 0.000 121.750 123.250
+52 570 122.125 803600 0.000 122.000 123.375
+52 571 122.250 650900 0.000 121.880 122.880
+52 572 123.625 858900 0.000 122.375 123.625
+52 573 125.250 1165800 0.000 124.000 125.750
+52 574 126.875 784800 0.000 125.125 127.250
+52 575 127.500 1030600 0.000 127.000 128.250
+52 576 124.500 1354300 0.000 124.000 127.630
+53 577 111.250 1043200 0.000 109.250 111.500
+53 578 112.880 1458900 0.000 110.750 113.500
+53 579 110.000 1241300 0.000 109.875 112.500
+53 580 110.250 1117000 0.000 109.375 111.750
+53 581 111.500 812900 0.000 109.750 111.625
+53 582 112.000 1005000 0.000 111.625 112.875
+53 583 111.000 779400 0.000 110.375 111.500
+53 584 109.250 868500 0.000 109.125 111.625
+54 585 108.000 729600 0.000 107.375 108.625
+54 586 110.250 1455900 0.000 108.500 110.500
+54 587 110.625 1432300 0.000 110.375 112.000
+54 588 111.000 990700 0.000 110.375 111.500
+54 589 112.000 954700 0.000 111.125 112.500
+54 590 113.750 2110500 0.000 112.750 114.250
+54 591 114.000 817600 0.000 112.500 114.000
+54 592 114.750 1241000 0.000 113.500 115.380
+54 593 114.000 914900 0.000 114.000 115.500
+54 594 112.750 881400 0.000 112.375 114.000
+54 595 112.250 836700 0.000 111.500 113.000
+54 596 112.250 694200 0.000 111.750 113.250
+55 597 112.750 770500 0.000 110.630 112.880
+55 598 112.250 931500 0.000 112.000 113.500
+55 599 111.125 720700 0.000 110.500 112.250
+55 600 110.750 1028700 0.000 110.125 111.125
+55 601 109.880 836400 0.000 109.630 111.380
+55 602 110.750 1104400 0.000 109.500 111.625
+55 603 111.875 899400 0.000 110.625 112.250
+55 604 113.250 1090000 0.000 111.875 113.500
+55 605 113.380 1022900 0.000 112.630 114.250
+55 606 113.750 1122500 0.000 112.750 114.630
+55 607 116.500 1463500 0.000 114.000 116.500
+55 608 116.250 1337900 0.000 116.000 117.250
+56 609 107.250 704700 0.000 105.375 107.500
+56 610 107.125 802400 0.000 106.250 107.375
+56 611 105.250 897500 0.000 105.250 107.130
+56 612 104.375 1076900 0.000 104.125 105.625
+56 613 105.250 856100 0.000 104.000 105.750
+56 614 107.250 925700 0.000 104.500 107.250
+56 615 108.130 1047100 0.000 106.750 108.380
+56 616 107.500 625100 0.000 107.125 108.125
+56 617 107.000 895900 0.000 106.250 107.375
+56 618 106.500 810000 0.000 105.500 107.250
+56 619 106.125 931100 0.000 105.125 106.375
+56 620 105.380 710200 0.000 105.130 107.000
+56 621 107.125 1591800 0.000 104.500 107.750
+56 622 108.250 1002700 0.000 106.750 108.750
+56 623 109.000 1059300 0.000 107.875 109.625
+56 624 108.380 610400 0.000 108.250 109.380
+57 625 124.625 929500 0.000 124.625 126.125
+57 626 125.750 1200100 0.000 125.250 126.875
+57 627 125.880 763200 0.000 125.750 126.500
+57 628 126.750 589700 0.000 123.750 126.875
+57 629 127.500 1270600 0.000 126.500 127.750
+57 630 125.750 1668400 0.000 125.375 127.125
+57 631 124.880 1056700 0.000 124.250 125.750
+57 632 123.250 944300 0.000 123.250 125.500
+57 633 124.000 782700 0.000 122.750 124.000
+58 634 124.250 754200 0.000 123.250 124.500
+58 635 123.750 418300 0.000 123.625 124.250
+58 636 123.125 505200 0.000 122.750 124.250
+58 637 123.500 573600 0.000 122.750 124.000
+58 638 123.125 685000 0.000 122.875 123.875
+58 639 121.000 872500 0.000 120.750 122.630
+58 640 120.000 1388100 0.000 119.875 122.250
+58 641 119.750 1007000 0.000 119.000 120.375
+59 642 123.625 1069600 0.000 123.250 124.625
+59 643 124.130 1163300 0.000 123.630 125.000
+59 644 128.250 2138800 0.000 124.630 128.500
+59 645 129.630 2539000 0.000 129.130 130.630
+59 646 132.500 2270200 0.000 129.375 132.625
+59 647 133.130 1825200 0.000 132.380 134.500
+59 648 133.250 1212300 0.000 132.625 134.125
+59 649 134.625 1936500 0.000 133.250 135.250
+59 650 137.000 1128700 0.000 133.750 137.000
+59 651 136.630 1563600 0.000 136.130 137.630
+60 652 130.500 1037200 0.000 128.250 131.125
+60 653 129.375 1118800 0.000 129.000 130.625
+60 654 128.750 1552200 0.000 128.125 130.000
+60 655 127.750 1197800 0.000 127.750 128.750
+60 656 124.500 2057900 0.000 124.500 128.000
+60 657 125.375 1404400 0.000 124.125 125.625
+60 658 126.380 1098400 0.000 125.500 126.630
+60 659 126.625 993400 0.000 126.375 127.625
+60 660 128.125 840800 0.000 126.875 128.250
+60 661 127.750 949000 0.000 127.500 129.500
+61 662 126.500 1280300 0.000 125.500 126.750
+61 663 125.630 1135000 0.000 125.130 126.880
+61 664 125.375 1258800 0.000 124.750 126.375
+61 665 125.000 770500 0.000 124.630 125.630
+61 666 124.750 579400 0.000 124.750 125.625
+61 667 125.750 772900 0.000 125.000 126.250
+61 668 127.125 925700 0.000 125.375 127.250
+61 669 127.880 978300 0.000 127.000 128.250
+61 670 130.130 2239000 0.000 128.500 130.880
+61 671 130.250 1157600 0.000 129.750 130.625
+61 672 128.000 1551200 0.000 127.750 130.875
+62 673 133.000 1215000 0.000 132.380 133.250
+62 674 132.375 857100 0.000 131.875 132.750
+62 675 131.375 829300 0.000 131.250 132.375
+62 676 130.880 675200 0.000 130.750 131.630
+62 677 130.375 921900 0.000 129.750 131.750
+62 678 129.750 939700 0.000 129.250 130.250
+62 679 128.000 1666600 0.000 127.250 129.500
+62 680 128.625 1371900 0.000 127.250 130.000
+62 681 129.125 1265600 0.000 128.250 129.500
+62 682 129.750 1176100 0.000 128.750 130.130
+62 683 128.500 1644500 0.000 128.500 130.630
+63 684 121.130 1753000 0.000 119.380 121.130
+63 685 120.130 1316600 0.000 119.250 120.500
+63 686 119.875 1819000 0.000 119.125 121.625
+63 687 119.375 1268900 0.000 119.250 120.875
+63 688 118.750 1400300 0.000 117.375 119.750
+63 689 119.380 1365100 0.000 118.380 119.750
+63 690 120.875 1125300 0.000 118.750 121.125
+63 691 122.000 1753000 0.000 121.380 123.380
+63 692 123.130 914300 0.000 122.000 123.250
+63 693 124.000 1151500 0.000 123.125 124.250
+63 694 123.750 820500 0.000 123.500 124.500
+64 695 141.750 1638400 0.000 140.250 141.750
+64 696 144.630 1662500 0.000 142.130 144.880
+64 697 146.625 1940200 0.000 144.250 148.000
+64 698 149.000 1985600 0.000 146.625 149.375
+64 699 148.630 1407600 0.000 147.000 149.380
+64 700 150.250 1890100 0.000 149.130 151.500
+64 701 152.250 1586100 0.000 150.000 153.500
+64 702 152.500 1786100 0.000 151.500 154.500
+64 703 152.875 1524100 0.000 151.625 153.875
+64 704 153.750 1105900 0.000 152.375 154.000
+64 705 154.250 2127800 0.000 153.750 156.625
+64 706 154.000 1220900 0.000 153.380 155.130
+64 707 152.750 1011600 0.000 151.250 154.630
+64 708 153.250 562700 0.000 152.125 153.750
+65 709 141.130 1858600 0.000 138.880 141.250
+65 710 141.125 1561600 0.000 140.500 142.000
+65 711 140.000 1229000 0.000 139.500 140.380
+65 712 138.750 887600 0.000 138.000 140.000
+65 713 136.000 1938300 0.000 135.500 138.875
+65 714 136.125 1628600 0.000 135.125 136.375
+65 715 139.380 1630000 0.000 136.000 139.500
+65 716 140.375 2618100 0.000 138.500 141.000
+65 717 143.750 2629100 0.000 140.875 143.875
+65 718 143.880 2251100 0.000 143.500 145.500
+65 719 144.130 1766900 0.000 142.880 144.500
+65 720 139.250 2866100 0.000 138.750 142.880
+65 721 137.380 4379500 0.000 136.250 139.880
+65 722 137.000 2194500 0.000 136.000 138.500
+65 723 138.250 1413100 0.000 135.625 138.500
+66 724 123.625 2431100 0.000 122.375 124.500
+66 725 122.000 1609300 0.000 121.880 125.250
+66 726 120.880 3142200 0.000 119.750 123.000
+66 727 123.000 2239500 0.000 120.375 123.000
+66 728 121.130 2220400 0.000 120.880 123.380
+66 729 121.875 1947600 0.000 120.500 122.375
+66 730 120.250 1484700 0.000 119.875 121.250
+66 731 121.375 1697000 0.000 120.250 121.375
+66 732 120.375 1283900 0.000 120.125 121.500
+67 733 123.130 2107700 0.000 122.500 124.750
+67 734 121.380 2581200 0.000 119.250 122.000
+67 735 121.500 1103400 0.000 120.000 121.880
+67 736 123.125 998200 0.000 121.250 123.250
+67 737 123.750 751400 0.000 122.875 124.250
+67 738 122.750 1130400 0.000 121.875 124.375
+68 739 151.750 1804700 0.000 151.500 154.130
+68 740 154.250 3308900 0.000 149.500 156.000
+68 741 155.250 2945900 0.000 154.625 156.625
+68 742 158.630 3248800 0.000 155.880 159.630
+68 743 160.125 3272200 0.000 157.750 161.500
+68 744 160.630 2385000 0.000 159.130 162.380
+68 745 163.000 1561700 0.000 159.750 163.000
+68 746 166.875 2792500 0.000 164.250 167.625
+68 747 166.750 2187900 0.000 166.000 167.630
+68 748 164.880 1652200 0.000 163.880 166.750
+68 749 163.750 1430500 0.000 163.250 165.875
+68 750 163.000 2269600 0.000 163.000 167.380
+68 751 165.380 1998200 0.000 162.250 166.130
+69 752 159.250 2057900 0.000 158.750 160.880
+69 753 159.380 1668400 0.000 158.500 160.000
+69 754 160.250 2435800 0.000 159.750 161.000
+69 755 163.000 2284000 0.000 159.125 163.250
+69 756 164.375 2386900 0.000 163.000 165.625
+69 757 166.750 1632200 0.000 164.500 166.750
+69 758 169.000 3203300 0.000 167.130 169.630
+69 759 170.380 3299900 0.000 168.000 172.130
+69 760 173.380 2586600 0.000 170.380 174.000
+69 761 173.250 2109200 0.000 172.000 175.000
+70 762 150.500 2630500 0.000 149.500 153.750
+70 763 150.750 3073300 0.000 148.500 151.880
+70 764 154.630 1908400 0.000 151.000 154.750
+70 765 155.250 1868500 0.000 154.750 156.880
+70 766 156.500 1544300 0.000 154.500 157.250
+70 767 151.000 2044200 0.000 151.000 156.375
+71 768 145.250 2922100 0.000 144.130 147.380
+71 769 140.125 3923900 0.000 140.000 145.750
+71 770 135.000 4665000 0.000 132.000 141.250
+71 771 104.000 6384400 0.000 102.000 130.750
+71 772 115.000 0 0.000 110.130 122.000
+71 773 122.750 5386600 0.000 120.000 125.500
+72 774 118.880 2233600 0.000 115.000 119.250
+72 775 115.500 1824700 0.000 115.000 119.500
+72 776 117.250 2798900 0.000 115.250 118.250
+72 777 118.250 1550200 0.000 116.880 119.500
+72 778 118.250 1217000 0.000 116.500 118.375
+72 779 119.500 1646400 0.000 118.750 120.380
+72 780 119.875 630300 0.000 119.000 119.875
+72 781 115.750 1086800 0.000 115.130 117.630
+73 782 111.630 1230300 0.000 110.750 112.130
+73 783 112.250 769000 0.000 111.500 112.750
+73 784 113.380 867400 0.000 112.750 114.000
+73 785 112.875 896800 0.000 112.625 113.750
+73 786 111.500 932200 0.000 110.750 113.500
+73 787 110.250 1489000 0.000 109.500 111.130
+73 788 114.000 1564900 0.000 111.630 114.750
+73 789 112.875 958200 0.000 112.375 113.875
+74 790 114.125 1652700 0.000 112.500 114.375
+74 791 113.500 947400 0.000 113.375 114.125
+74 792 112.750 1100200 0.000 112.500 114.380
+74 793 112.500 2195900 0.000 110.630 112.630
+74 794 113.000 1558100 0.000 111.880 113.000
+74 795 113.380 1187700 0.000 113.130 114.250
+74 796 115.000 1365300 0.000 113.630 115.250
+74 797 114.625 1992000 0.000 114.500 116.500
+74 798 114.000 1582100 0.000 113.625 115.000
+74 799 113.750 2225000 0.000 112.625 114.000
+74 800 115.500 2406400 0.000 113.250 115.750
+74 801 117.375 2209500 0.000 115.750 117.625
+74 802 118.250 2648200 0.000 117.750 119.375
+74 803 117.125 1619800 0.000 117.125 119.125
+75 804 116.380 1333800 0.000 116.000 118.380
+75 805 116.875 1509800 0.000 116.125 117.500
+75 806 117.130 939000 0.000 116.880 117.630
+75 807 117.380 1049800 0.000 116.880 118.250
+75 808 117.750 1708300 0.000 117.375 118.750
+75 809 116.750 1105600 0.000 116.000 117.630
+75 810 116.250 1141200 0.000 115.875 117.000
+75 811 116.000 1173400 0.000 115.000 116.380
+75 812 116.250 946600 0.000 115.375 116.375
+76 813 98.750 1249700 0.000 98.000 99.380
+76 814 99.500 1127900 0.000 98.500 99.750
+76 815 100.125 1129400 0.000 99.375 100.500
+76 816 100.375 638800 0.000 100.250 100.750
+76 817 99.380 1245400 0.000 98.250 100.750
+76 818 98.880 1157700 0.000 98.500 99.380
+76 819 97.750 956500 0.000 97.500 98.625
+76 820 97.630 1001300 0.000 96.880 98.130
+76 821 97.130 1434200 0.000 96.880 98.130
+76 822 99.250 2026900 0.000 97.380 99.500
+77 823 95.375 1005100 0.000 95.125 95.750
+77 824 94.500 682200 0.000 94.250 95.500
+77 825 94.750 1165700 0.000 94.500 95.375
+77 826 94.130 1192300 0.000 94.000 95.130
+77 827 94.130 1637100 0.000 94.000 94.630
+77 828 98.000 1760400 0.000 94.500 98.130
+77 829 98.875 2366000 0.000 98.375 99.500
+77 830 100.000 2418700 0.000 98.880 100.380
+77 831 99.750 1890500 0.000 99.500 101.630
+78 832 106.625 1050400 0.000 106.000 106.750
+78 833 106.500 1716200 0.000 105.880 107.750
+78 834 106.130 868400 0.000 105.880 106.880
+78 835 105.875 752300 0.000 105.250 106.375
+78 836 105.500 782800 0.000 105.250 105.880
+78 837 105.875 934800 0.000 105.125 106.375
+78 838 106.375 645200 0.000 105.875 106.500
+78 839 107.125 921600 0.000 107.000 108.000
+78 840 110.880 4120700 0.000 110.130 111.250
+78 841 111.125 1916900 0.000 109.875 111.250
+78 842 109.875 1531000 0.000 109.500 110.875
+79 843 102.380 1347000 0.000 101.500 103.500
+79 844 105.500 1634700 0.000 101.880 105.500
+79 845 105.880 1609400 0.000 105.500 107.630
+79 846 107.500 1438200 0.000 105.880 107.750
+79 847 107.880 1192900 0.000 107.130 108.380
+79 848 105.000 1077000 0.000 104.750 107.750
+80 849 107.375 1101700 0.000 107.125 109.000
+80 850 107.880 1162000 0.000 106.380 108.250
+80 851 108.250 1570000 0.000 106.000 109.000
+80 852 109.500 901300 0.000 108.625 109.500
+80 853 105.375 1418600 0.000 105.375 108.625
+80 854 103.500 2258600 0.000 102.875 106.625
+80 855 101.000 3113800 0.000 99.750 103.880
+80 856 100.250 2308100 0.000 99.500 102.000
+80 857 99.250 3121400 0.000 96.250 100.875
+80 858 99.380 1690200 0.000 98.250 100.380
+80 859 100.750 1330300 0.000 99.380 101.630
+80 860 105.375 1866500 0.000 101.750 105.375
+80 861 107.625 2658600 0.000 104.625 109.000
+80 862 107.750 1918300 0.000 106.500 108.750
+80 863 106.750 1270700 0.000 106.625 108.250
+81 864 111.500 3389800 0.000 111.250 116.000
+81 865 112.500 1909400 0.000 110.750 112.875
+81 866 113.380 1173800 0.000 112.130 113.750
+81 867 112.875 997900 0.000 112.375 113.125
+81 868 114.375 1471900 0.000 112.625 114.500
+81 869 112.880 1093100 0.000 112.880 114.630
+81 870 111.250 1435200 0.000 111.000 112.500
+81 871 111.500 954900 0.000 110.625 111.750
+81 872 113.500 1617100 0.000 111.500 113.500
+81 873 112.750 2463900 0.000 112.130 114.000
+81 874 113.750 1896000 0.000 112.250 114.630
+81 875 113.875 2084600 0.000 113.375 114.375
+81 876 113.880 341300 0.000 113.750 114.250
+81 877 113.500 396800 0.000 113.500 114.250
+81 878 113.630 714100 0.000 113.000 113.880
+81 879 113.375 728200 0.000 113.250 113.875
+81 880 113.000 461300 0.000 112.880 113.500
+81 881 112.125 1037700 0.000 112.125 113.750
+81 882 112.500 1307400 0.000 112.250 113.880
+81 883 112.130 1085100 0.000 111.880 113.000
+81 884 110.250 1189400 0.000 110.000 111.875
+81 885 109.000 1715500 0.000 108.750 110.375
+81 886 106.880 2078000 0.000 106.750 110.750
+81 887 108.380 962000 0.000 107.250 108.380
+81 888 108.130 608700 0.000 107.750 108.250
+81 889 106.750 1746500 0.000 105.500 107.130
+81 890 107.500 1168100 0.000 106.750 107.750
+81 891 109.130 1582000 0.000 106.630 109.250
+81 892 115.750 4330400 0.000 114.250 116.130
+81 893 117.625 4592500 0.000 114.875 118.250
+82 894 118.000 1963700 0.000 117.750 119.625
+82 895 119.250 2211000 0.000 117.875 120.125
+82 896 121.000 2841200 0.000 119.880 121.630
+82 897 122.625 2441100 0.000 120.500 122.625
+82 898 124.250 2478400 0.000 122.380 125.000
+82 899 124.500 1949200 0.000 123.000 125.380
+82 900 127.000 2524300 0.000 124.630 127.000
+82 901 126.750 1718300 0.000 126.250 127.130
+82 902 126.880 1919100 0.000 125.630 127.630
+83 903 128.500 2532000 0.000 128.380 131.000
+83 904 129.500 1751700 0.000 128.250 129.875
+83 905 132.375 1865300 0.000 129.625 132.500
+83 906 132.750 2372400 0.000 131.500 133.875
+83 907 134.630 1923800 0.000 132.250 135.000
+83 908 135.000 2485400 0.000 133.880 136.250
+83 909 137.375 2738900 0.000 135.375 137.750
+83 910 139.500 2278100 0.000 137.000 139.750
+83 911 137.880 1854200 0.000 137.880 138.750
+84 912 110.630 2214700 0.000 110.130 111.880
+84 913 108.500 3555500 0.000 108.130 111.000
+84 914 106.875 3026800 0.000 105.625 108.375
+84 915 109.250 2086700 0.000 106.750 109.250
+84 916 109.880 2089000 0.000 109.250 110.750
+84 917 109.750 1463800 0.000 109.375 110.750
+84 918 109.500 2109300 0.000 109.000 110.250
+84 919 108.500 1570400 0.000 108.125 109.375
+85 920 98.000 1438800 0.000 97.630 99.380
+85 921 97.250 1909500 0.000 96.750 98.250
+85 922 99.750 1417200 0.000 97.000 99.875
+85 923 99.880 893600 0.000 99.500 100.500
+85 924 101.250 791000 0.000 99.125 101.625
+85 925 104.250 3033600 0.000 99.750 104.750
+85 926 101.625 2025900 0.000 101.375 103.750
+85 927 99.875 1631900 0.000 99.375 101.250
+85 928 100.380 1315700 0.000 99.630 100.380
+86 929 94.625 1973000 92.000 91.875 94.750
+86 930 92.380 2515900 94.000 92.250 95.000
+86 931 91.250 2040100 92.625 91.250 94.250
+86 932 90.875 1611500 91.375 90.625 92.500
+86 933 90.250 1333300 90.750 89.750 90.875
+86 934 92.375 1338400 90.375 90.250 92.500
+86 935 95.380 3103900 94.000 93.500 95.750
+86 936 95.500 2253200 95.125 95.000 97.125
+86 937 96.375 3582000 95.000 92.625 98.125
+86 938 95.250 1209500 96.250 95.250 96.375
+86 939 92.880 1426400 95.250 92.880 95.500
+86 940 95.630 1273400 92.880 92.880 95.630
+87 941 92.250 1502600 91.125 90.000 92.250
+87 942 91.250 1632500 91.750 91.000 92.130
+87 943 89.880 1401700 90.380 89.630 90.880
+87 944 89.250 1724200 90.000 88.625 90.375
+87 945 89.750 945800 89.625 89.125 89.875
+87 946 90.250 1130700 89.750 89.750 90.875
+87 947 92.125 1581500 90.500 89.875 92.375
+87 948 90.750 1954000 92.750 90.130 93.250
+87 949 89.750 1295100 90.500 89.500 91.130
+87 950 89.625 1314500 90.000 89.125 91.125
+87 951 89.630 1148400 90.000 89.380 90.750
+87 952 89.750 1363200 89.750 89.125 90.625
+87 953 89.875 1446100 89.750 89.250 90.375
+87 954 89.380 1056500 89.880 88.750 90.250
+88 955 82.380 1926400 83.380 82.250 84.880
+88 956 84.875 3020100 82.625 82.625 85.000
+88 957 85.125 2275600 85.375 84.625 85.875
+88 958 86.000 2052900 85.630 84.500 86.130
+88 959 87.500 1712300 86.130 85.880 87.500
+88 960 88.250 3316600 89.500 88.125 89.750
+88 961 88.500 2215600 88.750 87.625 88.875
+88 962 88.625 2063100 88.500 87.500 88.625
+88 963 89.500 1792800 88.500 88.250 89.750
+88 964 90.000 2117200 89.630 89.500 90.250
+88 965 90.125 1324900 89.875 89.625 90.125
+88 966 89.250 1475400 90.000 89.000 90.630
+89 967 89.250 1475400 90.000 89.000 90.630
+89 968 88.130 993100 89.130 88.130 89.250
+89 969 88.125 1000900 87.875 87.750 88.750
+89 970 88.500 1108900 88.130 87.380 88.500
+89 971 88.130 1268600 88.880 87.750 88.880
+89 972 90.750 1681900 88.500 88.250 90.750
+89 973 90.625 2074400 91.250 90.375 92.125
+89 974 92.500 2341200 92.500 92.125 93.000
+89 975 93.875 2044900 92.500 92.375 94.000
+90 976 62.880 2541800 62.380 62.380 63.380
+90 977 56.130 12193500 0.000 56.000 59.880
+90 978 51.875 13421200 53.625 50.750 54.875
+90 979 53.000 8025400 0.000 51.250 53.380
+90 980 51.375 7658000 53.125 50.250 53.250
+90 981 48.875 7384600 51.250 48.750 51.625
+90 982 51.750 6115700 50.000 49.000 52.000
+90 983 51.250 4186200 51.625 51.000 52.625
+91 984 46.380 3638900 47.000 45.880 47.000
+91 985 48.625 3420500 46.500 46.500 48.750
+91 986 48.875 3054500 48.750 47.500 49.250
+91 987 49.000 7802400 52.875 48.750 53.250
+91 988 49.625 3307500 49.125 48.250 50.000
+91 989 50.250 3095700 50.000 49.750 50.875
+91 990 51.500 2981500 51.000 50.875 51.625
+91 991 52.625 3039500 52.000 52.000 52.625
+91 992 52.125 2635400 52.250 52.000 53.000
+92 993 42.250 2494200 43.625 42.000 43.625
+92 994 42.375 3922300 41.875 41.125 42.375
+92 995 45.630 6260300 44.880 44.500 45.880
+92 996 44.000 4216700 46.000 43.875 46.375
+93 997 51.875 2331700 52.250 51.500 52.625
+93 998 52.000 2595300 51.875 51.625 52.500
+93 999 53.125 2067200 52.375 51.875 53.250
+93 1000 55.125 4184000 54.000 53.750 55.250
+93 1001 55.750 1211000 55.500 55.125 56.000
+93 1002 54.380 2487900 56.000 54.380 56.130
+93 1003 53.875 1976800 54.500 53.750 55.125
+93 1004 53.130 2385300 54.750 53.130 54.750
+93 1005 53.750 1865800 53.250 53.250 54.125
+94 1006 59.000 1846800 58.130 58.000 59.130
+94 1007 59.500 2570000 59.000 58.500 59.875
+94 1008 58.500 2379800 60.000 58.500 60.000
+94 1009 58.875 1458600 58.500 58.000 59.375
+94 1010 59.250 1734900 58.875 58.500 59.500
+94 1011 58.630 1690600 59.130 58.500 59.380
+94 1012 58.130 1503100 58.750 58.130 58.880
+94 1013 58.750 1804200 58.250 58.250 59.000
+94 1014 58.630 1586000 58.750 58.380 59.500
+94 1015 57.500 1528100 58.500 57.130 58.500
+94 1016 57.125 2255900 57.375 56.375 57.375
+94 1017 56.000 1959500 56.750 55.750 57.000
+94 1018 55.250 2278500 55.875 55.125 55.875
+94 1019 55.250 3176600 55.125 54.750 55.625
+94 1020 58.630 4219300 55.750 55.750 58.750
+94 1021 58.250 10154900 58.880 55.380 59.380
+95 1022 52.880 1565600 53.380 52.750 53.630
+95 1023 53.250 1985500 52.500 52.000 53.380
+95 1024 54.000 1873700 53.375 53.375 54.625
+95 1025 54.500 1727400 54.380 54.000 54.630
+95 1026 54.625 1495700 55.250 54.500 55.250
+95 1027 52.750 2290200 54.880 52.500 55.000
+96 1028 53.380 2097500 53.500 51.380 53.750
+96 1029 52.250 2207700 53.250 51.750 53.500
+96 1030 58.380 8758100 55.000 54.000 58.630
+96 1031 58.750 5660500 58.375 58.000 59.500
+97 1032 58.500 2217200 58.880 58.500 59.250
+97 1033 57.125 2297500 57.875 56.750 58.250
+97 1034 57.500 1908600 57.375 56.875 57.875
+97 1035 58.000 1608500 57.500 56.875 58.375
+97 1036 58.380 1376600 58.130 57.880 58.630
+97 1037 57.750 1500300 58.250 57.500 58.250
+98 1038 55.625 1817200 56.750 55.625 56.875
+98 1039 55.880 3189300 55.750 54.500 56.250
+98 1040 62.380 8807500 59.000 58.000 62.630
+98 1041 61.380 3714100 62.250 61.130 62.380
+99 1042 72.250 2341600 73.625 72.250 74.125
+99 1043 73.125 2040800 72.500 71.875 73.125
+99 1044 73.250 1536400 72.880 72.750 73.750
+99 1045 74.375 2463400 73.500 73.375 74.500
+99 1046 75.380 3933900 74.000 73.750 75.750
+99 1047 75.000 5216900 75.500 73.875 76.125
+99 1048 74.625 3372500 74.625 74.125 75.375
+99 1049 73.000 2615200 74.625 72.875 74.625
+99 1050 73.880 2829600 72.630 72.630 74.380
+100 1051 71.750 1418500 72.750 71.500 73.000
+100 1052 72.130 2742400 72.000 70.250 72.880
+100 1053 73.625 3292000 72.375 72.250 73.875
+100 1054 74.380 1458600 73.630 73.500 74.380
+100 1055 74.625 2562800 74.875 74.500 75.250
+100 1056 74.380 1352100 74.380 74.130 74.880
+100 1057 74.250 1450700 74.250 73.875 74.375
+100 1058 74.250 1496400 74.125 74.125 74.875
+101 1059 87.130 2576400 87.880 86.000 88.250
+101 1060 89.000 5977700 90.500 88.630 90.500
+101 1061 91.625 5886400 89.375 89.125 92.000
+101 1062 93.500 4318900 91.500 91.000 93.880
+101 1063 93.625 3346300 93.125 92.875 93.875
+101 1064 95.250 3694500 93.125 92.875 95.625
+102 1065 89.375 3394000 90.125 88.375 90.750
+102 1066 89.130 3308400 89.630 88.630 90.250
+102 1067 90.250 2995700 90.500 89.750 90.750
+102 1068 91.750 2905200 91.380 90.880 92.250
+102 1069 93.000 2562500 91.875 91.875 93.375
+102 1070 93.000 2192900 93.630 93.000 94.000
+103 1071 109.000 3285600 106.500 105.625 109.500
+103 1072 108.625 1257200 109.125 108.500 109.750
+103 1073 108.875 1430800 109.125 108.625 109.375
+103 1074 109.630 1795300 108.880 108.880 110.000
+103 1075 109.875 1966800 110.000 109.625 110.375
+103 1076 109.125 1971400 109.875 108.875 110.375
+103 1077 109.250 2327700 109.500 108.380 109.630
+103 1078 110.750 1748300 109.250 109.250 110.875
+103 1079 112.000 2701000 111.000 110.250 112.125
+103 1080 112.375 2946800 112.125 110.750 112.750
+103 1081 113.625 3029700 112.375 112.250 114.625
+103 1082 111.375 3698100 113.875 111.125 114.000
+103 1083 106.750 4700700 111.380 106.500 111.380
+103 1084 107.630 5514300 107.000 105.630 108.250
+103 1085 106.625 3239000 108.125 106.500 108.500
+104 1086 91.880 3744400 89.130 89.130 91.880
+104 1087 89.375 3605800 92.000 89.125 92.000
+104 1088 90.875 2674000 89.875 89.750 91.125
+104 1089 91.250 1877400 90.880 90.880 91.880
+104 1090 91.750 945400 92.000 91.625 92.125
+104 1091 92.000 1572000 91.750 90.750 92.000
+105 1092 87.250 4624600 88.130 86.000 88.500
+105 1093 86.375 2664400 87.250 86.125 87.250
+105 1094 83.125 3742800 86.500 83.125 86.500
+105 1095 87.000 4613000 84.125 83.500 87.750
+105 1096 87.625 4821100 86.750 85.750 89.250
+105 1097 96.250 9856900 90.000 89.625 96.250
+105 1098 102.000 13145900 96.625 96.500 104.125
+105 1099 102.250 5293000 101.500 101.375 103.875
+105 1100 103.000 4959100 100.750 100.750 104.500
+105 1101 107.000 7989000 103.130 103.130 109.000
+105 1102 104.125 4721800 107.625 104.125 108.000
+106 1103 113.625 3467900 114.000 113.500 114.875
+106 1104 114.250 3237400 113.880 113.750 115.000
+106 1105 117.625 5835300 114.375 114.250 118.500
+106 1106 118.000 4985900 118.000 117.500 118.880
+106 1107 119.125 5322200 116.750 116.750 120.875
+106 1108 120.250 4097300 119.000 118.630 120.630
+106 1109 124.125 5355800 121.750 121.250 125.625
+106 1110 125.630 4126200 125.000 124.000 126.130
+106 1111 124.880 2882500 123.880 123.880 125.500
+107 1112 108.750 4722900 114.875 108.375 115.125
+107 1113 110.750 4558900 108.375 107.625 112.000
+107 1114 111.250 3619800 111.130 110.250 113.750
+107 1115 109.750 3046600 111.875 109.500 112.375
+107 1116 111.250 2931600 110.380 110.380 113.750
+107 1117 110.380 2379500 112.130 110.000 112.750
+107 1118 117.625 5407700 111.000 110.500 118.125
+107 1119 119.750 4149900 117.500 116.130 120.500
+108 1120 91.750 3683600 89.630 89.130 92.630
+108 1121 103.630 9014900 96.000 95.630 103.750
+108 1122 103.875 3990400 103.125 101.625 104.125
+108 1123 104.750 3338200 103.500 103.380 105.500
+108 1124 107.380 3992800 105.250 103.750 108.000
+108 1125 107.500 3505300 107.125 107.000 108.875
+108 1126 107.250 3120600 108.000 107.250 109.000
+108 1127 108.875 2265800 108.375 107.750 109.375
+108 1128 109.250 2151100 108.000 108.000 109.375
+109 1129 124.750 1312200 124.750 124.000 125.500
+109 1130 124.500 1864400 124.875 124.125 127.125
+109 1131 123.750 2446500 124.000 123.130 125.250
+109 1132 125.880 2013200 124.250 124.000 125.880
+109 1133 125.000 1845100 125.750 124.625 126.250
+109 1134 126.625 2302300 125.875 125.625 127.125
+109 1135 127.875 1835400 126.875 126.875 128.000
+109 1136 128.380 3148000 128.000 127.630 129.500
+109 1137 126.250 2402400 129.000 126.130 129.380
+109 1138 127.750 2248500 125.500 125.500 128.000
+109 1139 129.875 2858300 128.500 128.250 130.125
+109 1140 130.000 1400900 129.880 129.130 130.000
+109 1141 129.500 2863900 130.750 128.630 131.250
+109 1142 127.625 1875700 129.500 127.375 129.500
+109 1143 125.625 2135400 128.750 125.250 129.125
+109 1144 129.375 3473700 125.500 124.000 129.500
+110 1145 133.375 2886400 130.625 130.250 133.875
+110 1146 133.750 3481600 133.125 132.750 135.125
+110 1147 134.375 1569400 133.375 132.875 134.500
+110 1148 134.500 1429200 134.375 134.000 134.875
+110 1149 133.250 2724100 135.000 133.000 135.750
+110 1150 134.750 2392300 133.500 133.250 135.875
+110 1151 136.880 2774500 134.630 134.630 137.630
+110 1152 145.000 7803800 137.630 137.380 145.500
+110 1153 146.750 5777100 144.750 143.000 150.000
+111 1154 157.625 2833000 158.125 156.125 158.500
+111 1155 158.000 5515400 157.500 157.000 160.750
+111 1156 158.125 2799700 158.250 157.750 160.250
+111 1157 159.375 931300 158.125 158.125 159.625
+111 1158 163.000 3316000 159.630 159.630 163.500
+111 1159 162.625 4677800 163.625 162.625 166.000
+111 1160 162.000 4437400 162.875 159.125 164.000
+111 1161 158.500 3036300 161.375 158.375 162.625
+111 1162 155.625 6051900 151.000 151.000 158.000
+111 1163 160.000 2769900 156.630 156.250 160.130
+112 1164 141.250 3583000 143.130 140.250 143.500
+112 1165 137.625 6308500 140.750 137.125 140.875
+112 1166 143.875 4282700 137.625 0.000 144.000
+112 1167 146.500 4198400 144.380 144.130 148.000
+112 1168 146.875 3670400 147.000 144.000 147.500
+112 1169 143.000 1772200 146.880 143.000 146.880
+113 1170 136.500 2634400 134.500 133.250 136.880
+113 1171 137.880 3931900 138.630 134.380 139.000
+113 1172 137.500 1845300 137.380 135.750 138.380
+113 1173 137.750 2655100 137.375 136.750 139.750
+113 1174 139.750 2343200 138.000 137.750 140.130
+113 1175 137.380 2356700 139.630 136.630 141.250
+113 1176 140.000 3022200 137.250 134.750 140.130
+113 1177 142.380 3917100 140.000 139.000 143.750
+113 1178 153.625 10659100 150.500 149.500 157.000
+113 1179 150.750 3005000 152.250 150.625 152.875
+114 1180 98.625 2949600 102.437 98.625 104.125
+114 1181 99.130 2060000 100.130 99.000 100.810
+114 1182 101.690 1069100 99.380 99.250 101.750
+114 1183 102.750 2307300 102.875 102.750 103.687
+114 1184 103.130 3558000 103.690 102.940 105.500
+114 1185 104.625 2736500 104.000 103.187 105.187
+114 1186 105.625 2635000 104.500 104.062 105.750
+114 1187 106.437 5017000 105.562 105.000 106.812
+114 1188 105.250 3555600 105.375 105.062 107.187
+114 1189 104.250 4308200 105.000 102.625 105.000
+114 1190 104.190 4059400 103.500 102.940 105.500
+114 1191 100.062 7182800 104.312 99.375 104.875
+114 1192 100.130 6410700 98.250 97.500 101.130
+114 1193 102.130 3414500 100.560 100.310 102.440
+114 1194 102.625 4275400 103.500 101.250 104.000
+114 1195 103.437 6321700 103.187 102.375 104.937
+114 1196 105.000 4189100 104.875 104.500 105.187
+114 1197 108.380 4970600 105.190 104.690 108.380
+114 1198 100.125 13907800 100.250 99.625 102.000
+114 1199 99.380 6583500 99.810 98.500 100.630
+114 1200 99.187 3911500 99.812 98.937 100.000
+114 1201 98.125 2807900 99.250 98.000 99.937
+114 1202 96.500 7019700 96.750 95.630 97.880
+114 1203 97.000 5192400 97.130 96.500 98.000
+114 1204 98.187 4413600 97.000 97.000 99.312
+114 1205 98.750 2900100 98.312 98.000 99.562
+114 1206 100.562 3437700 100.000 100.000 101.250
+114 1207 99.380 3585600 100.130 98.690 100.250
+115 1208 98.125 5205100 99.125 98.125 99.812
+115 1209 96.125 6464200 97.125 95.875 98.250
+115 1210 97.500 4167100 97.380 96.690 98.190
+115 1211 99.130 3403400 97.880 97.440 99.880
+115 1212 100.250 2951700 99.187 98.187 100.625
+115 1213 99.630 2925300 100.880 99.000 101.130
+115 1214 101.250 3420700 99.690 99.250 101.380
+115 1215 100.880 3585100 101.310 99.560 101.810
+115 1216 101.560 3594700 100.630 100.560 102.310
+115 1217 102.875 2585000 101.687 101.437 103.000
+115 1218 102.062 4107800 103.000 101.437 103.250
+116 1219 108.812 4240700 109.000 107.625 109.125
+116 1220 106.130 4884000 108.750 106.000 109.250
+116 1221 108.130 4276900 106.250 106.190 109.500
+116 1222 111.750 4039200 110.000 109.940 112.310
+116 1223 112.000 3030000 111.880 110.060 112.130
+116 1224 112.440 3187800 112.500 112.000 114.690
+116 1225 113.000 1636400 112.380 112.310 113.440
+116 1226 114.125 2184600 114.125 113.562 114.812
+116 1227 114.810 4356000 114.130 113.500 116.310
+116 1228 116.750 3166400 116.000 115.500 116.937
+116 1229 115.190 3219800 116.560 114.500 116.630
+117 1230 115.000 2258200 113.750 113.630 115.560
+117 1231 117.062 3189500 114.750 114.625 118.500
+117 1232 118.500 3070100 118.250 117.375 119.250
+117 1233 119.380 2339800 118.500 118.250 120.000
+117 1234 119.625 2268600 119.750 119.000 120.625
+117 1235 117.000 2557800 119.250 117.000 120.000
+117 1236 118.380 2180100 117.130 116.000 118.940
+117 1237 120.187 3168400 118.562 118.500 120.375
+117 1238 122.000 4489200 121.000 120.312 123.000
+117 1239 128.130 12308600 128.250 127.750 131.000
+117 1240 127.440 6049900 126.000 124.500 128.440
+117 1241 123.880 4153100 127.190 123.690 129.000
+117 1242 124.250 2874600 125.875 122.500 125.937
+118 1243 123.500 4994700 125.750 123.375 127.437
+118 1244 122.500 5579800 122.130 118.190 122.500
+118 1245 126.562 4965100 121.375 120.625 126.562
+118 1246 129.130 4557100 128.000 127.630 129.940
+118 1247 130.000 3246800 129.000 128.500 130.687
+118 1248 130.500 3319400 130.000 128.190 130.940
+119 1249 133.625 3820400 134.500 132.187 136.187
+119 1250 131.437 3544900 133.750 130.000 134.125
+119 1251 128.500 4149700 130.750 127.630 131.500
+119 1252 125.375 6268700 125.062 123.375 126.437
+119 1253 124.810 7302600 124.130 118.940 125.250
+119 1254 120.250 6721000 123.750 117.310 123.750
+119 1255 119.250 7183700 124.000 118.750 124.000
+119 1256 120.750 6582400 119.500 118.125 122.875
+119 1257 123.500 9156300 120.750 116.812 123.500
+119 1258 127.312 5841600 124.437 122.500 127.937
+119 1259 130.875 5402500 130.125 129.562 132.875
+119 1260 128.190 4696800 130.500 126.560 130.500
+120 1261 147.380 3456500 148.500 147.190 149.940
+120 1262 147.880 4096300 148.880 145.750 149.750
+120 1263 149.000 3293000 147.690 146.810 149.560
+120 1264 149.940 3569000 148.880 147.500 150.940
+120 1265 151.380 3788700 150.750 149.940 152.190
+120 1266 156.000 4371500 150.880 150.690 156.750
+120 1267 157.125 5109100 157.000 156.500 158.875
+120 1268 157.880 4333900 156.690 156.630 159.880
+120 1269 157.437 2997600 157.000 155.750 157.500
+121 1270 165.000 3885800 163.500 162.500 165.810
+121 1271 164.375 2826400 166.875 164.312 166.875
+121 1272 166.062 4381600 165.437 165.062 169.625
+121 1273 171.560 4908400 166.630 166.630 172.190
+121 1274 176.375 3739300 171.562 171.562 178.937
+121 1275 182.250 4352300 177.500 175.250 183.000
+121 1276 185.000 3535000 182.690 181.130 185.380
+121 1277 187.937 1524900 184.750 184.062 187.937
+121 1278 189.250 2634600 186.500 186.000 189.937
+121 1279 187.125 1881100 188.625 187.000 188.937
+121 1280 186.750 2406600 186.875 186.312 188.625
+121 1281 184.375 1932400 186.750 183.500 187.187
+121 1282 183.000 4074800 185.000 181.500 186.500
+121 1283 189.630 4953800 183.000 182.810 189.880
+122 1284 177.937 4600100 172.250 171.062 177.937
+122 1285 176.940 4554600 178.000 175.750 180.000
+122 1286 173.750 4318700 177.250 173.130 179.690
+122 1287 173.625 5234800 172.000 169.312 173.937
+122 1288 169.750 7399000 171.000 166.500 171.880
+122 1289 168.375 4795400 169.500 165.125 169.500
+122 1290 167.750 4371200 170.500 166.880 170.940
+122 1291 166.750 3871500 168.500 165.062 168.937
+122 1292 171.000 10750500 173.500 170.125 177.000
+122 1293 178.380 5803200 173.880 173.750 178.940
+122 1294 178.875 3779900 178.625 177.062 180.000
+122 1295 182.190 5009500 179.880 178.560 184.880
+122 1296 181.500 3504900 181.630 180.250 182.880
+123 1297 182.000 3951400 179.187 177.125 182.312
+123 1298 181.000 3303000 182.000 180.190 183.500
+123 1299 178.060 3452700 179.500 178.000 180.000
+123 1300 177.630 7252500 177.500 172.940 177.750
+123 1301 168.562 9748400 176.625 168.000 178.000
+123 1302 167.000 5948300 168.687 166.625 170.500
+123 1303 165.380 5007500 167.000 164.000 167.630
+123 1304 169.500 5910500 168.000 166.500 170.500
+123 1305 171.312 4914400 172.000 169.500 173.250
+123 1306 172.380 4133200 171.060 170.560 175.880
+123 1307 178.562 3733400 177.000 176.875 181.250
+123 1308 177.250 2917600 180.000 176.500 181.500
+124 1309 187.000 4265500 186.000 182.312 188.062
+124 1310 186.310 3055900 185.630 184.000 188.060
+124 1311 183.440 4489900 183.500 181.060 183.630
+124 1312 180.000 3423200 184.000 179.940 184.000
+124 1313 179.380 3630500 182.750 177.630 183.880
+124 1314 177.750 4165200 179.500 173.500 179.500
+124 1315 170.375 5964400 177.500 170.125 177.500
+124 1316 166.750 7691800 170.880 164.060 172.310
+124 1317 169.750 7358000 167.000 163.000 169.750
+124 1318 171.875 6580800 169.750 165.125 171.875
+124 1319 194.500 19584900 197.250 192.687 198.750
+124 1320 199.750 10375500 193.380 192.940 206.560
+124 1321 209.880 9333000 204.750 202.310 210.750
+124 1322 212.000 10464600 212.000 208.500 215.250
+124 1323 205.000 5670900 209.250 203.500 210.500
+125 1324 209.250 4169800 211.880 208.000 214.000
+125 1325 217.250 4993400 211.250 211.062 217.500
+125 1326 218.630 4774200 217.250 217.060 221.880
+125 1327 221.000 4186400 220.500 217.500 221.875
+125 1328 225.500 6033400 221.125 215.500 228.437
+125 1329 246.000 12165700 234.000 234.000 246.000
+125 1330 239.250 9036900 241.500 236.000 243.500
+125 1331 237.500 5780200 236.000 232.000 239.625
+125 1332 238.500 4316900 238.250 237.000 240.625
+125 1333 235.875 3895800 238.250 233.062 238.750
+125 1334 232.875 3220900 235.625 232.500 236.375
+125 1335 230.380 3452300 232.750 229.190 233.380
+125 1336 223.750 4299100 230.310 221.750 230.690
+125 1337 221.187 4702500 222.500 221.000 226.000
+125 1338 236.250 8314000 223.000 221.440 236.630
+125 1339 116.000 10552600 116.687 112.625 116.875
+125 1340 116.000 6379500 116.000 114.187 116.750
+126 1341 124.440 6043500 122.750 122.310 124.880
+126 1342 122.000 8442100 123.375 121.000 123.625
+126 1343 122.380 6463000 122.000 120.880 124.060
+126 1344 122.940 6245300 122.630 122.060 125.880
+126 1345 124.000 4354600 125.000 122.750 125.190
+126 1346 123.187 4773000 124.250 122.687 125.625
+127 1347 98.000 18720400 94.000 93.562 98.250
+127 1348 104.750 19673300 99.000 98.380 105.000
+127 1349 107.880 13681500 105.500 104.440 108.440
+127 1350 106.125 10665000 108.812 106.000 109.875
+127 1351 104.060 9710300 105.000 101.810 105.000
+127 1352 105.000 3108400 105.937 104.125 106.000
+127 1353 104.187 5677700 104.937 103.375 104.937
+127 1354 103.060 5719900 103.690 102.130 104.500
+127 1355 103.750 5336000 102.562 102.250 104.437
+127 1356 105.560 6208600 103.440 103.380 106.310
+127 1357 112.000 14679900 109.812 107.937 112.875
+127 1358 116.000 9924400 113.000 112.500 116.500
+128 1359 109.190 7117300 108.000 105.750 109.690
+128 1360 109.125 7680700 110.687 108.625 111.062
+128 1361 108.060 4834900 109.060 107.750 110.440
+128 1362 110.125 4773500 108.500 108.062 110.125
+128 1363 108.375 4005500 109.875 108.250 110.312
+128 1364 108.625 4528700 109.125 108.187 110.437
+128 1365 109.750 3740700 109.687 108.125 110.000
+128 1366 109.250 4081300 109.560 109.060 110.750
+128 1367 109.060 2683100 110.440 108.750 110.500
+128 1368 108.750 3435100 109.690 108.560 110.500
+128 1369 107.875 2870300 109.187 106.625 109.500
+128 1370 115.625 10346800 112.437 111.875 115.812
+128 1371 112.060 8227800 114.000 110.880 114.500
+129 1372 107.000 5665900 107.500 106.130 108.250
+129 1373 109.000 6622500 109.000 107.880 109.810
+129 1374 110.000 8856400 108.250 107.687 111.687
+129 1375 112.750 6563500 110.000 109.940 113.380
+129 1376 113.500 6771400 112.810 111.940 114.690
+129 1377 114.250 6137800 114.500 113.875 115.375
+129 1378 115.250 6492400 115.875 111.625 115.875
+129 1379 121.500 12124300 115.375 115.062 122.250
+129 1380 126.880 11862900 125.000 124.630 128.250
+129 1381 122.500 8163000 125.750 122.060 127.000
+130 1382 102.940 6402800 104.130 102.630 104.810
+130 1383 103.500 7428100 102.380 101.000 104.380
+130 1384 117.000 22927900 112.000 111.750 117.812
+130 1385 114.440 8764700 117.000 113.750 117.000
+131 1386 90.130 7705300 90.190 89.810 94.440
+131 1387 86.000 12579500 85.880 84.810 88.000
+131 1388 81.560 12724900 85.500 80.060 87.940
+131 1389 89.000 9514000 84.500 84.500 89.375
+131 1390 84.810 6052300 89.000 84.250 89.560
+132 1391 92.750 5671800 93.750 91.810 94.000
+132 1392 96.687 9712900 95.375 94.312 97.750
+132 1393 108.312 25243300 104.375 103.500 110.000
+132 1394 111.250 14760400 107.500 107.250 113.937
+133 1395 106.470 6793100 107.170 105.370 107.340
+133 1396 99.290 16688100 105.000 97.905 105.010
+133 1397 95.490 14494800 97.900 95.020 98.400
+133 1398 98.390 12127900 95.100 94.200 98.440
+133 1399 94.960 14246600 95.400 93.340 98.900
+134 1400 99.700 9957300 97.000 97.000 99.880
+134 1401 106.500 19849500 103.000 102.300 110.000
+134 1402 114.470 25822400 112.000 110.450 115.900
+134 1403 114.830 12759100 114.350 113.750 116.400
+135 1404 119.600 7726900 117.500 117.500 119.600
+135 1405 117.800 5629100 119.520 117.760 119.900
+135 1406 115.270 5818000 117.350 115.080 117.590
+135 1407 112.650 9598700 114.000 112.000 114.100
+135 1408 111.800 6757600 112.650 111.640 113.860
+135 1409 112.890 5138900 112.500 111.900 113.600
+135 1410 113.640 3812200 113.250 112.850 114.750
+135 1411 116.970 6154400 113.640 113.560 117.600
+135 1412 117.500 9625700 117.600 116.500 119.000
+135 1413 117.250 6423700 117.000 117.000 118.250
+135 1414 116.100 3099100 117.500 115.600 117.500
+135 1415 117.360 6542700 116.300 116.100 118.140
+136 1416 104.720 9312600 106.500 103.650 106.950
+136 1417 101.960 10266800 104.730 101.600 105.400
+136 1418 103.850 8894800 101.600 101.560 104.130
+136 1419 107.250 8767200 105.900 105.620 107.800
+136 1420 108.530 7841800 106.150 106.110 109.300
+136 1421 107.820 5330100 108.530 107.280 109.380
+137 1422 108.800 5675100 108.500 107.210 109.470
+137 1423 108.180 3704100 107.800 107.100 110.090
+137 1424 106.510 4176800 107.250 105.790 107.370
+137 1425 106.250 4028300 105.800 105.260 107.370
+137 1426 104.190 6398000 105.000 104.100 106.100
+137 1427 104.080 6763200 103.700 102.700 104.540
+137 1428 104.950 5254800 103.850 102.810 104.950
+137 1429 105.860 6024400 105.000 104.650 106.640
+137 1430 106.200 5730200 106.500 105.400 106.850
+137 1431 105.010 4713100 106.350 105.010 106.590
+138 1432 91.300 13783100 94.450 90.300 94.450
+138 1433 90.000 13172200 91.300 87.490 91.500
+138 1434 91.720 11077200 90.200 89.900 92.710
+138 1435 92.710 8676200 91.720 91.340 93.480
+138 1436 93.770 7221200 92.300 92.100 93.900
+138 1437 96.950 11118800 92.900 92.400 97.620
+138 1438 97.310 9785100 97.150 96.800 98.880
+138 1439 98.020 9733000 96.650 95.370 98.450
+138 1440 98.500 7093400 98.030 96.750 99.000
+138 1441 97.140 7525300 98.500 96.760 98.500
+139 1442 123.200 7950400 121.120 120.060 123.210
+139 1443 120.250 8061000 121.550 120.120 122.300
+139 1444 121.100 7036000 120.800 119.410 121.480
+139 1445 121.340 6225500 120.150 120.150 122.140
+139 1446 122.200 5596200 121.510 121.350 122.970
+139 1447 123.890 8054200 121.900 121.010 124.700
+140 1448 75.490 7953000 77.850 75.200 78.250
+140 1449 74.650 13296100 75.400 73.250 75.950
+140 1450 75.600 8909700 75.450 75.300 77.400
+140 1451 76.170 8592000 74.200 73.250 76.770
+140 1452 77.140 7775800 76.180 75.160 77.750
+140 1453 75.940 7240900 75.600 75.470 76.900
+141 1454 72.000 10945600 71.350 71.250 73.620
+141 1455 67.600 12056000 72.010 67.160 72.410
+141 1456 68.580 12012900 67.850 67.700 69.400
+141 1457 70.510 8469900 67.950 67.870 70.710
+141 1458 73.500 5243000 72.450 72.000 73.900
+141 1459 71.300 9488100 73.000 71.030 73.480
+141 1460 69.670 11165100 71.400 69.450 72.250
+141 1461 68.760 10073400 70.600 68.530 71.250
+141 1462 69.410 11034600 68.000 67.550 69.750
+142 1463 69.350 10989800 68.900 67.300 70.000
+142 1464 66.400 11576400 68.750 65.760 69.470
+142 1465 71.180 11918600 70.370 68.000 71.400
+142 1466 71.790 10136400 70.500 70.020 72.700
+142 1467 70.400 12708100 70.750 69.110 71.600
+142 1468 68.250 8832000 70.400 67.220 70.700
+142 1469 67.880 6915000 68.210 67.123 68.230
+142 1470 65.990 7126900 67.940 65.850 68.350
+142 1471 67.900 8370800 67.000 66.610 69.420
+142 1472 69.170 8036800 69.000 66.800 69.900
+142 1473 71.610 8432500 69.200 68.350 72.050
+142 1474 71.830 6940900 70.610 70.400 74.300
+142 1475 71.770 5263300 71.050 70.610 72.640
+143 1476 55.070 12155800 56.050 54.810 56.700
+143 1477 57.580 12672600 54.650 54.010 58.480
+143 1478 63.920 17852500 62.000 61.500 63.920
+143 1479 63.420 7925300 61.540 61.540 63.800
+143 1480 68.480 14600200 67.750 66.580 68.480
+143 1481 64.900 15828900 66.600 64.240 67.005
+143 1482 72.200 21344500 72.800 71.230 73.000
+143 1483 74.250 12532700 71.730 70.260 74.250
+144 1484 72.100 10570000 75.500 71.750 75.550
+144 1485 74.560 7538800 72.500 72.160 74.620
+144 1486 76.560 12544800 75.150 74.560 77.500
+144 1487 76.740 11648500 76.560 74.200 77.050
+144 1488 78.670 12865800 77.000 76.020 79.400
+144 1489 78.940 9844300 78.670 78.500 79.790
+144 1490 80.400 9395200 78.900 78.320 80.500
+144 1491 82.500 12005100 82.000 81.700 83.810
+144 1492 81.680 10466700 81.750 80.470 82.010
+145 1493 80.260 5922700 79.750 79.440 81.507
+145 1494 79.760 2420200 80.200 79.760 81.000
+145 1495 78.500 5784300 79.810 78.260 80.700
+145 1496 77.360 6334400 78.500 76.610 79.180
+145 1497 76.250 8066600 77.000 75.600 77.430
+145 1498 77.500 7871400 77.100 77.100 78.460
+145 1499 80.570 7864500 78.800 78.190 80.570
+145 1500 81.650 5963400 80.700 80.210 81.650
+145 1501 83.590 7923300 81.900 81.810 84.590
+145 1502 86.000 11907000 83.950 83.750 86.180
+145 1503 84.190 9509700 85.550 84.070 85.690
+146 1504 76.500 5617700 77.500 76.500 78.090
+146 1505 75.860 11218600 76.100 74.310 76.350
+146 1506 77.450 8300100 76.400 75.350 77.450
+146 1507 79.330 7793000 78.020 77.910 79.510
+146 1508 79.510 5441300 79.080 78.826 79.700
+146 1509 79.040 6295600 79.570 78.710 80.050
+147 1510 79.850 7024700 81.130 79.800 81.330
+147 1511 79.750 7472200 79.700 78.730 80.680
+147 1512 80.690 5298500 79.690 79.380 80.820
+147 1513 80.880 4615600 81.050 80.350 81.270
+147 1514 81.020 4262200 80.870 80.280 81.500
+147 1515 81.510 4634200 81.450 80.650 81.540
+148 1516 89.700 8842500 91.450 89.010 91.510
+148 1517 90.310 5529700 89.900 89.750 90.460
+148 1518 94.020 19813600 95.070 93.550 95.650
+148 1519 95.320 9302100 95.000 94.710 95.350
+148 1520 97.100 9285300 96.000 95.730 97.440
+148 1521 97.700 6952700 97.230 96.640 98.040
+148 1522 97.510 4371600 97.840 97.320 98.160
+149 1523 99.300 3611700 100.060 99.300 100.300
+149 1524 99.710 3562400 99.100 99.080 100.090
+149 1525 99.370 3930500 99.990 99.320 100.000
+149 1526 98.420 4830500 99.310 98.150 99.770
+149 1527 97.800 5447700 98.420 97.520 99.230
+149 1528 97.310 5690200 98.600 97.190 98.600
+149 1529 95.960 6629600 97.400 95.460 97.510
+149 1530 96.790 7012100 95.200 95.200 97.460
+149 1531 96.540 3362500 96.500 96.230 97.090
+150 1532 96.840 4807000 96.570 95.600 96.890
+150 1533 96.390 3548500 96.580 96.130 96.920
+150 1534 96.450 3971800 95.950 95.560 96.980
+150 1535 94.590 5084200 96.490 94.590 96.880
+150 1536 94.530 6391400 94.300 93.770 95.280
+150 1537 93.060 6536400 94.380 92.680 94.740
+150 1538 91.210 8560800 92.000 91.150 92.980
+150 1539 93.300 6290600 92.000 91.680 93.380
+151 1540 85.350 5677300 85.300 85.050 85.940
+151 1541 83.650 7530100 85.000 83.580 85.010
+151 1542 83.890 6595200 84.400 83.510 84.500
+151 1543 84.950 6009200 84.000 83.420 85.250
+151 1544 85.250 6001500 85.900 85.200 86.090
+151 1545 84.130 6298700 84.840 83.780 85.240
+152 1546 83.690 5268500 83.700 83.100 83.980
+152 1547 82.210 7134900 83.050 81.900 83.050
+152 1548 83.910 5916900 82.540 82.510 83.940
+152 1549 84.020 4361500 83.700 83.510 84.560
+152 1550 84.040 3559500 84.100 83.660 84.540
+152 1551 85.130 4833700 83.600 83.570 85.130
+153 1552 84.160 4650300 84.100 83.980 84.440
+153 1553 84.480 3874200 84.350 83.880 84.650
+153 1554 84.980 4204600 84.480 84.150 84.980
+153 1555 85.740 5198000 85.140 85.010 85.980
+153 1556 86.720 4542700 85.950 85.880 86.980
+153 1557 87.160 5001400 87.000 86.720 88.100
+153 1558 87.320 5150800 87.950 87.130 88.030
+153 1559 88.040 3984400 87.140 87.100 88.100
+153 1560 87.420 3076900 88.040 87.400 88.100
+153 1561 86.710 4090000 87.430 86.510 87.910
+153 1562 86.630 3016300 86.770 86.270 87.200
+153 1563 86.000 4626600 86.020 85.580 86.200
+153 1564 84.980 6665200 86.260 84.430 86.480
+153 1565 84.780 4233700 84.750 84.300 84.980
+153 1566 84.850 5928500 84.780 84.600 85.250
+153 1567 85.920 7275600 84.300 84.290 86.150
+153 1568 89.370 13824500 88.200 88.000 89.730
+153 1569 88.820 7079800 88.450 88.290 89.190
+153 1570 88.100 6272100 88.400 87.660 88.760
+153 1571 87.390 5998900 88.220 87.290 88.450
+153 1572 88.430 5775000 87.360 87.310 88.900
+153 1573 89.000 7337300 88.330 88.250 89.570
+153 1574 90.000 6035100 88.580 88.500 90.270
+153 1575 89.500 4226500 89.800 89.430 90.240
+153 1576 89.750 4518500 89.400 88.950 89.900
+153 1577 90.110 5160900 89.330 89.230 90.600
+153 1578 90.470 5391100 89.550 89.500 91.220
+153 1579 91.200 6553300 91.250 90.970 91.900
+153 1580 92.380 6952100 91.050 90.820 92.700
+153 1581 93.280 6708500 92.400 92.400 93.520
+153 1582 93.370 4907300 92.500 92.500 93.700
+153 1583 93.370 4513100 93.000 93.000 93.950
+154 1584 94.130 3541200 94.280 94.130 94.640
+154 1585 92.700 5664200 94.140 92.540 94.830
+154 1586 92.760 4838700 92.950 92.300 93.100
+154 1587 93.300 4233000 92.700 92.500 93.970
+154 1588 93.570 2868800 93.160 93.160 94.020
+154 1589 94.330 4299200 93.500 93.480 94.670
+155 1590 93.300 3821900 92.640 92.590 93.430
+155 1591 92.920 4541200 92.750 92.750 93.730
+155 1592 92.410 4037900 93.150 92.200 93.210
+155 1593 92.370 4754000 92.940 92.360 93.180
+155 1594 91.600 8051000 92.350 91.590 92.510
+155 1595 92.130 5087800 91.700 91.700 92.560
+155 1596 92.350 4513900 92.020 92.010 93.000
+155 1597 92.410 3424800 92.350 92.090 92.800
+155 1598 91.510 4494300 92.250 91.200 92.410
+156 1599 91.380 4419100 90.460 90.220 91.410
+156 1600 90.440 5740800 91.490 90.040 91.760
+156 1601 90.320 3737900 90.080 89.773 90.620
+156 1602 89.570 4579300 90.230 89.260 90.330
+156 1603 89.000 7836800 89.050 88.706 89.380
+156 1604 88.440 6376500 89.000 88.100 89.200
+156 1605 87.600 5273400 88.280 87.500 88.460
+156 1606 86.200 8296500 87.350 86.090 87.560
+156 1607 85.750 8450000 85.900 85.170 86.240
+156 1608 84.570 7090800 85.760 84.240 85.972
+156 1609 83.640 10708300 84.630 83.470 85.410
+156 1610 76.700 27932400 79.490 76.330 79.660
+156 1611 76.650 13256100 77.150 76.140 77.750
+156 1612 75.480 9292700 76.980 75.250 77.200
+156 1613 72.010 20389800 75.480 71.850 75.870
+156 1614 74.030 16273600 72.990 72.800 74.100
+156 1615 74.210 10985200 74.050 73.260 74.700
+156 1616 74.610 10223400 75.240 74.050 75.720
+156 1617 75.430 12500200 74.680 74.650 76.980
+156 1618 77.050 11706800 75.690 75.500 77.180
+156 1619 75.910 8638300 77.050 75.650 77.110
+157 1620 83.140 5340900 82.930 82.500 83.780
+157 1621 84.000 4490200 83.140 83.000 84.080
+157 1622 84.190 4712900 83.710 83.560 84.630
+157 1623 84.190 5210600 84.150 83.790 84.690
+157 1624 84.700 4661700 84.200 84.100 84.900
+157 1625 86.080 7788600 84.450 84.440 86.210
+157 1626 86.710 8633600 86.080 85.750 87.600
+157 1627 86.950 13328300 86.330 86.220 87.940
+157 1628 89.820 27876200 90.510 89.730 92.040
+157 1629 89.860 10676400 89.800 89.310 90.980
+157 1630 90.480 7391600 90.040 89.550 90.700
+157 1631 91.560 8862300 90.050 90.010 92.000
+157 1632 91.490 9222500 90.910 90.800 91.950
diff --git a/src/test/regress/expected/rpr.out b/src/test/regress/expected/rpr.out
index 35b90a04492..ecbe835cbb4 100644
--- a/src/test/regress/expected/rpr.out
+++ b/src/test/regress/expected/rpr.out
@@ -1,11 +1,25 @@
--
-- Test for row pattern recognition: WINDOW clause integration and
--- real-world scenario tests using stock price data.
+-- scenario tests using synthetic stock data.
--
-- Parser/planner tests: rpr_base.sql
-- NFA engine tests: rpr_nfa.sql
-- EXPLAIN statistics tests: rpr_explain.sql
--
+\getenv abs_srcdir PG_ABS_SRCDIR
+-- Synthetic stock data for RPR pattern matching tests
+CREATE TABLE rpr_stock (
+ part_id integer,
+ rn integer,
+ price numeric(10,3),
+ volume bigint,
+ open numeric(10,3),
+ low numeric(10,3),
+ high numeric(10,3)
+);
+\set filename :abs_srcdir '/data/stock.data'
+COPY rpr_stock FROM :'filename';
+ANALYZE rpr_stock;
CREATE TEMP TABLE stock (
company TEXT,
tdate DATE,
@@ -1839,3 +1853,504 @@ WINDOW w AS (
(6 rows)
DROP TABLE rpr_consec_null;
+-- ============================================================
+-- Stock Scenario Tests (1632 rows, partitioned regions)
+-- ============================================================
+-- Consecutive rising days: find streaks of 7+ days
+SELECT * FROM (
+ SELECT first_value(rn) OVER w AS start_rn,
+ last_value(rn) OVER w AS end_rn,
+ count(*) OVER w AS days
+ FROM rpr_stock
+ WINDOW w AS (
+ PARTITION BY part_id
+ ORDER BY rn
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (UP{7,})
+ DEFINE UP AS price > PREV(price)
+ )
+) t WHERE days > 0 ORDER BY start_rn;
+ start_rn | end_rn | days
+----------+--------+------
+ 29 | 35 | 7
+ 38 | 44 | 7
+ 96 | 102 | 7
+ 118 | 125 | 8
+ 308 | 317 | 10
+ 328 | 334 | 7
+ 475 | 481 | 7
+ 491 | 497 | 7
+ 509 | 517 | 9
+ 536 | 542 | 7
+ 586 | 592 | 7
+ 643 | 650 | 8
+ 740 | 746 | 7
+ 753 | 760 | 8
+ 904 | 910 | 7
+ 956 | 965 | 10
+ 985 | 991 | 7
+ 1095 | 1101 | 7
+ 1104 | 1110 | 7
+ 1181 | 1187 | 7
+ 1221 | 1228 | 8
+ 1262 | 1268 | 7
+ 1272 | 1278 | 7
+ 1373 | 1380 | 8
+ 1434 | 1440 | 7
+ 1485 | 1491 | 7
+ 1553 | 1559 | 7
+ 1576 | 1582 | 7
+ 1624 | 1631 | 8
+(29 rows)
+
+-- V-shape recovery: 4+ days decline followed by 4+ days rise
+SELECT * FROM (
+ SELECT first_value(rn) OVER w AS start_rn,
+ last_value(rn) OVER w AS end_rn,
+ first_value(price) OVER w AS start_price,
+ last_value(price) OVER w AS end_price,
+ count(*) OVER w AS days
+ FROM rpr_stock
+ WINDOW w AS (
+ PARTITION BY part_id
+ ORDER BY rn
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (DECLINE{4,} RISE{4,})
+ DEFINE
+ DECLINE AS price < PREV(price),
+ RISE AS price > PREV(price)
+ )
+) t WHERE days > 0 ORDER BY start_rn;
+ start_rn | end_rn | start_price | end_price | days
+----------+--------+-------------+-----------+------
+ 84 | 93 | 301.000 | 309.750 | 10
+ 114 | 125 | 394.500 | 418.750 | 12
+ 173 | 183 | 260.000 | 282.000 | 11
+ 204 | 214 | 179.000 | 171.500 | 11
+ 262 | 271 | 277.625 | 275.500 | 10
+ 337 | 344 | 308.250 | 308.125 | 8
+ 436 | 444 | 56.630 | 60.250 | 9
+ 567 | 575 | 126.500 | 127.500 | 9
+ 598 | 607 | 112.250 | 116.500 | 10
+ 653 | 660 | 129.375 | 128.125 | 8
+ 663 | 671 | 125.630 | 130.250 | 9
+ 685 | 693 | 120.130 | 124.000 | 9
+ 710 | 719 | 141.125 | 144.130 | 10
+ 833 | 841 | 106.500 | 111.125 | 9
+ 853 | 862 | 105.375 | 107.750 | 10
+ 930 | 937 | 92.380 | 96.375 | 8
+ 1188 | 1197 | 105.250 | 108.380 | 10
+ 1198 | 1206 | 100.125 | 100.562 | 9
+ 1250 | 1259 | 131.437 | 130.875 | 10
+ 1285 | 1295 | 176.940 | 182.190 | 11
+ 1298 | 1307 | 181.000 | 178.562 | 10
+ 1310 | 1322 | 186.310 | 212.000 | 13
+ 1405 | 1412 | 117.800 | 117.500 | 8
+ 1467 | 1474 | 70.400 | 71.830 | 8
+ 1494 | 1502 | 79.760 | 86.000 | 9
+ 1600 | 1618 | 90.440 | 77.050 | 19
+(26 rows)
+
+-- W-bottom: decline, bounce, re-decline, recovery
+SELECT * FROM (
+ SELECT first_value(rn) OVER w AS start_rn,
+ last_value(rn) OVER w AS end_rn,
+ first_value(price) OVER w AS start_price,
+ last_value(price) OVER w AS end_price,
+ count(*) OVER w AS days
+ FROM rpr_stock
+ WINDOW w AS (
+ PARTITION BY part_id
+ ORDER BY rn
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (DECLINE{3,} BOUNCE{3,} DIP{3,} RECOVER{3,})
+ DEFINE
+ DECLINE AS price < PREV(price),
+ BOUNCE AS price > PREV(price),
+ DIP AS price < PREV(price),
+ RECOVER AS price > PREV(price)
+ )
+) t WHERE days > 0 ORDER BY start_rn;
+ start_rn | end_rn | start_price | end_price | days
+----------+--------+-------------+-----------+------
+ 136 | 153 | 444.000 | 434.250 | 18
+ 456 | 469 | 64.500 | 65.125 | 14
+ 520 | 534 | 115.250 | 115.750 | 15
+ 610 | 623 | 107.125 | 109.000 | 14
+ 791 | 802 | 113.500 | 118.250 | 12
+ 942 | 953 | 91.250 | 89.875 | 12
+ 1188 | 1206 | 105.250 | 100.562 | 19
+ 1560 | 1574 | 87.420 | 90.000 | 15
+(8 rows)
+
+-- Volume surge streak: 6+ consecutive days of increasing volume
+SELECT * FROM (
+ SELECT first_value(rn) OVER w AS start_rn,
+ last_value(rn) OVER w AS end_rn,
+ first_value(volume) OVER w AS start_vol,
+ last_value(volume) OVER w AS end_vol,
+ count(*) OVER w AS days
+ FROM rpr_stock
+ WINDOW w AS (
+ PARTITION BY part_id
+ ORDER BY rn
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (INIT SURGE{5,})
+ DEFINE
+ SURGE AS volume > PREV(volume)
+ )
+) t WHERE days > 0 ORDER BY start_rn;
+ start_rn | end_rn | start_vol | end_vol | days
+----------+--------+-----------+----------+------
+ 186 | 191 | 25100 | 35300 | 6
+ 291 | 296 | 52200 | 188300 | 6
+ 408 | 413 | 163300 | 610300 | 6
+ 439 | 444 | 438400 | 1089200 | 6
+ 500 | 506 | 373700 | 1114200 | 7
+ 551 | 558 | 691100 | 2097500 | 8
+ 635 | 640 | 418300 | 1388100 | 6
+ 783 | 788 | 769000 | 1564900 | 6
+ 824 | 830 | 682200 | 2418700 | 7
+ 968 | 974 | 993100 | 2341200 | 7
+ 1072 | 1077 | 1257200 | 2327700 | 6
+ 1078 | 1084 | 1748300 | 5514300 | 7
+ 1093 | 1098 | 2664400 | 13145900 | 6
+ 1334 | 1339 | 3220900 | 10552600 | 6
+ 1524 | 1530 | 3562400 | 7012100 | 7
+ 1533 | 1538 | 3548500 | 8560800 | 6
+ 1575 | 1580 | 4226500 | 6952100 | 6
+(17 rows)
+
+-- Volatility squeeze: consecutive narrowing of daily price range
+SELECT * FROM (
+ SELECT first_value(rn) OVER w AS start_rn,
+ last_value(rn) OVER w AS end_rn,
+ first_value(high - low) OVER w AS start_range,
+ last_value(high - low) OVER w AS end_range,
+ count(*) OVER w AS days
+ FROM rpr_stock
+ WINDOW w AS (
+ PARTITION BY part_id
+ ORDER BY rn
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (INIT NARROW{5,})
+ DEFINE
+ NARROW AS (high - low) < PREV(high) - PREV(low)
+ )
+) t WHERE days > 0 ORDER BY start_rn;
+ start_rn | end_rn | start_range | end_range | days
+----------+--------+-------------+-----------+------
+ 128 | 133 | 11.000 | 3.000 | 6
+ 170 | 175 | 10.250 | 2.750 | 6
+ 194 | 201 | 10.000 | 1.625 | 8
+ 283 | 288 | 7.000 | 2.375 | 6
+ 320 | 325 | 4.750 | 2.500 | 6
+ 578 | 583 | 2.750 | 1.125 | 6
+ 725 | 731 | 3.370 | 1.125 | 7
+ 775 | 780 | 4.500 | 0.875 | 6
+ 913 | 918 | 2.870 | 1.250 | 6
+ 1130 | 1135 | 3.000 | 1.125 | 6
+ 1348 | 1353 | 6.620 | 1.562 | 6
+(11 rows)
+
+-- Gap up: open significantly higher than previous close (5%+)
+SELECT * FROM (
+ SELECT first_value(rn) OVER w AS gap_rn,
+ first_value(price) OVER w AS prev_close,
+ last_value(open) OVER w AS gap_open,
+ count(*) OVER w AS cnt
+ FROM rpr_stock
+ WINDOW w AS (
+ PARTITION BY part_id
+ ORDER BY rn
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (PREV_DAY GAP_UP)
+ DEFINE
+ GAP_UP AS open > PREV(price) * 1.05
+ )
+) t WHERE cnt > 0 ORDER BY gap_rn;
+ gap_rn | prev_close | gap_open | cnt
+--------+------------+----------+-----
+ 986 | 48.875 | 52.875 | 2
+ 994 | 42.375 | 44.880 | 2
+ 1029 | 52.250 | 55.000 | 2
+ 1039 | 55.880 | 59.000 | 2
+ 1177 | 142.380 | 150.500 | 2
+ 1238 | 122.000 | 128.250 | 2
+ 1318 | 171.875 | 197.250 | 2
+ 1383 | 103.500 | 112.000 | 2
+ 1392 | 96.687 | 104.375 | 2
+ 1401 | 106.500 | 112.000 | 2
+ 1464 | 66.400 | 70.370 | 2
+ 1477 | 57.580 | 62.000 | 2
+ 1479 | 63.420 | 67.750 | 2
+ 1481 | 64.900 | 72.800 | 2
+ 1517 | 90.310 | 95.070 | 2
+(15 rows)
+
+-- Price-volume divergence: price rising while volume declining (bearish signal)
+SELECT * FROM (
+ SELECT first_value(rn) OVER w AS start_rn,
+ last_value(rn) OVER w AS end_rn,
+ first_value(price) OVER w AS start_price,
+ last_value(price) OVER w AS end_price,
+ count(*) OVER w AS days
+ FROM rpr_stock
+ WINDOW w AS (
+ PARTITION BY part_id
+ ORDER BY rn
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (INIT DIVERGE{3,})
+ DEFINE
+ DIVERGE AS price > PREV(price) AND volume < PREV(volume)
+ )
+) t WHERE days > 0 ORDER BY start_rn;
+ start_rn | end_rn | start_price | end_price | days
+----------+--------+-------------+-----------+------
+ 10 | 13 | 338.500 | 349.750 | 4
+ 47 | 50 | 352.000 | 358.000 | 4
+ 53 | 56 | 314.000 | 319.000 | 4
+ 63 | 66 | 294.500 | 301.750 | 4
+ 99 | 102 | 401.000 | 404.000 | 4
+ 177 | 180 | 248.500 | 261.750 | 4
+ 209 | 212 | 152.000 | 166.500 | 4
+ 237 | 240 | 207.500 | 215.500 | 4
+ 274 | 277 | 259.500 | 264.500 | 4
+ 347 | 350 | 306.750 | 312.000 | 4
+ 531 | 534 | 111.875 | 115.750 | 4
+ 545 | 548 | 118.000 | 121.000 | 4
+ 561 | 564 | 131.130 | 134.250 | 4
+ 586 | 589 | 110.250 | 112.000 | 4
+ 645 | 648 | 129.630 | 133.250 | 4
+ 656 | 660 | 124.500 | 128.125 | 5
+ 679 | 682 | 128.000 | 129.750 | 4
+ 734 | 737 | 121.380 | 123.750 | 4
+ 763 | 766 | 150.750 | 156.500 | 4
+ 844 | 847 | 105.500 | 107.880 | 4
+ 921 | 924 | 97.250 | 101.250 | 4
+ 956 | 959 | 84.875 | 87.500 | 4
+ 960 | 963 | 88.250 | 89.500 | 4
+ 987 | 990 | 49.000 | 51.500 | 4
+ 1023 | 1026 | 53.250 | 54.625 | 4
+ 1033 | 1036 | 57.125 | 58.380 | 4
+ 1060 | 1063 | 89.000 | 93.625 | 4
+ 1066 | 1069 | 89.130 | 93.000 | 4
+ 1087 | 1090 | 89.375 | 91.750 | 4
+ 1165 | 1168 | 137.625 | 146.875 | 4
+ 1202 | 1205 | 96.500 | 98.750 | 4
+ 1209 | 1212 | 96.125 | 100.250 | 4
+ 1220 | 1223 | 106.130 | 112.000 | 4
+ 1231 | 1234 | 117.062 | 119.625 | 4
+ 1244 | 1247 | 122.500 | 130.000 | 4
+ 1304 | 1307 | 169.500 | 178.562 | 4
+ 1342 | 1345 | 122.000 | 124.000 | 4
+ 1417 | 1420 | 101.960 | 108.530 | 4
+ 1433 | 1436 | 90.000 | 93.770 | 4
+ 1437 | 1440 | 96.950 | 98.500 | 4
+ 1443 | 1446 | 120.250 | 122.200 | 4
+ 1449 | 1452 | 74.650 | 77.140 | 4
+ 1455 | 1458 | 67.600 | 73.500 | 4
+ 1497 | 1500 | 76.250 | 81.650 | 4
+ 1505 | 1508 | 75.860 | 79.510 | 4
+ 1511 | 1514 | 79.750 | 81.020 | 4
+ 1518 | 1521 | 94.020 | 97.700 | 4
+ 1541 | 1544 | 83.650 | 85.250 | 4
+ 1547 | 1550 | 82.210 | 84.040 | 4
+ 1585 | 1588 | 92.700 | 93.570 | 4
+ 1594 | 1597 | 91.600 | 92.410 | 4
+ 1613 | 1616 | 72.010 | 74.610 | 4
+(52 rows)
+
+-- Consolidation then breakout: sideways movement followed by sharp rise
+SELECT * FROM (
+ SELECT first_value(rn) OVER w AS start_rn,
+ last_value(rn) OVER w AS end_rn,
+ first_value(price) OVER w AS start_price,
+ last_value(price) OVER w AS end_price,
+ count(*) OVER w AS days
+ FROM rpr_stock
+ WINDOW w AS (
+ PARTITION BY part_id
+ ORDER BY rn
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (FLAT{5,} BREAKOUT)
+ DEFINE
+ FLAT AS price BETWEEN PREV(price) * 0.98 AND PREV(price) * 1.02,
+ BREAKOUT AS price > PREV(price) * 1.05
+ )
+) t WHERE days > 0 ORDER BY start_rn;
+ start_rn | end_rn | start_price | end_price | days
+----------+--------+-------------+-----------+------
+ 69 | 81 | 296.750 | 314.000 | 13
+ 214 | 225 | 171.500 | 164.125 | 12
+ 371 | 395 | 71.630 | 71.250 | 25
+ 416 | 424 | 54.250 | 53.875 | 9
+ 484 | 494 | 75.500 | 79.000 | 11
+ 865 | 892 | 112.500 | 115.750 | 28
+ 1007 | 1020 | 59.500 | 58.630 | 14
+ 1113 | 1118 | 110.750 | 117.625 | 6
+ 1146 | 1152 | 133.750 | 145.000 | 7
+ 1171 | 1178 | 137.880 | 153.625 | 8
+ 1350 | 1357 | 106.125 | 112.000 | 8
+ 1360 | 1370 | 109.125 | 115.625 | 11
+(12 rows)
+
+-- Dead cat bounce: decline followed by weak recovery (<1% per day)
+SELECT * FROM (
+ SELECT first_value(rn) OVER w AS start_rn,
+ last_value(rn) OVER w AS end_rn,
+ first_value(price) OVER w AS start_price,
+ last_value(price) OVER w AS end_price,
+ count(*) OVER w AS days
+ FROM rpr_stock
+ WINDOW w AS (
+ PARTITION BY part_id
+ ORDER BY rn
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (DECLINE{4,} BOUNCE{3,})
+ DEFINE
+ DECLINE AS price < PREV(price),
+ BOUNCE AS price > PREV(price) AND price < PREV(price) * 1.01
+ )
+) t WHERE days > 0 ORDER BY start_rn;
+ start_rn | end_rn | start_price | end_price | days
+----------+--------+-------------+-----------+------
+ 59 | 66 | 315.000 | 301.750 | 8
+ 262 | 271 | 277.625 | 275.500 | 10
+ 280 | 287 | 272.000 | 261.250 | 8
+ 361 | 368 | 75.375 | 74.130 | 8
+ 427 | 433 | 65.130 | 65.500 | 7
+ 447 | 453 | 55.500 | 54.625 | 7
+ 653 | 659 | 129.375 | 126.625 | 7
+ 674 | 682 | 132.375 | 129.750 | 9
+ 833 | 839 | 106.500 | 107.125 | 7
+ 1423 | 1430 | 108.180 | 106.200 | 8
+ 1591 | 1597 | 92.920 | 92.410 | 7
+(11 rows)
+
+-- Uptrend: 7+ consecutive days of higher highs AND higher lows
+SELECT * FROM (
+ SELECT first_value(rn) OVER w AS start_rn,
+ last_value(rn) OVER w AS end_rn,
+ first_value(price) OVER w AS start_price,
+ last_value(price) OVER w AS end_price,
+ count(*) OVER w AS days
+ FROM rpr_stock
+ WINDOW w AS (
+ PARTITION BY part_id
+ ORDER BY rn
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (UPTREND{7,})
+ DEFINE
+ UPTREND AS high > PREV(high) AND low > PREV(low)
+ )
+) t WHERE days > 0 ORDER BY start_rn;
+ start_rn | end_rn | start_price | end_price | days
+----------+--------+-------------+-----------+------
+ 156 | 162 | 317.000 | 329.500 | 7
+ 299 | 305 | 266.000 | 273.500 | 7
+ 696 | 702 | 144.630 | 152.500 | 7
+ 741 | 747 | 155.250 | 166.750 | 7
+ 895 | 901 | 119.250 | 126.750 | 7
+ 1121 | 1127 | 103.630 | 108.875 | 7
+ 1211 | 1217 | 99.130 | 102.875 | 7
+ 1271 | 1278 | 164.375 | 189.250 | 8
+ 1621 | 1628 | 84.000 | 89.820 | 8
+(9 rows)
+
+-- Panic and snap-back: 3%+ daily drops followed by 2%+ rebound
+SELECT * FROM (
+ SELECT first_value(rn) OVER w AS start_rn,
+ last_value(rn) OVER w AS end_rn,
+ first_value(price) OVER w AS start_price,
+ last_value(price) OVER w AS end_price,
+ count(*) OVER w AS days
+ FROM rpr_stock
+ WINDOW w AS (
+ PARTITION BY part_id
+ ORDER BY rn
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (PANIC{2,} SNAP)
+ DEFINE
+ PANIC AS price < PREV(price) * 0.97,
+ SNAP AS price > PREV(price) * 1.02
+ )
+) t WHERE days > 0 ORDER BY start_rn;
+ start_rn | end_rn | start_price | end_price | days
+----------+--------+-------------+-----------+------
+ 24 | 26 | 230.625 | 229.875 | 3
+ 165 | 167 | 272.000 | 270.750 | 3
+ 169 | 171 | 260.000 | 259.500 | 3
+ 769 | 772 | 140.125 | 115.000 | 4
+ 977 | 979 | 56.130 | 53.000 | 3
+ 980 | 982 | 51.375 | 51.750 | 3
+ 1387 | 1389 | 86.000 | 89.000 | 3
+ 1396 | 1398 | 99.290 | 98.390 | 3
+(8 rows)
+
+-- Volume climax reversal: uptrend, volume spike (1.5x), then decline
+SELECT * FROM (
+ SELECT first_value(rn) OVER w AS start_rn,
+ last_value(rn) OVER w AS end_rn,
+ first_value(price) OVER w AS start_price,
+ last_value(price) OVER w AS end_price,
+ count(*) OVER w AS days
+ FROM rpr_stock
+ WINDOW w AS (
+ PARTITION BY part_id
+ ORDER BY rn
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (RALLY{3,} CLIMAX SELLOFF{2,})
+ DEFINE
+ RALLY AS price > PREV(price),
+ CLIMAX AS volume > PREV(volume) * 1.5,
+ SELLOFF AS price < PREV(price)
+ )
+) t WHERE days > 0 ORDER BY start_rn;
+ start_rn | end_rn | start_price | end_price | days
+----------+--------+-------------+-----------+------
+ 2 | 7 | 368.250 | 367.750 | 6
+ 16 | 21 | 349.250 | 344.500 | 6
+ 105 | 111 | 388.500 | 394.500 | 7
+ 228 | 234 | 164.250 | 163.250 | 7
+ 243 | 248 | 223.250 | 228.500 | 6
+ 251 | 259 | 251.000 | 253.500 | 9
+ 352 | 358 | 310.500 | 309.000 | 7
+ 398 | 405 | 65.380 | 65.000 | 8
+ 466 | 472 | 64.250 | 63.875 | 7
+ 586 | 595 | 110.250 | 112.250 | 10
+ 626 | 632 | 125.750 | 123.250 | 7
+ 700 | 707 | 150.250 | 152.750 | 8
+ 714 | 722 | 136.125 | 137.000 | 9
+ 740 | 750 | 154.250 | 163.000 | 11
+ 805 | 811 | 116.875 | 116.000 | 7
+ 814 | 821 | 99.500 | 97.130 | 8
+ 850 | 857 | 107.880 | 99.250 | 8
+ 922 | 927 | 99.750 | 99.875 | 6
+ 934 | 939 | 92.375 | 92.880 | 6
+ 998 | 1004 | 52.000 | 53.130 | 7
+ 1043 | 1049 | 73.125 | 73.000 | 7
+ 1052 | 1057 | 72.130 | 74.250 | 6
+ 1138 | 1143 | 127.750 | 125.625 | 6
+ 1155 | 1162 | 158.000 | 155.625 | 8
+ 1181 | 1191 | 99.130 | 100.062 | 11
+ 1192 | 1202 | 100.130 | 96.500 | 11
+ 1236 | 1241 | 118.380 | 123.880 | 6
+ 1272 | 1282 | 166.062 | 183.000 | 11
+ 1325 | 1331 | 217.250 | 237.500 | 7
+ 1409 | 1414 | 112.890 | 116.100 | 6
+ 1456 | 1461 | 68.580 | 68.760 | 6
+(31 rows)
+
diff --git a/src/test/regress/sql/rpr.sql b/src/test/regress/sql/rpr.sql
index 8dbd3f9036a..b308f8d7cb4 100644
--- a/src/test/regress/sql/rpr.sql
+++ b/src/test/regress/sql/rpr.sql
@@ -1,12 +1,29 @@
--
-- Test for row pattern recognition: WINDOW clause integration and
--- real-world scenario tests using stock price data.
+-- scenario tests using synthetic stock data.
--
-- Parser/planner tests: rpr_base.sql
-- NFA engine tests: rpr_nfa.sql
-- EXPLAIN statistics tests: rpr_explain.sql
--
+\getenv abs_srcdir PG_ABS_SRCDIR
+
+-- Synthetic stock data for RPR pattern matching tests
+CREATE TABLE rpr_stock (
+ part_id integer,
+ rn integer,
+ price numeric(10,3),
+ volume bigint,
+ open numeric(10,3),
+ low numeric(10,3),
+ high numeric(10,3)
+);
+
+\set filename :abs_srcdir '/data/stock.data'
+COPY rpr_stock FROM :'filename';
+ANALYZE rpr_stock;
+
CREATE TEMP TABLE stock (
company TEXT,
tdate DATE,
@@ -908,3 +925,240 @@ WINDOW w AS (
);
DROP TABLE rpr_consec_null;
+
+-- ============================================================
+-- Stock Scenario Tests (1632 rows, partitioned regions)
+-- ============================================================
+
+-- Consecutive rising days: find streaks of 7+ days
+SELECT * FROM (
+ SELECT first_value(rn) OVER w AS start_rn,
+ last_value(rn) OVER w AS end_rn,
+ count(*) OVER w AS days
+ FROM rpr_stock
+ WINDOW w AS (
+ PARTITION BY part_id
+ ORDER BY rn
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (UP{7,})
+ DEFINE UP AS price > PREV(price)
+ )
+) t WHERE days > 0 ORDER BY start_rn;
+
+-- V-shape recovery: 4+ days decline followed by 4+ days rise
+SELECT * FROM (
+ SELECT first_value(rn) OVER w AS start_rn,
+ last_value(rn) OVER w AS end_rn,
+ first_value(price) OVER w AS start_price,
+ last_value(price) OVER w AS end_price,
+ count(*) OVER w AS days
+ FROM rpr_stock
+ WINDOW w AS (
+ PARTITION BY part_id
+ ORDER BY rn
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (DECLINE{4,} RISE{4,})
+ DEFINE
+ DECLINE AS price < PREV(price),
+ RISE AS price > PREV(price)
+ )
+) t WHERE days > 0 ORDER BY start_rn;
+
+-- W-bottom: decline, bounce, re-decline, recovery
+SELECT * FROM (
+ SELECT first_value(rn) OVER w AS start_rn,
+ last_value(rn) OVER w AS end_rn,
+ first_value(price) OVER w AS start_price,
+ last_value(price) OVER w AS end_price,
+ count(*) OVER w AS days
+ FROM rpr_stock
+ WINDOW w AS (
+ PARTITION BY part_id
+ ORDER BY rn
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (DECLINE{3,} BOUNCE{3,} DIP{3,} RECOVER{3,})
+ DEFINE
+ DECLINE AS price < PREV(price),
+ BOUNCE AS price > PREV(price),
+ DIP AS price < PREV(price),
+ RECOVER AS price > PREV(price)
+ )
+) t WHERE days > 0 ORDER BY start_rn;
+
+-- Volume surge streak: 6+ consecutive days of increasing volume
+SELECT * FROM (
+ SELECT first_value(rn) OVER w AS start_rn,
+ last_value(rn) OVER w AS end_rn,
+ first_value(volume) OVER w AS start_vol,
+ last_value(volume) OVER w AS end_vol,
+ count(*) OVER w AS days
+ FROM rpr_stock
+ WINDOW w AS (
+ PARTITION BY part_id
+ ORDER BY rn
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (INIT SURGE{5,})
+ DEFINE
+ SURGE AS volume > PREV(volume)
+ )
+) t WHERE days > 0 ORDER BY start_rn;
+
+-- Volatility squeeze: consecutive narrowing of daily price range
+SELECT * FROM (
+ SELECT first_value(rn) OVER w AS start_rn,
+ last_value(rn) OVER w AS end_rn,
+ first_value(high - low) OVER w AS start_range,
+ last_value(high - low) OVER w AS end_range,
+ count(*) OVER w AS days
+ FROM rpr_stock
+ WINDOW w AS (
+ PARTITION BY part_id
+ ORDER BY rn
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (INIT NARROW{5,})
+ DEFINE
+ NARROW AS (high - low) < PREV(high) - PREV(low)
+ )
+) t WHERE days > 0 ORDER BY start_rn;
+
+-- Gap up: open significantly higher than previous close (5%+)
+SELECT * FROM (
+ SELECT first_value(rn) OVER w AS gap_rn,
+ first_value(price) OVER w AS prev_close,
+ last_value(open) OVER w AS gap_open,
+ count(*) OVER w AS cnt
+ FROM rpr_stock
+ WINDOW w AS (
+ PARTITION BY part_id
+ ORDER BY rn
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (PREV_DAY GAP_UP)
+ DEFINE
+ GAP_UP AS open > PREV(price) * 1.05
+ )
+) t WHERE cnt > 0 ORDER BY gap_rn;
+
+-- Price-volume divergence: price rising while volume declining (bearish signal)
+SELECT * FROM (
+ SELECT first_value(rn) OVER w AS start_rn,
+ last_value(rn) OVER w AS end_rn,
+ first_value(price) OVER w AS start_price,
+ last_value(price) OVER w AS end_price,
+ count(*) OVER w AS days
+ FROM rpr_stock
+ WINDOW w AS (
+ PARTITION BY part_id
+ ORDER BY rn
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (INIT DIVERGE{3,})
+ DEFINE
+ DIVERGE AS price > PREV(price) AND volume < PREV(volume)
+ )
+) t WHERE days > 0 ORDER BY start_rn;
+
+-- Consolidation then breakout: sideways movement followed by sharp rise
+SELECT * FROM (
+ SELECT first_value(rn) OVER w AS start_rn,
+ last_value(rn) OVER w AS end_rn,
+ first_value(price) OVER w AS start_price,
+ last_value(price) OVER w AS end_price,
+ count(*) OVER w AS days
+ FROM rpr_stock
+ WINDOW w AS (
+ PARTITION BY part_id
+ ORDER BY rn
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (FLAT{5,} BREAKOUT)
+ DEFINE
+ FLAT AS price BETWEEN PREV(price) * 0.98 AND PREV(price) * 1.02,
+ BREAKOUT AS price > PREV(price) * 1.05
+ )
+) t WHERE days > 0 ORDER BY start_rn;
+
+-- Dead cat bounce: decline followed by weak recovery (<1% per day)
+SELECT * FROM (
+ SELECT first_value(rn) OVER w AS start_rn,
+ last_value(rn) OVER w AS end_rn,
+ first_value(price) OVER w AS start_price,
+ last_value(price) OVER w AS end_price,
+ count(*) OVER w AS days
+ FROM rpr_stock
+ WINDOW w AS (
+ PARTITION BY part_id
+ ORDER BY rn
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (DECLINE{4,} BOUNCE{3,})
+ DEFINE
+ DECLINE AS price < PREV(price),
+ BOUNCE AS price > PREV(price) AND price < PREV(price) * 1.01
+ )
+) t WHERE days > 0 ORDER BY start_rn;
+
+-- Uptrend: 7+ consecutive days of higher highs AND higher lows
+SELECT * FROM (
+ SELECT first_value(rn) OVER w AS start_rn,
+ last_value(rn) OVER w AS end_rn,
+ first_value(price) OVER w AS start_price,
+ last_value(price) OVER w AS end_price,
+ count(*) OVER w AS days
+ FROM rpr_stock
+ WINDOW w AS (
+ PARTITION BY part_id
+ ORDER BY rn
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (UPTREND{7,})
+ DEFINE
+ UPTREND AS high > PREV(high) AND low > PREV(low)
+ )
+) t WHERE days > 0 ORDER BY start_rn;
+
+-- Panic and snap-back: 3%+ daily drops followed by 2%+ rebound
+SELECT * FROM (
+ SELECT first_value(rn) OVER w AS start_rn,
+ last_value(rn) OVER w AS end_rn,
+ first_value(price) OVER w AS start_price,
+ last_value(price) OVER w AS end_price,
+ count(*) OVER w AS days
+ FROM rpr_stock
+ WINDOW w AS (
+ PARTITION BY part_id
+ ORDER BY rn
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (PANIC{2,} SNAP)
+ DEFINE
+ PANIC AS price < PREV(price) * 0.97,
+ SNAP AS price > PREV(price) * 1.02
+ )
+) t WHERE days > 0 ORDER BY start_rn;
+
+-- Volume climax reversal: uptrend, volume spike (1.5x), then decline
+SELECT * FROM (
+ SELECT first_value(rn) OVER w AS start_rn,
+ last_value(rn) OVER w AS end_rn,
+ first_value(price) OVER w AS start_price,
+ last_value(price) OVER w AS end_price,
+ count(*) OVER w AS days
+ FROM rpr_stock
+ WINDOW w AS (
+ PARTITION BY part_id
+ ORDER BY rn
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (RALLY{3,} CLIMAX SELLOFF{2,})
+ DEFINE
+ RALLY AS price > PREV(price),
+ CLIMAX AS volume > PREV(volume) * 1.5,
+ SELLOFF AS price < PREV(price)
+ )
+) t WHERE days > 0 ORDER BY start_rn;
--
2.50.1 (Apple Git-155)
From d13cf490ce7585fd99a7690911a75d90a51017bb Mon Sep 17 00:00:00 2001
From: Henson Choi <[email protected]>
Date: Wed, 4 Mar 2026 14:08:47 +0900
Subject: [PATCH 8/8] Fix zero-min reluctant quantifier to produce zero-length
match
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 4441b4d9c51..3075c51789c 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -307,24 +307,24 @@ static inline bool nfa_eval_var_match(WindowAggState *winstate,
static void nfa_match(WindowAggState *winstate, RPRNFAContext *ctx,
bool *varMatched);
static void nfa_advance_state(WindowAggState *winstate, RPRNFAContext *ctx,
- RPRNFAState *state, int64 currentPos, bool initialAdvance);
+ RPRNFAState *state, int64 currentPos);
static void nfa_route_to_elem(WindowAggState *winstate, RPRNFAContext *ctx,
RPRNFAState *state, RPRPatternElement *nextElem,
- int64 currentPos, bool initialAdvance);
+ int64 currentPos);
static void nfa_advance_alt(WindowAggState *winstate, RPRNFAContext *ctx,
RPRNFAState *state, RPRPatternElement *elem,
- int64 currentPos, bool initialAdvance);
+ int64 currentPos);
static void nfa_advance_begin(WindowAggState *winstate, RPRNFAContext *ctx,
RPRNFAState *state, RPRPatternElement *elem,
- int64 currentPos, bool initialAdvance);
+ int64 currentPos);
static void nfa_advance_end(WindowAggState *winstate, RPRNFAContext *ctx,
RPRNFAState *state, RPRPatternElement *elem,
- int64 currentPos, bool initialAdvance);
+ int64 currentPos);
static void nfa_advance_var(WindowAggState *winstate, RPRNFAContext *ctx,
RPRNFAState *state, RPRPatternElement *elem,
- int64 currentPos, bool initialAdvance);
+ int64 currentPos);
static void nfa_advance(WindowAggState *winstate, RPRNFAContext *ctx,
- int64 currentPos, bool initialAdvance);
+ int64 currentPos);
/*
* Not null info bit array consists of 2-bit items
@@ -5008,9 +5008,14 @@ register_result:
* handles nested groups like "((A|B)+)+" correctly - exiting the inner
* group counts as one iteration of the outer group.
*
- * - initialAdvance flag: The first advance after context creation must
- * skip FIN recording. Reaching FIN without evaluating any VAR would
- * create a zero-length match, which is invalid.
+ * - Zero-length match handling: The initial advance uses currentPos =
+ * startPos - 1 (before any row is consumed). If FIN is reached via
+ * epsilon transitions alone, matchEndRow = startPos - 1 < matchStartRow,
+ * resulting in UNMATCHED. For reluctant min=0 patterns (A*?, A??),
+ * the skip path reaches FIN first and early termination prunes enter
+ * paths, yielding an immediate zero-length (unmatched) result. For
+ * greedy patterns (A*), the enter path adds VAR states first, then
+ * the skip FIN is recorded but VAR states survive for later matching.
*
* Context Absorption Runtime:
* ---------------------------
@@ -5122,7 +5127,7 @@ nfa_process_row(WindowAggState *winstate, int64 currentPos,
Assert(!hasLimitedFrame ||
currentPos < ctx->matchStartRow + frameOffset + 1);
- nfa_advance(winstate, ctx, currentPos, false);
+ nfa_advance(winstate, ctx, currentPos);
}
}
@@ -5467,10 +5472,11 @@ nfa_start_context(WindowAggState *winstate, int64 startPos)
* states for VAR elements with min=0. This prepares the context for the
* first row's match phase.
*
- * Pass initialAdvance=true to prevent recording zero-length matches when
- * optional patterns can skip all VARs to reach FIN immediately.
+ * Use startPos - 1 as currentPos since no row has been consumed yet. If
+ * FIN is reached via epsilon transitions, matchEndRow = startPos - 1
+ * which is less than matchStartRow, resulting in UNMATCHED treatment.
*/
- nfa_advance(winstate, ctx, startPos, true);
+ nfa_advance(winstate, ctx, startPos - 1);
return ctx;
}
@@ -5711,7 +5717,7 @@ nfa_finalize_all_contexts(WindowAggState *winstate, int64 lastPos)
if (ctx->states != NULL)
{
nfa_match(winstate, ctx, NULL);
- nfa_advance(winstate, ctx, lastPos, false);
+ nfa_advance(winstate, ctx, lastPos);
}
}
}
@@ -6047,7 +6053,7 @@ nfa_match(WindowAggState *winstate, RPRNFAContext *ctx, bool *varMatched)
static void
nfa_route_to_elem(WindowAggState *winstate, RPRNFAContext *ctx,
RPRNFAState *state, RPRPatternElement *nextElem,
- int64 currentPos, bool initialAdvance)
+ int64 currentPos)
{
if (RPRElemIsVar(nextElem))
{
@@ -6061,11 +6067,11 @@ nfa_route_to_elem(WindowAggState *winstate, RPRNFAContext *ctx,
nfa_add_state_unique(winstate, ctx, state);
if (skipState != NULL)
- nfa_advance_state(winstate, ctx, skipState, currentPos, initialAdvance);
+ nfa_advance_state(winstate, ctx, skipState, currentPos);
}
else
{
- nfa_advance_state(winstate, ctx, state, currentPos, initialAdvance);
+ nfa_advance_state(winstate, ctx, state, currentPos);
}
}
@@ -6077,7 +6083,7 @@ nfa_route_to_elem(WindowAggState *winstate, RPRNFAContext *ctx,
static void
nfa_advance_alt(WindowAggState *winstate, RPRNFAContext *ctx,
RPRNFAState *state, RPRPatternElement *elem,
- int64 currentPos, bool initialAdvance)
+ int64 currentPos)
{
RPRPattern *pattern = winstate->rpPattern;
RPRPatternElement *elements = pattern->elements;
@@ -6097,7 +6103,7 @@ nfa_advance_alt(WindowAggState *winstate, RPRNFAContext *ctx,
state->counts, state->isAbsorbable);
/* Recursively process this branch before next */
- nfa_advance_state(winstate, ctx, newState, currentPos, initialAdvance);
+ nfa_advance_state(winstate, ctx, newState, currentPos);
altIdx = altElem->jump;
}
@@ -6115,7 +6121,7 @@ nfa_advance_alt(WindowAggState *winstate, RPRNFAContext *ctx,
static void
nfa_advance_begin(WindowAggState *winstate, RPRNFAContext *ctx,
RPRNFAState *state, RPRPatternElement *elem,
- int64 currentPos, bool initialAdvance)
+ int64 currentPos)
{
RPRPattern *pattern = winstate->rpPattern;
RPRPatternElement *elements = pattern->elements;
@@ -6136,7 +6142,7 @@ nfa_advance_begin(WindowAggState *winstate, RPRNFAContext *ctx,
/* Reluctant: skip first (prefer fewer iterations), enter second */
nfa_route_to_elem(winstate, ctx, skipState,
- &elements[elem->jump], currentPos, initialAdvance);
+ &elements[elem->jump], currentPos);
/*
* If skip path reached FIN, shortest match is found. Skip group entry
@@ -6150,19 +6156,19 @@ nfa_advance_begin(WindowAggState *winstate, RPRNFAContext *ctx,
state->elemIdx = elem->next;
nfa_route_to_elem(winstate, ctx, state,
- &elements[state->elemIdx], currentPos, initialAdvance);
+ &elements[state->elemIdx], currentPos);
}
else
{
/* Greedy: enter first, skip second */
state->elemIdx = elem->next;
nfa_route_to_elem(winstate, ctx, state,
- &elements[state->elemIdx], currentPos, initialAdvance);
+ &elements[state->elemIdx], currentPos);
if (skipState != NULL)
{
nfa_route_to_elem(winstate, ctx, skipState,
- &elements[elem->jump], currentPos, initialAdvance);
+ &elements[elem->jump], currentPos);
}
}
}
@@ -6176,7 +6182,7 @@ nfa_advance_begin(WindowAggState *winstate, RPRNFAContext *ctx,
static void
nfa_advance_end(WindowAggState *winstate, RPRNFAContext *ctx,
RPRNFAState *state, RPRPatternElement *elem,
- int64 currentPos, bool initialAdvance)
+ int64 currentPos)
{
RPRPattern *pattern = winstate->rpPattern;
RPRPatternElement *elements = pattern->elements;
@@ -6199,7 +6205,7 @@ nfa_advance_end(WindowAggState *winstate, RPRNFAContext *ctx,
state->elemIdx = elem->jump;
jumpElem = &elements[state->elemIdx];
nfa_route_to_elem(winstate, ctx, state, jumpElem,
- currentPos, initialAdvance);
+ currentPos);
/*
* Fast-forward fallback for nullable bodies. E.g. (A?){2,3} when A
@@ -6223,7 +6229,7 @@ nfa_advance_end(WindowAggState *winstate, RPRNFAContext *ctx,
ffState->counts[nextElem->depth]++;
nfa_route_to_elem(winstate, ctx, ffState, nextElem,
- currentPos, initialAdvance);
+ currentPos);
}
}
else if (elem->max != RPR_QUANTITY_INF && count >= elem->max)
@@ -6239,7 +6245,7 @@ nfa_advance_end(WindowAggState *winstate, RPRNFAContext *ctx,
if (RPRElemIsEnd(nextElem) && state->counts[nextElem->depth] < RPR_COUNT_MAX)
state->counts[nextElem->depth]++;
- nfa_route_to_elem(winstate, ctx, state, nextElem, currentPos, initialAdvance);
+ nfa_route_to_elem(winstate, ctx, state, nextElem, currentPos);
}
else
{
@@ -6277,7 +6283,7 @@ nfa_advance_end(WindowAggState *winstate, RPRNFAContext *ctx,
/* Exit first (preferred for reluctant) */
nfa_route_to_elem(winstate, ctx, exitState, nextElem,
- currentPos, initialAdvance);
+ currentPos);
/*
* If exit path reached FIN, shortest match is found. Skip loop to
@@ -6291,16 +6297,16 @@ nfa_advance_end(WindowAggState *winstate, RPRNFAContext *ctx,
/* Loop second */
nfa_route_to_elem(winstate, ctx, state, jumpElem,
- currentPos, initialAdvance);
+ currentPos);
}
else
{
/* Loop first (preferred for greedy) */
nfa_route_to_elem(winstate, ctx, state, jumpElem,
- currentPos, initialAdvance);
+ currentPos);
/* Exit second */
nfa_route_to_elem(winstate, ctx, exitState, nextElem,
- currentPos, initialAdvance);
+ currentPos);
}
}
}
@@ -6314,7 +6320,7 @@ nfa_advance_end(WindowAggState *winstate, RPRNFAContext *ctx,
static void
nfa_advance_var(WindowAggState *winstate, RPRNFAContext *ctx,
RPRNFAState *state, RPRPatternElement *elem,
- int64 currentPos, bool initialAdvance)
+ int64 currentPos)
{
RPRPattern *pattern = winstate->rpPattern;
RPRPatternElement *elements = pattern->elements;
@@ -6359,7 +6365,7 @@ nfa_advance_var(WindowAggState *winstate, RPRNFAContext *ctx,
/* Exit first (preferred for reluctant) */
nfa_route_to_elem(winstate, ctx, cloneState, nextElem,
- currentPos, initialAdvance);
+ currentPos);
/*
* If exit path reached FIN, the shortest match is found. Skip
@@ -6400,7 +6406,7 @@ nfa_advance_var(WindowAggState *winstate, RPRNFAContext *ctx,
}
nfa_route_to_elem(winstate, ctx, state, nextElem,
- currentPos, initialAdvance);
+ currentPos);
}
}
else if (canLoop)
@@ -6424,7 +6430,7 @@ nfa_advance_var(WindowAggState *winstate, RPRNFAContext *ctx,
state->counts[nextElem->depth]++;
}
- nfa_route_to_elem(winstate, ctx, state, nextElem, currentPos, initialAdvance);
+ nfa_route_to_elem(winstate, ctx, state, nextElem, currentPos);
}
}
@@ -6436,7 +6442,7 @@ nfa_advance_var(WindowAggState *winstate, RPRNFAContext *ctx,
*/
static void
nfa_advance_state(WindowAggState *winstate, RPRNFAContext *ctx,
- RPRNFAState *state, int64 currentPos, bool initialAdvance)
+ RPRNFAState *state, int64 currentPos)
{
RPRPattern *pattern = winstate->rpPattern;
RPRPatternElement *elem;
@@ -6458,28 +6464,25 @@ nfa_advance_state(WindowAggState *winstate, RPRNFAContext *ctx,
switch (elem->varId)
{
case RPR_VARID_FIN:
- /* FIN: record match (skip for initial advance) */
- if (!initialAdvance)
- nfa_add_matched_state(winstate, ctx, state, currentPos);
- else
- nfa_state_free(winstate, state);
+ /* FIN: record match */
+ nfa_add_matched_state(winstate, ctx, state, currentPos);
break;
case RPR_VARID_ALT:
- nfa_advance_alt(winstate, ctx, state, elem, currentPos, initialAdvance);
+ nfa_advance_alt(winstate, ctx, state, elem, currentPos);
break;
case RPR_VARID_BEGIN:
- nfa_advance_begin(winstate, ctx, state, elem, currentPos, initialAdvance);
+ nfa_advance_begin(winstate, ctx, state, elem, currentPos);
break;
case RPR_VARID_END:
- nfa_advance_end(winstate, ctx, state, elem, currentPos, initialAdvance);
+ nfa_advance_end(winstate, ctx, state, elem, currentPos);
break;
default:
/* VAR element */
- nfa_advance_var(winstate, ctx, state, elem, currentPos, initialAdvance);
+ nfa_advance_var(winstate, ctx, state, elem, currentPos);
break;
}
}
@@ -6489,13 +6492,12 @@ nfa_advance_state(WindowAggState *winstate, RPRNFAContext *ctx,
*
* Advance phase (divergence): transition from all surviving states.
* Called after match phase with matched VAR states, or at context creation
- * for initial epsilon expansion (initialAdvance=true skips FIN matches).
+ * for initial epsilon expansion (with currentPos = startPos - 1).
*
* Processes states in order, using recursive DFS to maintain lexical order.
*/
static void
-nfa_advance(WindowAggState *winstate, RPRNFAContext *ctx, int64 currentPos,
- bool initialAdvance)
+nfa_advance(WindowAggState *winstate, RPRNFAContext *ctx, int64 currentPos)
{
RPRNFAState *states = ctx->states;
RPRNFAState *state;
@@ -6515,7 +6517,7 @@ nfa_advance(WindowAggState *winstate, RPRNFAContext *ctx, int64 currentPos,
states = states->next;
state->next = NULL;
- nfa_advance_state(winstate, ctx, state, currentPos, initialAdvance);
+ nfa_advance_state(winstate, ctx, state, currentPos);
/*
* Early termination: if a FIN was newly reached in this advance,
diff --git a/src/test/regress/expected/rpr_base.out b/src/test/regress/expected/rpr_base.out
index a7c536625f1..2fefb933c71 100644
--- a/src/test/regress/expected/rpr_base.out
+++ b/src/test/regress/expected/rpr_base.out
@@ -1090,9 +1090,9 @@ WINDOW w AS (
);
count
-------
- 1
- 1
- 1
+ 0
+ 0
+ 0
(3 rows)
-- Reluctant quantifier: prefer shortest match
@@ -1124,9 +1124,9 @@ WINDOW w AS (
);
count
-------
- 1
- 1
- 1
+ 0
+ 0
+ 0
(3 rows)
-- Reluctant quantifier: prefer shortest match
@@ -1192,9 +1192,9 @@ WINDOW w AS (
);
count
-------
- 1
- 1
- 1
+ 0
+ 0
+ 0
(3 rows)
-- Reluctant quantifier: prefer shortest match
@@ -1382,9 +1382,9 @@ WINDOW w AS (
);
count
-------
- 1
- 1
- 1
+ 0
+ 0
+ 0
(3 rows)
-- Reluctant quantifier: prefer shortest match
@@ -1460,9 +1460,9 @@ WINDOW w AS (
);
count
-------
- 1
- 1
- 1
+ 0
+ 0
+ 0
(3 rows)
-- Reluctant quantifier: prefer shortest match
diff --git a/src/test/regress/expected/rpr_explain.out b/src/test/regress/expected/rpr_explain.out
index ef184b7950b..d5cd80f423e 100644
--- a/src/test/regress/expected/rpr_explain.out
+++ b/src/test/regress/expected/rpr_explain.out
@@ -3347,7 +3347,7 @@ WINDOW w AS (
Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
Pattern: ((a' b')+" c)*
Storage: Memory Maximum Storage: NkB
- NFA States: 7 peak, 178 total, 0 merged
+ NFA States: 9 peak, 178 total, 0 merged
NFA Contexts: 4 peak, 61 total, 22 pruned
NFA: 1 matched (len 57/57/57.0), 0 mismatched
NFA: 0 absorbed, 37 skipped (len 1/3/2.0)
@@ -3384,7 +3384,7 @@ WINDOW w AS (
Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
Pattern: (a (b c)+)*
Storage: Memory Maximum Storage: NkB
- NFA States: 5 peak, 160 total, 0 merged
+ NFA States: 7 peak, 160 total, 0 merged
NFA Contexts: 4 peak, 61 total, 22 pruned
NFA: 1 matched (len 57/57/57.0), 0 mismatched
NFA: 0 absorbed, 37 skipped (len 1/3/2.0)
--
2.50.1 (Apple Git-155)
Attachments:
[text/plain] nocfbot-0001-fix-elog-message-to-use-lowercase-per-postgresql-con.txt (729B, 3-nocfbot-0001-fix-elog-message-to-use-lowercase-per-postgresql-con.txt)
download | inline diff:
From f4abb464c187ef6d1334d856db881d40069531da Mon Sep 17 00:00:00 2001
From: Henson Choi <[email protected]>
Date: Tue, 3 Mar 2026 15:49:54 +0900
Subject: [PATCH 1/8] Fix elog message to use lowercase per PostgreSQL
convention
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 701e83b519b..4441b4d9c51 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -4710,7 +4710,7 @@ row_is_in_reduced_frame(WindowObject winobj, int64 pos)
break;
default:
- elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+ elog(ERROR, "unrecognized state: %d at: " INT64_FORMAT,
state, pos);
break;
}
--
2.50.1 (Apple Git-155)
[text/plain] nocfbot-0002-fix-rpr-reluctant-quantifier-flag-lost-during-view-s.txt (9.4K, 4-nocfbot-0002-fix-rpr-reluctant-quantifier-flag-lost-during-view-s.txt)
download | inline diff:
From 2bc7f29dcfceefc83fd787eb138c96247f565360 Mon Sep 17 00:00:00 2001
From: Henson Choi <[email protected]>
Date: Tue, 3 Mar 2026 15:50:48 +0900
Subject: [PATCH 2/8] Fix RPR reluctant quantifier flag lost during VIEW
serialization
diff --git a/src/backend/optimizer/plan/rpr.c b/src/backend/optimizer/plan/rpr.c
index 8792d8174fa..009c0f5019d 100644
--- a/src/backend/optimizer/plan/rpr.c
+++ b/src/backend/optimizer/plan/rpr.c
@@ -198,7 +198,7 @@ flattenSeqChildren(List *children)
/* GROUP{1,1} should have been unwrapped by optimizeGroupPattern */
Assert(!(opt->nodeType == RPR_PATTERN_GROUP &&
- opt->min == 1 && opt->max == 1 && opt->reluctant < 0));
+ opt->min == 1 && opt->max == 1 && opt->reluctant == false));
if (opt->nodeType == RPR_PATTERN_SEQ)
{
@@ -234,7 +234,7 @@ mergeConsecutiveVars(List *children)
{
RPRPatternNode *child = (RPRPatternNode *) lfirst(lc);
- if (child->nodeType == RPR_PATTERN_VAR && child->reluctant < 0)
+ if (child->nodeType == RPR_PATTERN_VAR && child->reluctant == false)
{
/* ----------------------
* Can merge consecutive VAR nodes if:
@@ -253,7 +253,7 @@ mergeConsecutiveVars(List *children)
* Merge: accumulate min/max into prev. prev is guaranteed to
* be a non-reluctant VAR by the outer condition.
*/
- Assert(prev->nodeType == RPR_PATTERN_VAR && prev->reluctant < 0);
+ Assert(prev->nodeType == RPR_PATTERN_VAR && prev->reluctant == false);
prev->min += child->min;
@@ -308,7 +308,7 @@ mergeConsecutiveGroups(List *children)
{
RPRPatternNode *child = (RPRPatternNode *) lfirst(lc);
- if (child->nodeType == RPR_PATTERN_GROUP && child->reluctant < 0)
+ if (child->nodeType == RPR_PATTERN_GROUP && child->reluctant == false)
{
/* ----------------------
* Can merge consecutive GROUP nodes if:
@@ -327,7 +327,7 @@ mergeConsecutiveGroups(List *children)
* Merge: accumulate min/max into prev. prev is guaranteed to
* be a non-reluctant GROUP by the outer condition.
*/
- Assert(prev->nodeType == RPR_PATTERN_GROUP && prev->reluctant < 0);
+ Assert(prev->nodeType == RPR_PATTERN_GROUP && prev->reluctant == false);
prev->min += child->min;
@@ -385,7 +385,7 @@ mergeConsecutiveAlts(List *children)
{
RPRPatternNode *child = (RPRPatternNode *) lfirst(lc);
- if (child->nodeType == RPR_PATTERN_ALT && child->reluctant < 0)
+ if (child->nodeType == RPR_PATTERN_ALT && child->reluctant == false)
{
if (prev != NULL &&
rprPatternChildrenEqual(prev->children, child->children))
@@ -406,7 +406,8 @@ mergeConsecutiveAlts(List *children)
group->nodeType = RPR_PATTERN_GROUP;
group->min = count;
group->max = count;
- group->reluctant = -1;
+ group->reluctant = false;
+ group->reluctant_location = -1;
group->location = -1;
group->children = list_make1(prev);
mergedChildren = lappend(mergedChildren, group);
@@ -430,7 +431,8 @@ mergeConsecutiveAlts(List *children)
group->nodeType = RPR_PATTERN_GROUP;
group->min = count;
group->max = count;
- group->reluctant = -1;
+ group->reluctant = false;
+ group->reluctant_location = -1;
group->location = -1;
group->children = list_make1(prev);
mergedChildren = lappend(mergedChildren, group);
@@ -454,7 +456,8 @@ mergeConsecutiveAlts(List *children)
group->nodeType = RPR_PATTERN_GROUP;
group->min = count;
group->max = count;
- group->reluctant = -1;
+ group->reluctant = false;
+ group->reluctant_location = -1;
group->location = -1;
group->children = list_make1(prev);
mergedChildren = lappend(mergedChildren, group);
@@ -498,7 +501,7 @@ mergeGroupPrefixSuffix(List *children)
* children. GROUP's content may be wrapped in a SEQ - unwrap for
* comparison.
*/
- if (child->nodeType == RPR_PATTERN_GROUP && child->reluctant < 0)
+ if (child->nodeType == RPR_PATTERN_GROUP && child->reluctant == false)
{
List *groupContent = child->children;
int groupChildCount;
@@ -773,14 +776,14 @@ tryMultiplyQuantifiers(RPRPatternNode *pattern)
/* Parser always creates GROUP with exactly one child */
Assert(list_length(pattern->children) == 1);
- if (pattern->reluctant >= 0)
+ if (pattern->reluctant)
return pattern;
child = (RPRPatternNode *) linitial(pattern->children);
if ((child->nodeType != RPR_PATTERN_VAR &&
child->nodeType != RPR_PATTERN_GROUP) ||
- child->reluctant >= 0)
+ child->reluctant)
return pattern;
/* Case 1: Both unbounded - (A*)* -> A*, (A+)+ -> A+ */
@@ -862,11 +865,12 @@ tryUnwrapGroup(RPRPatternNode *pattern)
* the child and unwrap. E.g., (A)?? -> A??, (A)+? -> A+?
*/
if (child->nodeType == RPR_PATTERN_VAR &&
- child->min == 1 && child->max == 1 && child->reluctant < 0)
+ child->min == 1 && child->max == 1 && child->reluctant == false)
{
child->min = pattern->min;
child->max = pattern->max;
child->reluctant = pattern->reluctant;
+ child->reluctant_location = pattern->reluctant_location;
return child;
}
@@ -1150,7 +1154,7 @@ fillRPRPatternVar(RPRPatternNode *node, RPRPattern *pat, int *idx, RPRDepth dept
elem->max = (node->max == INT_MAX) ? RPR_QUANTITY_INF : node->max;
elem->next = RPR_ELEMIDX_INVALID;
elem->jump = RPR_ELEMIDX_INVALID;
- if (node->reluctant >= 0)
+ if (node->reluctant)
elem->flags |= RPR_ELEM_RELUCTANT;
(*idx)++;
@@ -1189,7 +1193,7 @@ fillRPRPatternGroup(RPRPatternNode *node, RPRPattern *pat, int *idx, RPRDepth de
elem->max = (node->max == INT_MAX) ? RPR_QUANTITY_INF : node->max;
elem->next = RPR_ELEMIDX_INVALID; /* set by finalize */
elem->jump = RPR_ELEMIDX_INVALID; /* set after END */
- if (node->reluctant >= 0)
+ if (node->reluctant)
elem->flags |= RPR_ELEM_RELUCTANT;
(*idx)++;
groupStartIdx = *idx; /* children start after BEGIN */
@@ -1214,7 +1218,7 @@ fillRPRPatternGroup(RPRPatternNode *node, RPRPattern *pat, int *idx, RPRDepth de
endElem->max = (node->max == INT_MAX) ? RPR_QUANTITY_INF : node->max;
endElem->next = RPR_ELEMIDX_INVALID;
endElem->jump = groupStartIdx; /* loop to first child */
- if (node->reluctant >= 0)
+ if (node->reluctant)
endElem->flags |= RPR_ELEM_RELUCTANT;
/*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index b55a11cc837..f1a71cd036b 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -16967,7 +16967,8 @@ row_pattern_alt:
n->children = list_make2($1, $3);
n->min = 1;
n->max = 1;
- n->reluctant = -1;
+ n->reluctant = false;
+ n->reluctant_location = -1;
n->location = @1;
$$ = (Node *) n;
}
@@ -16994,7 +16995,8 @@ row_pattern_seq:
n->children = list_make2($1, $2);
n->min = 1;
n->max = 1;
- n->reluctant = -1;
+ n->reluctant = false;
+ n->reluctant_location = -1;
n->location = @1;
$$ = (Node *) n;
}
@@ -17010,6 +17012,7 @@ row_pattern_term:
n->min = q->min;
n->max = q->max;
n->reluctant = q->reluctant;
+ n->reluctant_location = q->reluctant_location;
$$ = (Node *) n;
}
;
@@ -17022,7 +17025,8 @@ row_pattern_primary:
n->varName = $1;
n->min = 1;
n->max = 1;
- n->reluctant = -1;
+ n->reluctant = false;
+ n->reluctant_location = -1;
n->children = NIL;
n->location = @1;
$$ = (Node *) n;
@@ -17035,7 +17039,8 @@ row_pattern_primary:
n->children = list_make1(inner);
n->min = 1;
n->max = 1;
- n->reluctant = -1;
+ n->reluctant = false;
+ n->reluctant_location = -1;
n->location = @1;
$$ = (Node *) n;
}
@@ -20400,14 +20405,15 @@ makeRecursiveViewSelect(char *relname, List *aliases, Node *query)
* Create an RPRPatternNode with specified quantifier bounds.
*/
static RPRPatternNode *
-makeRPRQuantifier(int min, int max, ParseLoc reluctant, int location,
+makeRPRQuantifier(int min, int max, ParseLoc reluctant_location, int location,
core_yyscan_t yyscanner)
{
RPRPatternNode *n = makeNode(RPRPatternNode);
n->min = min;
n->max = max;
- n->reluctant = reluctant;
+ n->reluctant = (reluctant_location >= 0);
+ n->reluctant_location = reluctant_location;
n->location = location;
return n;
}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index f5bb81e8c05..928bed2e9fb 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -6778,7 +6778,7 @@ append_pattern_quantifier(StringInfo buf, RPRPatternNode *node)
else
appendStringInfo(buf, "{%d,%d}", node->min, node->max);
- if (node->reluctant >= 0)
+ if (node->reluctant)
{
if (!has_quantifier)
appendStringInfo(buf, "{1}"); /* make reluctant ? unambiguous */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 8af527b57d3..22e856c671d 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -608,8 +608,8 @@ typedef struct RPRPatternNode
RPRPatternNodeType nodeType; /* VAR, SEQ, ALT, GROUP */
int min; /* minimum repetitions (0 for *, ?) */
int max; /* maximum repetitions (INT_MAX for *, +) */
- ParseLoc reluctant; /* location of '?' for reluctant, -1 for
- * greedy */
+ bool reluctant; /* true for reluctant (non-greedy) */
+ ParseLoc reluctant_location; /* location of '?' token, or -1 */
ParseLoc location; /* token location, or -1 */
char *varName; /* VAR: variable name */
List *children; /* SEQ, ALT, GROUP: child nodes */
--
2.50.1 (Apple Git-155)
[text/plain] nocfbot-0003-expand-rpr-test-coverage-and-improve-test-comments.txt (265.5K, 5-nocfbot-0003-expand-rpr-test-coverage-and-improve-test-comments.txt)
download
[text/plain] nocfbot-0004-keep-rpr-test-objects-for-pg_upgrade-pg_dump-testing.txt (150.8K, 6-nocfbot-0004-keep-rpr-test-objects-for-pg_upgrade-pg_dump-testing.txt)
download
[text/plain] nocfbot-0005-disable-run-condition-pushdown-for-rpr-windows.txt (1.6K, 7-nocfbot-0005-disable-run-condition-pushdown-for-rpr-windows.txt)
download | inline diff:
From 81a9b83b9d61704e85d97bc79b10ac6a00d73def Mon Sep 17 00:00:00 2001
From: Henson Choi <[email protected]>
Date: Wed, 4 Mar 2026 15:53:33 +0900
Subject: [PATCH 5/8] Disable run condition pushdown for RPR windows
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 90275e25872..b67c35af39a 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -2453,6 +2453,17 @@ find_window_run_conditions(Query *subquery, AttrNumber attno,
wclause = (WindowClause *) list_nth(subquery->windowClause,
wfunc->winref - 1);
+ /*
+ * If a DEFINE clause exists, we cannot push down a run condition. In the
+ * case, a window partition (or frame) is divided into multiple reduced
+ * frames and each frame should be evaluated to the end of the partition
+ * (or full frame end). This means we cannot apply the run condition
+ * optimization because it stops evaluation window functions in certain
+ * cases.
+ */
+ if (wclause->defineClause != NIL)
+ return false;
+
req.type = T_SupportRequestWFuncMonotonic;
req.window_func = wfunc;
req.window_clause = wclause;
diff --git a/src/test/regress/expected/rpr_base.out b/src/test/regress/expected/rpr_base.out
index ae6d9c9b937..a7c536625f1 100644
--- a/src/test/regress/expected/rpr_base.out
+++ b/src/test/regress/expected/rpr_base.out
@@ -2465,7 +2465,9 @@ WHERE cnt > 0
ORDER BY id;
id | val | cnt
----+-----+-----
-(0 rows)
+ 2 | 20 | 2
+ 4 | 40 | 1
+(2 rows)
-- Nested subqueries
SELECT *
--
2.50.1 (Apple Git-155)
[text/plain] nocfbot-0006-disable-frame-optimization-for-rpr-windows.txt (8.3K, 8-nocfbot-0006-disable-frame-optimization-for-rpr-windows.txt)
download | inline diff:
From 08910348e4c3770880fa098b50596805b15e38f7 Mon Sep 17 00:00:00 2001
From: Henson Choi <[email protected]>
Date: Wed, 4 Mar 2026 15:59:45 +0900
Subject: [PATCH 6/8] Disable frame optimization for RPR windows
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 04faf919033..29a02f3affb 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -5907,6 +5907,14 @@ optimize_window_clauses(PlannerInfo *root, WindowFuncLists *wflists)
if (wflists->windowFuncs[wc->winref] == NIL)
continue;
+ /*
+ * If a DEFINE clause exists, do not let support functions replace the
+ * frame with a non-RPR-compatible one. RPR windows require ROWS
+ * BETWEEN CURRENT ROW AND ...
+ */
+ if (wc->defineClause != NIL)
+ continue;
+
foreach(lc2, wflists->windowFuncs[wc->winref])
{
SupportRequestOptimizeWindowClause req;
diff --git a/src/test/regress/expected/rpr_explain.out b/src/test/regress/expected/rpr_explain.out
index 3c70a12874a..ef184b7950b 100644
--- a/src/test/regress/expected/rpr_explain.out
+++ b/src/test/regress/expected/rpr_explain.out
@@ -3821,3 +3821,117 @@ WINDOW w AS (
-> Function Scan on generate_series s (actual rows=500.00 loops=1)
(9 rows)
+--
+-- Planner optimization: optimize_window_clauses must not alter RPR frame
+--
+-- optimize_window_clauses() replaces frame options via prosupport functions.
+-- Affected functions: row_number, rank, dense_rank, percent_rank, cume_dist,
+-- ntile. All would change the frame to ROWS UNBOUNDED PRECEDING, breaking
+-- RPR's required ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING.
+-- Test with row_number() as representative case.
+--
+-- Without RPR: row_number() frame is optimized to ROWS UNBOUNDED PRECEDING
+CREATE VIEW rpr_ev87 AS
+SELECT row_number() OVER w
+FROM generate_series(1, 10) AS s(v)
+WINDOW w AS (
+ ORDER BY v
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+);
+EXPLAIN (COSTS OFF) SELECT * FROM rpr_ev87;
+ QUERY PLAN
+--------------------------------------------------------------
+ Subquery Scan on rpr_ev87
+ -> WindowAgg
+ Window: w AS (ORDER BY s.v ROWS UNBOUNDED PRECEDING)
+ -> Sort
+ Sort Key: s.v
+ -> Function Scan on generate_series s
+(6 rows)
+
+-- With RPR: frame must remain ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+CREATE VIEW rpr_ev88 AS
+SELECT row_number() OVER w
+FROM generate_series(1, 10) AS s(v)
+WINDOW w AS (
+ ORDER BY v
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A B+)
+ DEFINE
+ B AS v > PREV(v)
+);
+EXPLAIN (COSTS OFF) SELECT * FROM rpr_ev88;
+ QUERY PLAN
+--------------------------------------------------------------------------------------
+ Subquery Scan on rpr_ev88
+ -> WindowAgg
+ Window: w AS (ORDER BY s.v ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+ Pattern: a b+
+ -> Sort
+ Sort Key: s.v
+ -> Function Scan on generate_series s
+(7 rows)
+
+--
+-- Planner optimization: find_window_run_conditions must not push down
+-- RPR window function results as Run Conditions.
+--
+-- find_window_run_conditions() pushes WHERE filters on monotonic window
+-- functions into WindowAgg as Run Conditions for early termination.
+-- With RPR's required frame (ROWS BETWEEN CURRENT ROW AND UNBOUNDED
+-- FOLLOWING), the monotonic direction determines which operators trigger
+-- Run Condition pushdown:
+-- INCREASING (<=): row_number, rank, dense_rank, percent_rank,
+-- cume_dist, ntile
+-- DECREASING (>): count(*) (via int8inc, END_UNBOUNDED_FOLLOWING)
+-- RPR window function results are match-dependent, not monotonic.
+-- Test with count(*) > 0 as representative case.
+--
+-- Without RPR: count(*) > 0 is pushed down as Run Condition
+EXPLAIN (COSTS OFF)
+SELECT * FROM (
+ SELECT count(*) OVER w AS cnt
+ FROM generate_series(1, 10) AS s(v)
+ WINDOW w AS (
+ ORDER BY v
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ )
+) t WHERE cnt > 0;
+ QUERY PLAN
+--------------------------------------------------------------------------------------
+ Subquery Scan on t
+ -> WindowAgg
+ Window: w AS (ORDER BY s.v ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+ Run Condition: (count(*) OVER w > 0)
+ -> Sort
+ Sort Key: s.v
+ -> Function Scan on generate_series s
+(7 rows)
+
+-- With RPR: count(*) > 0 must not be pushed down as Run Condition
+EXPLAIN (COSTS OFF)
+SELECT * FROM (
+ SELECT count(*) OVER w AS cnt
+ FROM generate_series(1, 10) AS s(v)
+ WINDOW w AS (
+ ORDER BY v
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A B+)
+ DEFINE
+ B AS v > PREV(v)
+ )
+) t WHERE cnt > 0;
+ QUERY PLAN
+--------------------------------------------------------------------------------------
+ Subquery Scan on t
+ Filter: (t.cnt > 0)
+ -> WindowAgg
+ Window: w AS (ORDER BY s.v ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
+ Pattern: a b+
+ -> Sort
+ Sort Key: s.v
+ -> Function Scan on generate_series s
+(8 rows)
+
diff --git a/src/test/regress/sql/rpr_explain.sql b/src/test/regress/sql/rpr_explain.sql
index 8e22382a68e..640d328957b 100644
--- a/src/test/regress/sql/rpr_explain.sql
+++ b/src/test/regress/sql/rpr_explain.sql
@@ -2196,3 +2196,81 @@ WINDOW w AS (
E AS v % 100 = 5
);');
+--
+-- Planner optimization: optimize_window_clauses must not alter RPR frame
+--
+-- optimize_window_clauses() replaces frame options via prosupport functions.
+-- Affected functions: row_number, rank, dense_rank, percent_rank, cume_dist,
+-- ntile. All would change the frame to ROWS UNBOUNDED PRECEDING, breaking
+-- RPR's required ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING.
+-- Test with row_number() as representative case.
+--
+
+-- Without RPR: row_number() frame is optimized to ROWS UNBOUNDED PRECEDING
+CREATE VIEW rpr_ev87 AS
+SELECT row_number() OVER w
+FROM generate_series(1, 10) AS s(v)
+WINDOW w AS (
+ ORDER BY v
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+);
+
+EXPLAIN (COSTS OFF) SELECT * FROM rpr_ev87;
+
+-- With RPR: frame must remain ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+CREATE VIEW rpr_ev88 AS
+SELECT row_number() OVER w
+FROM generate_series(1, 10) AS s(v)
+WINDOW w AS (
+ ORDER BY v
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A B+)
+ DEFINE
+ B AS v > PREV(v)
+);
+
+EXPLAIN (COSTS OFF) SELECT * FROM rpr_ev88;
+
+--
+-- Planner optimization: find_window_run_conditions must not push down
+-- RPR window function results as Run Conditions.
+--
+-- find_window_run_conditions() pushes WHERE filters on monotonic window
+-- functions into WindowAgg as Run Conditions for early termination.
+-- With RPR's required frame (ROWS BETWEEN CURRENT ROW AND UNBOUNDED
+-- FOLLOWING), the monotonic direction determines which operators trigger
+-- Run Condition pushdown:
+-- INCREASING (<=): row_number, rank, dense_rank, percent_rank,
+-- cume_dist, ntile
+-- DECREASING (>): count(*) (via int8inc, END_UNBOUNDED_FOLLOWING)
+-- RPR window function results are match-dependent, not monotonic.
+-- Test with count(*) > 0 as representative case.
+--
+
+-- Without RPR: count(*) > 0 is pushed down as Run Condition
+EXPLAIN (COSTS OFF)
+SELECT * FROM (
+ SELECT count(*) OVER w AS cnt
+ FROM generate_series(1, 10) AS s(v)
+ WINDOW w AS (
+ ORDER BY v
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ )
+) t WHERE cnt > 0;
+
+-- With RPR: count(*) > 0 must not be pushed down as Run Condition
+EXPLAIN (COSTS OFF)
+SELECT * FROM (
+ SELECT count(*) OVER w AS cnt
+ FROM generate_series(1, 10) AS s(v)
+ WINDOW w AS (
+ ORDER BY v
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ PATTERN (A B+)
+ DEFINE
+ B AS v > PREV(v)
+ )
+) t WHERE cnt > 0;
+
--
2.50.1 (Apple Git-155)
[text/plain] nocfbot-0007-add-stock-scenario-tests-for-rpr-pattern-matching.txt (103.5K, 9-nocfbot-0007-add-stock-scenario-tests-for-rpr-pattern-matching.txt)
download
[text/plain] nocfbot-0008-fix-zero-min-reluctant-quantifier-to-produce-zero-le.txt (15.2K, 10-nocfbot-0008-fix-zero-min-reluctant-quantifier-to-produce-zero-le.txt)
download | inline diff:
From d13cf490ce7585fd99a7690911a75d90a51017bb Mon Sep 17 00:00:00 2001
From: Henson Choi <[email protected]>
Date: Wed, 4 Mar 2026 14:08:47 +0900
Subject: [PATCH 8/8] Fix zero-min reluctant quantifier to produce zero-length
match
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 4441b4d9c51..3075c51789c 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -307,24 +307,24 @@ static inline bool nfa_eval_var_match(WindowAggState *winstate,
static void nfa_match(WindowAggState *winstate, RPRNFAContext *ctx,
bool *varMatched);
static void nfa_advance_state(WindowAggState *winstate, RPRNFAContext *ctx,
- RPRNFAState *state, int64 currentPos, bool initialAdvance);
+ RPRNFAState *state, int64 currentPos);
static void nfa_route_to_elem(WindowAggState *winstate, RPRNFAContext *ctx,
RPRNFAState *state, RPRPatternElement *nextElem,
- int64 currentPos, bool initialAdvance);
+ int64 currentPos);
static void nfa_advance_alt(WindowAggState *winstate, RPRNFAContext *ctx,
RPRNFAState *state, RPRPatternElement *elem,
- int64 currentPos, bool initialAdvance);
+ int64 currentPos);
static void nfa_advance_begin(WindowAggState *winstate, RPRNFAContext *ctx,
RPRNFAState *state, RPRPatternElement *elem,
- int64 currentPos, bool initialAdvance);
+ int64 currentPos);
static void nfa_advance_end(WindowAggState *winstate, RPRNFAContext *ctx,
RPRNFAState *state, RPRPatternElement *elem,
- int64 currentPos, bool initialAdvance);
+ int64 currentPos);
static void nfa_advance_var(WindowAggState *winstate, RPRNFAContext *ctx,
RPRNFAState *state, RPRPatternElement *elem,
- int64 currentPos, bool initialAdvance);
+ int64 currentPos);
static void nfa_advance(WindowAggState *winstate, RPRNFAContext *ctx,
- int64 currentPos, bool initialAdvance);
+ int64 currentPos);
/*
* Not null info bit array consists of 2-bit items
@@ -5008,9 +5008,14 @@ register_result:
* handles nested groups like "((A|B)+)+" correctly - exiting the inner
* group counts as one iteration of the outer group.
*
- * - initialAdvance flag: The first advance after context creation must
- * skip FIN recording. Reaching FIN without evaluating any VAR would
- * create a zero-length match, which is invalid.
+ * - Zero-length match handling: The initial advance uses currentPos =
+ * startPos - 1 (before any row is consumed). If FIN is reached via
+ * epsilon transitions alone, matchEndRow = startPos - 1 < matchStartRow,
+ * resulting in UNMATCHED. For reluctant min=0 patterns (A*?, A??),
+ * the skip path reaches FIN first and early termination prunes enter
+ * paths, yielding an immediate zero-length (unmatched) result. For
+ * greedy patterns (A*), the enter path adds VAR states first, then
+ * the skip FIN is recorded but VAR states survive for later matching.
*
* Context Absorption Runtime:
* ---------------------------
@@ -5122,7 +5127,7 @@ nfa_process_row(WindowAggState *winstate, int64 currentPos,
Assert(!hasLimitedFrame ||
currentPos < ctx->matchStartRow + frameOffset + 1);
- nfa_advance(winstate, ctx, currentPos, false);
+ nfa_advance(winstate, ctx, currentPos);
}
}
@@ -5467,10 +5472,11 @@ nfa_start_context(WindowAggState *winstate, int64 startPos)
* states for VAR elements with min=0. This prepares the context for the
* first row's match phase.
*
- * Pass initialAdvance=true to prevent recording zero-length matches when
- * optional patterns can skip all VARs to reach FIN immediately.
+ * Use startPos - 1 as currentPos since no row has been consumed yet. If
+ * FIN is reached via epsilon transitions, matchEndRow = startPos - 1
+ * which is less than matchStartRow, resulting in UNMATCHED treatment.
*/
- nfa_advance(winstate, ctx, startPos, true);
+ nfa_advance(winstate, ctx, startPos - 1);
return ctx;
}
@@ -5711,7 +5717,7 @@ nfa_finalize_all_contexts(WindowAggState *winstate, int64 lastPos)
if (ctx->states != NULL)
{
nfa_match(winstate, ctx, NULL);
- nfa_advance(winstate, ctx, lastPos, false);
+ nfa_advance(winstate, ctx, lastPos);
}
}
}
@@ -6047,7 +6053,7 @@ nfa_match(WindowAggState *winstate, RPRNFAContext *ctx, bool *varMatched)
static void
nfa_route_to_elem(WindowAggState *winstate, RPRNFAContext *ctx,
RPRNFAState *state, RPRPatternElement *nextElem,
- int64 currentPos, bool initialAdvance)
+ int64 currentPos)
{
if (RPRElemIsVar(nextElem))
{
@@ -6061,11 +6067,11 @@ nfa_route_to_elem(WindowAggState *winstate, RPRNFAContext *ctx,
nfa_add_state_unique(winstate, ctx, state);
if (skipState != NULL)
- nfa_advance_state(winstate, ctx, skipState, currentPos, initialAdvance);
+ nfa_advance_state(winstate, ctx, skipState, currentPos);
}
else
{
- nfa_advance_state(winstate, ctx, state, currentPos, initialAdvance);
+ nfa_advance_state(winstate, ctx, state, currentPos);
}
}
@@ -6077,7 +6083,7 @@ nfa_route_to_elem(WindowAggState *winstate, RPRNFAContext *ctx,
static void
nfa_advance_alt(WindowAggState *winstate, RPRNFAContext *ctx,
RPRNFAState *state, RPRPatternElement *elem,
- int64 currentPos, bool initialAdvance)
+ int64 currentPos)
{
RPRPattern *pattern = winstate->rpPattern;
RPRPatternElement *elements = pattern->elements;
@@ -6097,7 +6103,7 @@ nfa_advance_alt(WindowAggState *winstate, RPRNFAContext *ctx,
state->counts, state->isAbsorbable);
/* Recursively process this branch before next */
- nfa_advance_state(winstate, ctx, newState, currentPos, initialAdvance);
+ nfa_advance_state(winstate, ctx, newState, currentPos);
altIdx = altElem->jump;
}
@@ -6115,7 +6121,7 @@ nfa_advance_alt(WindowAggState *winstate, RPRNFAContext *ctx,
static void
nfa_advance_begin(WindowAggState *winstate, RPRNFAContext *ctx,
RPRNFAState *state, RPRPatternElement *elem,
- int64 currentPos, bool initialAdvance)
+ int64 currentPos)
{
RPRPattern *pattern = winstate->rpPattern;
RPRPatternElement *elements = pattern->elements;
@@ -6136,7 +6142,7 @@ nfa_advance_begin(WindowAggState *winstate, RPRNFAContext *ctx,
/* Reluctant: skip first (prefer fewer iterations), enter second */
nfa_route_to_elem(winstate, ctx, skipState,
- &elements[elem->jump], currentPos, initialAdvance);
+ &elements[elem->jump], currentPos);
/*
* If skip path reached FIN, shortest match is found. Skip group entry
@@ -6150,19 +6156,19 @@ nfa_advance_begin(WindowAggState *winstate, RPRNFAContext *ctx,
state->elemIdx = elem->next;
nfa_route_to_elem(winstate, ctx, state,
- &elements[state->elemIdx], currentPos, initialAdvance);
+ &elements[state->elemIdx], currentPos);
}
else
{
/* Greedy: enter first, skip second */
state->elemIdx = elem->next;
nfa_route_to_elem(winstate, ctx, state,
- &elements[state->elemIdx], currentPos, initialAdvance);
+ &elements[state->elemIdx], currentPos);
if (skipState != NULL)
{
nfa_route_to_elem(winstate, ctx, skipState,
- &elements[elem->jump], currentPos, initialAdvance);
+ &elements[elem->jump], currentPos);
}
}
}
@@ -6176,7 +6182,7 @@ nfa_advance_begin(WindowAggState *winstate, RPRNFAContext *ctx,
static void
nfa_advance_end(WindowAggState *winstate, RPRNFAContext *ctx,
RPRNFAState *state, RPRPatternElement *elem,
- int64 currentPos, bool initialAdvance)
+ int64 currentPos)
{
RPRPattern *pattern = winstate->rpPattern;
RPRPatternElement *elements = pattern->elements;
@@ -6199,7 +6205,7 @@ nfa_advance_end(WindowAggState *winstate, RPRNFAContext *ctx,
state->elemIdx = elem->jump;
jumpElem = &elements[state->elemIdx];
nfa_route_to_elem(winstate, ctx, state, jumpElem,
- currentPos, initialAdvance);
+ currentPos);
/*
* Fast-forward fallback for nullable bodies. E.g. (A?){2,3} when A
@@ -6223,7 +6229,7 @@ nfa_advance_end(WindowAggState *winstate, RPRNFAContext *ctx,
ffState->counts[nextElem->depth]++;
nfa_route_to_elem(winstate, ctx, ffState, nextElem,
- currentPos, initialAdvance);
+ currentPos);
}
}
else if (elem->max != RPR_QUANTITY_INF && count >= elem->max)
@@ -6239,7 +6245,7 @@ nfa_advance_end(WindowAggState *winstate, RPRNFAContext *ctx,
if (RPRElemIsEnd(nextElem) && state->counts[nextElem->depth] < RPR_COUNT_MAX)
state->counts[nextElem->depth]++;
- nfa_route_to_elem(winstate, ctx, state, nextElem, currentPos, initialAdvance);
+ nfa_route_to_elem(winstate, ctx, state, nextElem, currentPos);
}
else
{
@@ -6277,7 +6283,7 @@ nfa_advance_end(WindowAggState *winstate, RPRNFAContext *ctx,
/* Exit first (preferred for reluctant) */
nfa_route_to_elem(winstate, ctx, exitState, nextElem,
- currentPos, initialAdvance);
+ currentPos);
/*
* If exit path reached FIN, shortest match is found. Skip loop to
@@ -6291,16 +6297,16 @@ nfa_advance_end(WindowAggState *winstate, RPRNFAContext *ctx,
/* Loop second */
nfa_route_to_elem(winstate, ctx, state, jumpElem,
- currentPos, initialAdvance);
+ currentPos);
}
else
{
/* Loop first (preferred for greedy) */
nfa_route_to_elem(winstate, ctx, state, jumpElem,
- currentPos, initialAdvance);
+ currentPos);
/* Exit second */
nfa_route_to_elem(winstate, ctx, exitState, nextElem,
- currentPos, initialAdvance);
+ currentPos);
}
}
}
@@ -6314,7 +6320,7 @@ nfa_advance_end(WindowAggState *winstate, RPRNFAContext *ctx,
static void
nfa_advance_var(WindowAggState *winstate, RPRNFAContext *ctx,
RPRNFAState *state, RPRPatternElement *elem,
- int64 currentPos, bool initialAdvance)
+ int64 currentPos)
{
RPRPattern *pattern = winstate->rpPattern;
RPRPatternElement *elements = pattern->elements;
@@ -6359,7 +6365,7 @@ nfa_advance_var(WindowAggState *winstate, RPRNFAContext *ctx,
/* Exit first (preferred for reluctant) */
nfa_route_to_elem(winstate, ctx, cloneState, nextElem,
- currentPos, initialAdvance);
+ currentPos);
/*
* If exit path reached FIN, the shortest match is found. Skip
@@ -6400,7 +6406,7 @@ nfa_advance_var(WindowAggState *winstate, RPRNFAContext *ctx,
}
nfa_route_to_elem(winstate, ctx, state, nextElem,
- currentPos, initialAdvance);
+ currentPos);
}
}
else if (canLoop)
@@ -6424,7 +6430,7 @@ nfa_advance_var(WindowAggState *winstate, RPRNFAContext *ctx,
state->counts[nextElem->depth]++;
}
- nfa_route_to_elem(winstate, ctx, state, nextElem, currentPos, initialAdvance);
+ nfa_route_to_elem(winstate, ctx, state, nextElem, currentPos);
}
}
@@ -6436,7 +6442,7 @@ nfa_advance_var(WindowAggState *winstate, RPRNFAContext *ctx,
*/
static void
nfa_advance_state(WindowAggState *winstate, RPRNFAContext *ctx,
- RPRNFAState *state, int64 currentPos, bool initialAdvance)
+ RPRNFAState *state, int64 currentPos)
{
RPRPattern *pattern = winstate->rpPattern;
RPRPatternElement *elem;
@@ -6458,28 +6464,25 @@ nfa_advance_state(WindowAggState *winstate, RPRNFAContext *ctx,
switch (elem->varId)
{
case RPR_VARID_FIN:
- /* FIN: record match (skip for initial advance) */
- if (!initialAdvance)
- nfa_add_matched_state(winstate, ctx, state, currentPos);
- else
- nfa_state_free(winstate, state);
+ /* FIN: record match */
+ nfa_add_matched_state(winstate, ctx, state, currentPos);
break;
case RPR_VARID_ALT:
- nfa_advance_alt(winstate, ctx, state, elem, currentPos, initialAdvance);
+ nfa_advance_alt(winstate, ctx, state, elem, currentPos);
break;
case RPR_VARID_BEGIN:
- nfa_advance_begin(winstate, ctx, state, elem, currentPos, initialAdvance);
+ nfa_advance_begin(winstate, ctx, state, elem, currentPos);
break;
case RPR_VARID_END:
- nfa_advance_end(winstate, ctx, state, elem, currentPos, initialAdvance);
+ nfa_advance_end(winstate, ctx, state, elem, currentPos);
break;
default:
/* VAR element */
- nfa_advance_var(winstate, ctx, state, elem, currentPos, initialAdvance);
+ nfa_advance_var(winstate, ctx, state, elem, currentPos);
break;
}
}
@@ -6489,13 +6492,12 @@ nfa_advance_state(WindowAggState *winstate, RPRNFAContext *ctx,
*
* Advance phase (divergence): transition from all surviving states.
* Called after match phase with matched VAR states, or at context creation
- * for initial epsilon expansion (initialAdvance=true skips FIN matches).
+ * for initial epsilon expansion (with currentPos = startPos - 1).
*
* Processes states in order, using recursive DFS to maintain lexical order.
*/
static void
-nfa_advance(WindowAggState *winstate, RPRNFAContext *ctx, int64 currentPos,
- bool initialAdvance)
+nfa_advance(WindowAggState *winstate, RPRNFAContext *ctx, int64 currentPos)
{
RPRNFAState *states = ctx->states;
RPRNFAState *state;
@@ -6515,7 +6517,7 @@ nfa_advance(WindowAggState *winstate, RPRNFAContext *ctx, int64 currentPos,
states = states->next;
state->next = NULL;
- nfa_advance_state(winstate, ctx, state, currentPos, initialAdvance);
+ nfa_advance_state(winstate, ctx, state, currentPos);
/*
* Early termination: if a FIN was newly reached in this advance,
diff --git a/src/test/regress/expected/rpr_base.out b/src/test/regress/expected/rpr_base.out
index a7c536625f1..2fefb933c71 100644
--- a/src/test/regress/expected/rpr_base.out
+++ b/src/test/regress/expected/rpr_base.out
@@ -1090,9 +1090,9 @@ WINDOW w AS (
);
count
-------
- 1
- 1
- 1
+ 0
+ 0
+ 0
(3 rows)
-- Reluctant quantifier: prefer shortest match
@@ -1124,9 +1124,9 @@ WINDOW w AS (
);
count
-------
- 1
- 1
- 1
+ 0
+ 0
+ 0
(3 rows)
-- Reluctant quantifier: prefer shortest match
@@ -1192,9 +1192,9 @@ WINDOW w AS (
);
count
-------
- 1
- 1
- 1
+ 0
+ 0
+ 0
(3 rows)
-- Reluctant quantifier: prefer shortest match
@@ -1382,9 +1382,9 @@ WINDOW w AS (
);
count
-------
- 1
- 1
- 1
+ 0
+ 0
+ 0
(3 rows)
-- Reluctant quantifier: prefer shortest match
@@ -1460,9 +1460,9 @@ WINDOW w AS (
);
count
-------
- 1
- 1
- 1
+ 0
+ 0
+ 0
(3 rows)
-- Reluctant quantifier: prefer shortest match
diff --git a/src/test/regress/expected/rpr_explain.out b/src/test/regress/expected/rpr_explain.out
index ef184b7950b..d5cd80f423e 100644
--- a/src/test/regress/expected/rpr_explain.out
+++ b/src/test/regress/expected/rpr_explain.out
@@ -3347,7 +3347,7 @@ WINDOW w AS (
Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
Pattern: ((a' b')+" c)*
Storage: Memory Maximum Storage: NkB
- NFA States: 7 peak, 178 total, 0 merged
+ NFA States: 9 peak, 178 total, 0 merged
NFA Contexts: 4 peak, 61 total, 22 pruned
NFA: 1 matched (len 57/57/57.0), 0 mismatched
NFA: 0 absorbed, 37 skipped (len 1/3/2.0)
@@ -3384,7 +3384,7 @@ WINDOW w AS (
Window: w AS (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
Pattern: (a (b c)+)*
Storage: Memory Maximum Storage: NkB
- NFA States: 5 peak, 160 total, 0 merged
+ NFA States: 7 peak, 160 total, 0 merged
NFA Contexts: 4 peak, 61 total, 22 pruned
NFA: 1 matched (len 57/57/57.0), 0 mismatched
NFA: 0 absorbed, 37 skipped (len 1/3/2.0)
--
2.50.1 (Apple Git-155)
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: [email protected]
Cc: [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected]
Subject: Re: Row pattern recognition
In-Reply-To: <CAAAe_zBH=6nh6Yg9EuohthTgzHgtbhtvLYAnabg6mCHQPLLpqQ@mail.gmail.com>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox