v47-0003-v47-refactor-many-functions-in-rpr.c.nocfbot
application/octet-stream
Filename: v47-0003-v47-refactor-many-functions-in-rpr.c.nocfbot
Type: application/octet-stream
Part: 3
Message:
Re: Row pattern recognition
From 7b92e4fc46a1737ad1d7669a5b3dc52eed98e293 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Tue, 9 Jun 2026 16:50:20 +0800
Subject: [PATCH v47 3/4] v47 refactor many functions in rpr.c
In rpr.c, we have many newly constructed list: Each function built a new list
and returned it, requiring the caller to assign the result back to
pattern->children. Replace these with in-place variants that mutate the
existing list directly using list_delete_nth_cell(), avoiding unnecessary list
allocations:
mergeConsecutiveVars -> mergeConsecutiveVars1
mergeConsecutiveGroups -> mergeConsecutiveGroups1
mergeConsecutiveAlts -> mergeConsecutiveAlts1
mergeGroupPrefixSuffix -> mergeGroupPrefixSuffix1
flattenAltChildren -> flattenAltChildren1
removeDuplicateAlts -> removeDuplicateAlternatives1
I left the original functions intact and tagged them with pg_attribute_unused()
to help reviewers easily compare the two approaches.
Also simplify optimizeGroupPattern to update lfirst(lc) in place
rather than accumulating a separate newChildren list.
In tryMultiplyQuantifiers, replace the int64 cast-and-compare
overflow guard with pg_mul_s32_overflow(), consistent with how
pg_add_s32_overflow() is used elsewhere in the same file.
We can also optimize flattenSeqChildren using list_insert_nth, but
list_insert_nth have comments like:
/*
* Insert the given datum at position 'pos' (measured from 0) in the list.
* 'pos' must be valid, ie, 0 <= pos <= list's length.
*
* Note that this takes time proportional to the distance to the end of the
* list, since the following entries must be moved.
*/
---
src/backend/optimizer/plan/rpr.c | 452 +++++++++++++++++++++++--
src/test/regress/expected/rpr_base.out | 36 ++
src/test/regress/sql/rpr_base.sql | 16 +
3 files changed, 478 insertions(+), 26 deletions(-)
diff --git a/src/backend/optimizer/plan/rpr.c b/src/backend/optimizer/plan/rpr.c
index 664c942e57..3d9a4afe37 100644
--- a/src/backend/optimizer/plan/rpr.c
+++ b/src/backend/optimizer/plan/rpr.c
@@ -39,6 +39,7 @@
#include <limits.h>
+#include "common/int.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
@@ -57,10 +58,18 @@ static List *mergeConsecutiveAlts(List *children);
static List *mergeGroupPrefixSuffix(List *children);
static RPRPatternNode *optimizeSeqPattern(RPRPatternNode *pattern);
+static void mergeConsecutiveVars1(List *children);
+static void mergeConsecutiveGroups1(List *children);
+static void mergeConsecutiveAlts1(List **children);
+static void mergeGroupPrefixSuffix1(List *children);
+
static List *flattenAltChildren(List *children);
static List *removeDuplicateAlternatives(List *children);
static RPRPatternNode *optimizeAltPattern(RPRPatternNode *pattern);
+static void flattenAltChildren1(List **children);
+static void removeDuplicateAlternatives1(List *children);
+
static RPRPatternNode *tryMultiplyQuantifiers(RPRPatternNode *pattern);
static RPRPatternNode *tryUnwrapGroup(RPRPatternNode *pattern);
static RPRPatternNode *optimizeGroupPattern(RPRPatternNode *pattern);
@@ -214,6 +223,62 @@ flattenSeqChildren(List *children)
return newChildren;
}
+static void
+mergeConsecutiveVars1(List *children)
+{
+ for (int outerpos = 0; outerpos < list_length(children); outerpos++)
+ {
+ RPRPatternNode *rprpattern = list_nth_node(RPRPatternNode, children, outerpos);
+
+ if (rprpattern->nodeType != RPR_PATTERN_VAR ||
+ rprpattern->reluctant)
+ continue;
+
+ for (int restpos = outerpos + 1; restpos < list_length(children);)
+ {
+ RPRPatternNode *other;
+ int newmin;
+ int newmax = 0;
+
+ other = list_nth_node(RPRPatternNode, children, restpos);
+
+ if (other->nodeType != RPR_PATTERN_VAR)
+ break;
+
+ if (strcmp(rprpattern->varName, other->varName) != 0)
+ break;
+
+ if (rprpattern->max == RPR_QUANTITY_INF ||
+ other->max == RPR_QUANTITY_INF)
+ newmax = RPR_QUANTITY_INF;
+
+ if (pg_add_s32_overflow(rprpattern->min, other->min, &newmin))
+ break;
+
+ if (newmax != RPR_QUANTITY_INF &&
+ pg_add_s32_overflow(rprpattern->max, other->max, &newmax))
+ break;
+
+ if (newmin < RPR_QUANTITY_INF &&
+ newmax <= RPR_QUANTITY_INF)
+ {
+ rprpattern->min = newmin;
+ rprpattern->max = newmax;
+
+ /*
+ * Allow combining a non-reluctant preceding VAR with a
+ * reluctant current VAR. The resulting combination takes its
+ * reluctance property from the current VAR.
+ */
+ rprpattern->reluctant = other->reluctant;
+
+ children = list_delete_nth_cell(children, restpos);
+ }
+ else
+ break;
+ }
+ }
+}
/*
* mergeConsecutiveVars
@@ -224,6 +289,7 @@ flattenSeqChildren(List *children)
*
* Only merges non-reluctant VAR nodes with the same variable name.
*/
+pg_attribute_unused()
static List *
mergeConsecutiveVars(List *children)
{
@@ -289,6 +355,63 @@ mergeConsecutiveVars(List *children)
return mergedChildren;
}
+static void
+mergeConsecutiveGroups1(List *children)
+{
+ for (int outerpos = 0; outerpos < list_length(children); outerpos++)
+ {
+ RPRPatternNode *rprpattern = list_nth_node(RPRPatternNode, children, outerpos);
+
+ if (rprpattern->nodeType != RPR_PATTERN_GROUP ||
+ rprpattern->reluctant)
+ continue;;
+
+ for (int restpos = outerpos + 1; restpos < list_length(children);)
+ {
+ RPRPatternNode *other;
+ int newmin;
+ int newmax = 0;
+
+ other = list_nth_node(RPRPatternNode, children, restpos);
+
+ if (other->nodeType != RPR_PATTERN_GROUP)
+ break;
+
+ if (!rprPatternChildrenEqual(rprpattern->children, other->children))
+ break;
+
+ if (rprpattern->max == RPR_QUANTITY_INF ||
+ other->max == RPR_QUANTITY_INF)
+ newmax = RPR_QUANTITY_INF;
+
+ if (pg_add_s32_overflow(rprpattern->min, other->min, &newmin))
+ break;
+
+ if (newmax != RPR_QUANTITY_INF &&
+ pg_add_s32_overflow(rprpattern->max, other->max, &newmax))
+ break;
+
+ if (newmin < RPR_QUANTITY_INF &&
+ newmax <= RPR_QUANTITY_INF)
+ {
+ rprpattern->min = newmin;
+ rprpattern->max = newmax;
+
+ /*
+ * Allow combining a non-reluctant preceding GROUP with a
+ * reluctant current GROUP. The resulting combination takes
+ * its reluctance property from the current GROUP.
+ */
+ rprpattern->reluctant = other->reluctant;
+
+ children = list_delete_nth_cell(children, restpos);
+ }
+ else
+ break;
+ }
+ }
+}
+
/*
* mergeConsecutiveGroups
* Merge consecutive identical GROUP nodes.
@@ -298,6 +421,7 @@ mergeConsecutiveVars(List *children)
*
* Only merges non-reluctant GROUP nodes with identical children.
*/
+pg_attribute_unused()
static List *
mergeConsecutiveGroups(List *children)
{
@@ -362,6 +486,56 @@ mergeConsecutiveGroups(List *children)
return mergedChildren;
}
+static void
+mergeConsecutiveAlts1(List **children)
+{
+ int count = 1;
+
+ for (int outerpos = 0; outerpos < list_length(*children); outerpos++)
+ {
+ RPRPatternNode *rprpattern;
+
+ rprpattern = list_nth_node(RPRPatternNode, *children, outerpos);
+
+ if (rprpattern->nodeType != RPR_PATTERN_ALT ||
+ rprpattern->reluctant)
+ continue;
+
+ for (int restpos = outerpos + 1; restpos < list_length(*children);)
+ {
+ RPRPatternNode *other;
+
+ other = list_nth_node(RPRPatternNode, *children, restpos);
+
+ if (other->nodeType != RPR_PATTERN_ALT || other->reluctant)
+ break;
+
+ if (!rprPatternChildrenEqual(rprpattern->children,
+ other->children))
+ break;
+
+ *children = list_delete_nth_cell(*children, restpos);
+ count++;
+ }
+
+ if (count > 1)
+ {
+ /* Wrap it into GROUP {count, count}(ALT) */
+ RPRPatternNode *group = makeNode(RPRPatternNode);
+
+ group->nodeType = RPR_PATTERN_GROUP;
+ group->min = count;
+ group->max = count;
+ group->reluctant = false;
+ group->reluctant_location = -1;
+ group->location = -1;
+ group->children = list_make1(copyObject(rprpattern));
+ *children = list_delete_nth_cell(*children, outerpos);
+ *children = lcons(group, *children);
+ }
+ count = 1; /* reset */
+ }
+}
/*
* mergeConsecutiveAlts
@@ -374,6 +548,8 @@ mergeConsecutiveGroups(List *children)
* in the SEQ. This step detects consecutive identical ALT nodes and wraps
* them in a GROUP with the appropriate quantifier.
*/
+pg_attribute_unused()
+
static List *
mergeConsecutiveAlts(List *children)
{
@@ -489,6 +665,7 @@ mergeConsecutiveAlts(List *children)
* (A B)+ A B -> (A B){2,}
* A B (A B)+ A B -> (A B){3,}
*/
+pg_attribute_unused()
static List *
mergeGroupPrefixSuffix(List *children)
{
@@ -644,6 +821,167 @@ mergeGroupPrefixSuffix(List *children)
return result;
}
+static void
+mergeGroupPrefixSuffix1(List *children)
+{
+ int group_len;
+ List *prefixElements = NIL;
+ List *groupContent = NIL;
+ List *suffixElements = NIL;
+
+ for (int outerpos = 0; outerpos < list_length(children); outerpos++)
+ {
+ RPRPatternNode *rprpattern = list_nth_node(RPRPatternNode, children,
+ outerpos);
+
+ if (rprpattern->nodeType != RPR_PATTERN_GROUP ||
+ rprpattern->reluctant)
+ continue;
+
+ groupContent = rprpattern->children;
+ group_len = list_length(rprpattern->children);
+
+ /*
+ * If GROUP has single SEQ child, compare with SEQ's children. e.g.,
+ * (A B)+ internally contains sequence A B; compare against that.
+ */
+ if (group_len == 1)
+ {
+ RPRPatternNode *inner = (RPRPatternNode *) linitial(rprpattern->children);
+
+ if (inner->nodeType == RPR_PATTERN_SEQ)
+ {
+ group_len = list_length(inner->children);
+ groupContent = inner->children;
+ }
+ }
+
+ if (group_len > outerpos)
+ continue;
+
+ /*
+ * PATTERN (A B A B (A B)+) AS val > 50);
+ *
+ * PATTERN (A B C D (C D)+)
+ */
+ for (int restpos = outerpos % group_len;
+ restpos + group_len < list_length(children);)
+ {
+ int i;
+ int newmax;
+ int newmin;
+ bool matched;
+
+ for (i = restpos; i < restpos + group_len; i++)
+ prefixElements = lappend(prefixElements,
+ list_nth(children, i));
+
+ Assert(list_length(prefixElements) == group_len);
+
+ matched = rprPatternChildrenEqual(prefixElements, groupContent);
+ list_free(prefixElements);
+ prefixElements = NIL;
+
+ if (!matched)
+ {
+ restpos = restpos + group_len;
+ continue;
+ }
+
+ if (pg_add_s32_overflow(rprpattern->min, 1, &newmin))
+ {
+ restpos = restpos + group_len;
+ continue;
+ }
+
+ if (rprpattern->max == RPR_QUANTITY_INF)
+ newmax = RPR_QUANTITY_INF;
+ else if (pg_add_s32_overflow(rprpattern->max, 1, &newmax))
+ {
+ restpos = restpos + group_len;
+ continue;
+ }
+
+ rprpattern->max = newmax;
+ rprpattern->min = newmin;
+
+ for (; i > restpos; i--)
+ children = list_delete_nth_cell(children, i - 1);
+
+ /*
+ * No need advance restpos because we in-place delete list
+ * element, we need begin in the same position
+ */
+ }
+ }
+
+ for (int outerpos = 0; outerpos < list_length(children); outerpos++)
+ {
+ RPRPatternNode *rprpattern = list_nth_node(RPRPatternNode, children,
+ outerpos);
+
+ if (rprpattern->nodeType != RPR_PATTERN_GROUP ||
+ rprpattern->reluctant)
+ continue;
+
+ group_len = list_length(rprpattern->children);
+ groupContent = rprpattern->children;
+
+ /*
+ * If GROUP has single SEQ child, compare with SEQ's children. e.g.,
+ * (A B)+ internally contains sequence A B; compare against that.
+ */
+ if (group_len == 1)
+ {
+ RPRPatternNode *inner = (RPRPatternNode *) linitial(rprpattern->children);
+
+ if (inner->nodeType == RPR_PATTERN_SEQ)
+ {
+ group_len = list_length(inner->children);
+ groupContent = inner->children;
+ }
+ }
+
+ if (outerpos + group_len >= list_length(children))
+ continue;
+
+ for (int restpos = outerpos + 1; restpos + group_len <= list_length(children);)
+ {
+ int newmax;
+ int newmin;
+ bool matched;
+
+ for (int j = restpos; j < restpos + group_len; j++)
+ suffixElements = lappend(suffixElements,
+ list_nth(children, j));
+
+ Assert(list_length(suffixElements) == group_len);
+
+ matched = rprPatternChildrenEqual(suffixElements, groupContent);
+ list_free(suffixElements);
+ suffixElements = NIL;
+
+ if (!matched)
+ break;
+
+ if (pg_add_s32_overflow(rprpattern->min, 1, &newmin))
+ break;
+
+ if (rprpattern->max == RPR_QUANTITY_INF)
+ newmax = RPR_QUANTITY_INF;
+ else if (pg_add_s32_overflow(rprpattern->max, 1, &newmax))
+ break;
+
+ rprpattern->max = newmax;
+ rprpattern->min = newmin;
+
+ for (int j = restpos + group_len - 1; j >= restpos; j--)
+ children = list_delete_nth_cell(children, j);
+ }
+ }
+}
+
+
/*
* optimizeSeqPattern
* Optimize SEQ pattern node.
@@ -663,16 +1001,18 @@ optimizeSeqPattern(RPRPatternNode *pattern)
pattern->children = flattenSeqChildren(pattern->children);
/* Merge consecutive identical VAR nodes */
- pattern->children = mergeConsecutiveVars(pattern->children);
+ /* pattern->children = mergeConsecutiveVars(pattern->children); */
+ mergeConsecutiveVars1(pattern->children);
/* Merge consecutive identical GROUP nodes */
- pattern->children = mergeConsecutiveGroups(pattern->children);
+ mergeConsecutiveGroups1(pattern->children);
/* Merge consecutive identical ALT nodes into GROUP */
- pattern->children = mergeConsecutiveAlts(pattern->children);
+ mergeConsecutiveAlts1(&pattern->children);
/* Merge prefix/suffix into GROUP with matching children */
- pattern->children = mergeGroupPrefixSuffix(pattern->children);
+ /* pattern->children = mergeGroupPrefixSuffix(pattern->children); */
+ mergeGroupPrefixSuffix1(pattern->children);
/* Unwrap single-item SEQ */
return tryUnwrapSingleChild(pattern);
@@ -688,6 +1028,7 @@ optimizeSeqPattern(RPRPatternNode *pattern)
* Returns a new list with optimized children, with nested ALT children
* flattened into the parent list.
*/
+pg_attribute_unused()
static List *
flattenAltChildren(List *children)
{
@@ -705,6 +1046,41 @@ flattenAltChildren(List *children)
return newChildren;
}
+static void
+flattenAltChildren1(List **children)
+{
+ for (int outerpos = 0; outerpos < list_length(*children);)
+ {
+ ListCell *lc = list_nth_cell(*children, outerpos);
+
+ RPRPatternNode *rprpattern = list_nth_node(RPRPatternNode, *children,
+ outerpos);
+
+ RPRPatternNode *optimized = optimizeRPRPattern(rprpattern);
+
+ if (optimized->nodeType == RPR_PATTERN_ALT)
+ {
+ ListCell *lc1;
+ List *optimized_list;
+
+ optimized_list = list_copy(optimized->children);
+
+ *children = list_delete_nth_cell(*children, outerpos);
+
+ foreach(lc1, optimized_list)
+ {
+ *children = lappend(*children, lfirst(lc1));
+ }
+
+ outerpos = outerpos + list_length(optimized->children);
+ }
+ else
+ {
+ lfirst(lc) = optimized;
+ outerpos++;
+ }
+ }
+}
/*
* removeDuplicateAlternatives
@@ -716,6 +1092,7 @@ flattenAltChildren(List *children)
*
* Returns a new list with only unique children (first occurrence kept).
*/
+pg_attribute_unused()
static List *
removeDuplicateAlternatives(List *children)
{
@@ -741,6 +1118,29 @@ removeDuplicateAlternatives(List *children)
return uniqueChildren;
}
+static void
+removeDuplicateAlternatives1(List *children)
+{
+ for (int outerpos = 0; outerpos < list_length(children); outerpos++)
+ {
+ RPRPatternNode *rprpattern;
+
+ rprpattern = list_nth_node(RPRPatternNode, children, outerpos);
+
+ for (int restpos = outerpos + 1; restpos < list_length(children);)
+ {
+ RPRPatternNode *other;
+
+ other = list_nth_node(RPRPatternNode, children, restpos);
+
+ if (rprPatternEqual(rprpattern, other))
+ children = list_delete_nth_cell(children, restpos);
+ else
+ restpos++;
+ }
+ }
+}
+
/*
* optimizeAltPattern
* Optimize ALT pattern node.
@@ -754,10 +1154,11 @@ static RPRPatternNode *
optimizeAltPattern(RPRPatternNode *pattern)
{
/* Recursively optimize children and flatten nested ALT */
- pattern->children = flattenAltChildren(pattern->children);
+ /* pattern->children = flattenAltChildren(pattern->children); */
+ flattenAltChildren1(&pattern->children);
/* Remove duplicate alternatives */
- pattern->children = removeDuplicateAlternatives(pattern->children);
+ removeDuplicateAlternatives1(pattern->children);
/* Unwrap single-item ALT */
return tryUnwrapSingleChild(pattern);
@@ -793,8 +1194,8 @@ tryMultiplyQuantifiers(RPRPatternNode *pattern)
{
RPRPatternNode *child;
bool safe;
- int64 new_min_64;
- int64 new_max_64;
+ int32 newmin;
+ int32 newmax;
/* Parser always creates GROUP with exactly one child */
Assert(list_length(pattern->children) == 1);
@@ -846,22 +1247,19 @@ tryMultiplyQuantifiers(RPRPatternNode *pattern)
if (!safe)
return pattern;
- /* Flatten the child quantifier, guarding against overflow. */
- new_min_64 = (int64) pattern->min * child->min;
- if (new_min_64 >= RPR_QUANTITY_INF)
- return pattern; /* overflow, skip optimization */
+ /*
+ * Flatten the child quantifier, guarding against overflow.
+ */
+ if (pg_mul_s32_overflow(pattern->min, child->min, &newmin))
+ return pattern;
if (pattern->max == RPR_QUANTITY_INF || child->max == RPR_QUANTITY_INF)
- new_max_64 = RPR_QUANTITY_INF;
- else
- {
- new_max_64 = (int64) pattern->max * child->max;
- if (new_max_64 >= RPR_QUANTITY_INF)
- return pattern;
- }
+ newmax = RPR_QUANTITY_INF;
+ else if (pg_mul_s32_overflow(pattern->max, child->max, &newmax))
+ return pattern;
- child->min = (int) new_min_64;
- child->max = (int) new_max_64;
+ child->min = newmin;
+ child->max = newmax;
return child;
}
@@ -924,16 +1322,18 @@ tryUnwrapGroup(RPRPatternNode *pattern)
static RPRPatternNode *
optimizeGroupPattern(RPRPatternNode *pattern)
{
- List *newChildren;
+ ListCell *lc;
RPRPatternNode *result;
/* Recursively optimize children */
- newChildren = NIL;
- foreach_node(RPRPatternNode, child, pattern->children)
+ foreach(lc, pattern->children)
{
- newChildren = lappend(newChildren, optimizeRPRPattern(child));
+ RPRPatternNode *child = (RPRPatternNode *) lfirst(lc);
+
+ RPRPatternNode *optimized = optimizeRPRPattern(child);
+
+ lfirst(lc) = optimized;
}
- pattern->children = newChildren;
/* Try quantifier multiplication */
result = tryMultiplyQuantifiers(pattern);
diff --git a/src/test/regress/expected/rpr_base.out b/src/test/regress/expected/rpr_base.out
index 099ed6e644..428ae0ca6a 100644
--- a/src/test/regress/expected/rpr_base.out
+++ b/src/test/regress/expected/rpr_base.out
@@ -4497,6 +4497,25 @@ WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
-> Seq Scan on rpr_plan
(7 rows)
+-- cannot merge, prefix is different
+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 C (C D) +)
+DEFINE A AS val <= 25, B AS val > 25,
+ C AS val > 50, 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 c (c d)+
+ Nav Mark Lookback: 0
+ -> Sort
+ Sort Key: id
+ -> Seq Scan on rpr_plan
+(7 rows)
+
-- PREFIX merge with quantifiers: A B* (A B*)+ -> (a b*){2,}
EXPLAIN (COSTS OFF)
SELECT COUNT(*) OVER w FROM rpr_plan
@@ -4802,6 +4821,23 @@ WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
-> Seq Scan on rpr_plan
(7 rows)
+-- PREFIX+SUFFIX merge (5-way): B A B (A B)+ A B A B -> b (a b){4,}
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) OVER w FROM rpr_plan
+WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (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: b (a b){4,}
+ Nav Mark Lookback: 0
+ -> Sort
+ Sort Key: id
+ -> Seq Scan on rpr_plan
+(7 rows)
+
-- Unwrap single-item ALT after dedup: (A | A)+ -> a+
-- ALT dedup reduces to single-item, then GROUP unwrap
EXPLAIN (COSTS OFF)
diff --git a/src/test/regress/sql/rpr_base.sql b/src/test/regress/sql/rpr_base.sql
index e324d77da6..d26e2ec706 100644
--- a/src/test/regress/sql/rpr_base.sql
+++ b/src/test/regress/sql/rpr_base.sql
@@ -2773,6 +2773,15 @@ WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
DEFINE A AS val <= 25, B AS val > 25 AND val <= 50,
C AS val > 50 AND val <= 75, D AS val > 75);
+-- cannot merge, prefix is different
+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 C (C D) +)
+DEFINE A AS val <= 25, B AS val > 25,
+ C AS val > 50, D AS val > 75);
+
-- PREFIX merge with quantifiers: A B* (A B*)+ -> (a b*){2,}
EXPLAIN (COSTS OFF)
SELECT COUNT(*) OVER w FROM rpr_plan
@@ -2895,6 +2904,13 @@ 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);
+-- PREFIX+SUFFIX merge (5-way): B A B (A B)+ A B A B -> b (a b){4,}
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) OVER w FROM rpr_plan
+WINDOW w AS (ORDER BY id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (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)
--
2.34.1