From 42be99f692dc00e083059c84afbd4d8965f7ded2 Mon Sep 17 00:00:00 2001
From: Tatsuo Ishii <ishii@postgresql.org>
Date: Fri, 16 Jan 2026 13:11:21 +0900
Subject: [PATCH v39 3/8] Row pattern recognition patch (rewriter).

---
 src/backend/utils/adt/ruleutils.c | 172 ++++++++++++++++++++++++++++++
 1 file changed, 172 insertions(+)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 033b625f3fc..b8a3ee2915b 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -438,6 +438,12 @@ 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, bool force_colno,
+							 deparse_context *context);
+static void get_rule_define(List *defineClause, bool force_colno,
+							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);
@@ -6744,6 +6750,127 @@ 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 && has_quantifier)
+		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;
+
+	if (node == NULL)
+		return;
+
+	switch (node->nodeType)
+	{
+		case RPR_PATTERN_VAR:
+			appendStringInfoString(buf, 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, bool force_colno,
+				 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, bool force_colno, 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, te->resname);
+		get_rule_expr((Node *) te->expr, context, false);
+		sep = ",\n  ";
+	}
+}
+
 /*
  * Display a WINDOW clause.
  *
@@ -6824,6 +6951,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)
 	{
@@ -6832,7 +6960,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, false, context);
+		needspace = true;
+	}
+
+	if (wc->defineClause)
+	{
+		if (needspace)
+			appendStringInfoChar(buf, ' ');
+		appendStringInfoString(buf, "\n  DEFINE\n");
+		get_rule_define(wc->defineClause, false, context);
+		appendStringInfoChar(buf, ' ');
+	}
+
 	appendStringInfoChar(buf, ')');
 }
 
-- 
2.43.0

