v47-0003-Row-pattern-recognition-patch-rewriter.patch
application/octet-stream
Filename: v47-0003-Row-pattern-recognition-patch-rewriter.patch
Type: application/octet-stream
Part: 2
Message:
Re: Row pattern recognition
From 1af4cced6b445802855e03ce85a4ccd6003eccae Mon Sep 17 00:00:00 2001
From: Tatsuo Ishii <ishii@postgresql.org>
Date: Sat, 2 May 2026 13:40:29 +0900
Subject: [PATCH v47 3/9] Row pattern recognition patch (rewriter).
---
src/backend/utils/adt/ruleutils.c | 255 ++++++++++++++++++++++++++++++
1 file changed, 255 insertions(+)
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 75b77bb39f1..70bcad9aa36 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -447,6 +447,10 @@ static void get_rule_groupingset(GroupingSet *gset, List *targetlist,
bool omit_parens, deparse_context *context);
static void get_rule_orderby(List *orderList, List *targetList,
bool force_colno, deparse_context *context);
+static void append_pattern_quantifier(StringInfo buf, RPRPatternNode *node);
+static void get_rule_pattern_node(RPRPatternNode *node, deparse_context *context);
+static void get_rule_pattern(RPRPatternNode *rpPattern, deparse_context *context);
+static void get_rule_define(List *defineClause, deparse_context *context);
static void get_rule_windowclause(Query *query, deparse_context *context);
static void get_rule_windowspec(WindowClause *wc, List *targetList,
deparse_context *context);
@@ -7107,6 +7111,129 @@ get_rule_orderby(List *orderList, List *targetList,
}
}
+/*
+ * Helper function to append quantifier string for pattern node
+ */
+static void
+append_pattern_quantifier(StringInfo buf, RPRPatternNode *node)
+{
+ bool has_quantifier = true;
+
+ if (node->min == 1 && node->max == 1)
+ {
+ /* {1,1} = no quantifier */
+ has_quantifier = false;
+ }
+ else if (node->min == 0 && node->max == INT_MAX)
+ appendStringInfoChar(buf, '*');
+ else if (node->min == 1 && node->max == INT_MAX)
+ appendStringInfoChar(buf, '+');
+ else if (node->min == 0 && node->max == 1)
+ appendStringInfoChar(buf, '?');
+ else if (node->max == INT_MAX)
+ appendStringInfo(buf, "{%d,}", node->min);
+ else if (node->min == node->max)
+ appendStringInfo(buf, "{%d}", node->min);
+ else
+ appendStringInfo(buf, "{%d,%d}", node->min, node->max);
+
+ if (node->reluctant)
+ {
+ if (!has_quantifier)
+ appendStringInfo(buf, "{1}"); /* make reluctant ? unambiguous */
+ appendStringInfoChar(buf, '?');
+ }
+}
+
+/*
+ * Recursive helper to display RPRPatternNode tree
+ */
+static void
+get_rule_pattern_node(RPRPatternNode *node, deparse_context *context)
+{
+ StringInfo buf = context->buf;
+ ListCell *lc;
+ const char *sep;
+
+ Assert(node != NULL);
+
+ switch (node->nodeType)
+ {
+ case RPR_PATTERN_VAR:
+ appendStringInfoString(buf, quote_identifier(node->varName));
+ append_pattern_quantifier(buf, node);
+ break;
+
+ case RPR_PATTERN_SEQ:
+ sep = "";
+ foreach(lc, node->children)
+ {
+ appendStringInfoString(buf, sep);
+ get_rule_pattern_node((RPRPatternNode *) lfirst(lc), context);
+ sep = " ";
+ }
+ break;
+
+ case RPR_PATTERN_ALT:
+ sep = "";
+ foreach(lc, node->children)
+ {
+ appendStringInfoString(buf, sep);
+ get_rule_pattern_node((RPRPatternNode *) lfirst(lc), context);
+ sep = " | ";
+ }
+ break;
+
+ case RPR_PATTERN_GROUP:
+ appendStringInfoChar(buf, '(');
+ sep = "";
+ foreach(lc, node->children)
+ {
+ appendStringInfoString(buf, sep);
+ get_rule_pattern_node((RPRPatternNode *) lfirst(lc), context);
+ sep = " ";
+ }
+ appendStringInfoChar(buf, ')');
+ append_pattern_quantifier(buf, node);
+ break;
+ }
+}
+
+/*
+ * Display a PATTERN clause.
+ */
+static void
+get_rule_pattern(RPRPatternNode *rpPattern, deparse_context *context)
+{
+ StringInfo buf = context->buf;
+
+ appendStringInfoChar(buf, '(');
+ get_rule_pattern_node(rpPattern, context);
+ appendStringInfoChar(buf, ')');
+}
+
+/*
+ * Display a DEFINE clause.
+ */
+static void
+get_rule_define(List *defineClause, deparse_context *context)
+{
+ StringInfo buf = context->buf;
+ const char *sep;
+ ListCell *lc_def;
+
+ sep = " ";
+
+ foreach(lc_def, defineClause)
+ {
+ TargetEntry *te = (TargetEntry *) lfirst(lc_def);
+
+ appendStringInfo(buf, "%s%s AS ", sep, quote_identifier(te->resname));
+ get_rule_expr((Node *) te->expr, context, false);
+ sep = ",\n ";
+ }
+}
+
/*
* Display a WINDOW clause.
*
@@ -7187,6 +7314,7 @@ get_rule_windowspec(WindowClause *wc, List *targetList,
get_rule_orderby(wc->orderClause, targetList, false, context);
needspace = true;
}
+
/* framing clause is never inherited, so print unless it's default */
if (wc->frameOptions & FRAMEOPTION_NONDEFAULT)
{
@@ -7195,7 +7323,51 @@ get_rule_windowspec(WindowClause *wc, List *targetList,
get_window_frame_options(wc->frameOptions,
wc->startOffset, wc->endOffset,
context);
+ needspace = true;
+ }
+
+ /* RPR */
+ if (wc->rpSkipTo == ST_NEXT_ROW)
+ {
+ if (needspace)
+ appendStringInfoChar(buf, ' ');
+ appendStringInfoString(buf,
+ "\n AFTER MATCH SKIP TO NEXT ROW ");
+ needspace = true;
}
+ else if (wc->rpSkipTo == ST_PAST_LAST_ROW)
+ {
+ if (needspace)
+ appendStringInfoChar(buf, ' ');
+ appendStringInfoString(buf,
+ "\n AFTER MATCH SKIP PAST LAST ROW ");
+ needspace = true;
+ }
+ if (wc->initial)
+ {
+ if (needspace)
+ appendStringInfoChar(buf, ' ');
+ appendStringInfoString(buf, "\n INITIAL");
+ needspace = true;
+ }
+ if (wc->rpPattern)
+ {
+ if (needspace)
+ appendStringInfoChar(buf, ' ');
+ appendStringInfoString(buf, "\n PATTERN ");
+ get_rule_pattern(wc->rpPattern, context);
+ needspace = true;
+ }
+
+ if (wc->defineClause)
+ {
+ if (needspace)
+ appendStringInfoChar(buf, ' ');
+ appendStringInfoString(buf, "\n DEFINE\n");
+ get_rule_define(wc->defineClause, context);
+ appendStringInfoChar(buf, ' ');
+ }
+
appendStringInfoChar(buf, ')');
}
@@ -9939,6 +10111,89 @@ get_rule_expr(Node *node, deparse_context *context,
get_func_expr((FuncExpr *) node, context, showimplicit);
break;
+ case T_RPRNavExpr:
+ {
+ RPRNavExpr *nav = (RPRNavExpr *) node;
+ const char *outer_func = NULL;
+ const char *inner_func;
+
+ switch (nav->kind)
+ {
+ case RPR_NAV_PREV:
+ inner_func = "PREV(";
+ break;
+ case RPR_NAV_NEXT:
+ inner_func = "NEXT(";
+ break;
+ case RPR_NAV_FIRST:
+ inner_func = "FIRST(";
+ break;
+ case RPR_NAV_LAST:
+ inner_func = "LAST(";
+ break;
+ case RPR_NAV_PREV_FIRST:
+ outer_func = "PREV(";
+ inner_func = "FIRST(";
+ break;
+ case RPR_NAV_PREV_LAST:
+ outer_func = "PREV(";
+ inner_func = "LAST(";
+ break;
+ case RPR_NAV_NEXT_FIRST:
+ outer_func = "NEXT(";
+ inner_func = "FIRST(";
+ break;
+ case RPR_NAV_NEXT_LAST:
+ outer_func = "NEXT(";
+ inner_func = "LAST(";
+ break;
+ default:
+ elog(ERROR, "unrecognized RPR navigation kind: %d",
+ nav->kind);
+ inner_func = NULL; /* keep compiler quiet */
+ break;
+ }
+
+ if (outer_func != NULL)
+ {
+ /*
+ * Compound: PREV(FIRST(arg [, inner_offset]) [,
+ * outer_offset])
+ */
+ appendStringInfoString(buf, outer_func);
+ appendStringInfoString(buf, inner_func);
+ get_rule_expr((Node *) nav->arg, context, showimplicit);
+ if (nav->offset_arg != NULL)
+ {
+ appendStringInfoString(buf, ", ");
+ get_rule_expr((Node *) nav->offset_arg, context,
+ showimplicit);
+ }
+ appendStringInfoChar(buf, ')');
+ if (nav->compound_offset_arg != NULL)
+ {
+ appendStringInfoString(buf, ", ");
+ get_rule_expr((Node *) nav->compound_offset_arg,
+ context, showimplicit);
+ }
+ appendStringInfoChar(buf, ')');
+ }
+ else
+ {
+ /* Simple: FUNC(arg [, offset]) */
+ appendStringInfoString(buf, inner_func);
+ get_rule_expr((Node *) nav->arg, context, showimplicit);
+ if (nav->offset_arg != NULL)
+ {
+ appendStringInfoString(buf, ", ");
+ get_rule_expr((Node *) nav->offset_arg, context,
+ showimplicit);
+ }
+ appendStringInfoChar(buf, ')');
+ }
+ }
+ break;
+
case T_NamedArgExpr:
{
NamedArgExpr *na = (NamedArgExpr *) node;
--
2.43.0