v40-0002-Row-pattern-recognition-patch-parse-analysis.patch

application/octet-stream

Filename: v40-0002-Row-pattern-recognition-patch-parse-analysis.patch
Type: application/octet-stream
Part: 1
Message: Re: Row pattern recognition

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: format-patch
Series: patch v40-0002
Subject: Row pattern recognition patch (parse/analysis).
File+
src/backend/nodes/copyfuncs.c 39 0
src/backend/nodes/equalfuncs.c 33 0
src/backend/nodes/outfuncs.c 51 0
src/backend/nodes/readfuncs.c 79 0
src/backend/parser/Makefile 1 0
src/backend/parser/meson.build 1 0
src/backend/parser/parse_agg.c 7 0
src/backend/parser/parse_clause.c 6 4
src/backend/parser/parse_expr.c 6 0
src/backend/parser/parse_func.c 3 0
src/backend/parser/parse_rpr.c 337 0
src/backend/parser/README 1 0
src/include/parser/parse_clause.h 3 0
src/include/parser/parse_rpr.h 22 0
From 022626f166fb37a9e064d4438aff7ad6feccd99c Mon Sep 17 00:00:00 2001
From: Tatsuo Ishii <ishii@postgresql.org>
Date: Sun, 18 Jan 2026 21:01:37 +0900
Subject: [PATCH v40 2/8] Row pattern recognition patch (parse/analysis).

---
 src/backend/nodes/copyfuncs.c     |  39 ++++
 src/backend/nodes/equalfuncs.c    |  33 +++
 src/backend/nodes/outfuncs.c      |  51 +++++
 src/backend/nodes/readfuncs.c     |  79 +++++++
 src/backend/parser/Makefile       |   1 +
 src/backend/parser/README         |   1 +
 src/backend/parser/meson.build    |   1 +
 src/backend/parser/parse_agg.c    |   7 +
 src/backend/parser/parse_clause.c |  10 +-
 src/backend/parser/parse_expr.c   |   6 +
 src/backend/parser/parse_func.c   |   3 +
 src/backend/parser/parse_rpr.c    | 337 ++++++++++++++++++++++++++++++
 src/include/parser/parse_clause.h |   3 +
 src/include/parser/parse_rpr.h    |  22 ++
 14 files changed, 589 insertions(+), 4 deletions(-)
 create mode 100644 src/backend/parser/parse_rpr.c
 create mode 100644 src/include/parser/parse_rpr.h

diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index ff22a04abe5..5d69805fc24 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -16,6 +16,7 @@
 #include "postgres.h"
 
 #include "miscadmin.h"
+#include "nodes/plannodes.h"
 #include "utils/datum.h"
 
 
@@ -166,6 +167,44 @@ _copyBitmapset(const Bitmapset *from)
 	return bms_copy(from);
 }
 
+static RPRPattern *
+_copyRPRPattern(const RPRPattern *from)
+{
+	RPRPattern *newnode = makeNode(RPRPattern);
+
+	COPY_SCALAR_FIELD(numVars);
+	COPY_SCALAR_FIELD(maxDepth);
+	COPY_SCALAR_FIELD(numElements);
+
+	/* Deep copy the varNames array */
+	if (from->numVars > 0)
+	{
+		newnode->varNames = palloc0(from->numVars * sizeof(char *));
+		for (int i = 0; i < from->numVars; i++)
+			newnode->varNames[i] = pstrdup(from->varNames[i]);
+	}
+	else
+	{
+		newnode->varNames = NULL;
+	}
+
+	/* Deep copy the elements array */
+	if (from->numElements > 0)
+	{
+		newnode->elements = palloc(from->numElements * sizeof(RPRPatternElement));
+		memcpy(newnode->elements, from->elements,
+			   from->numElements * sizeof(RPRPatternElement));
+	}
+	else
+	{
+		newnode->elements = NULL;
+	}
+
+	COPY_SCALAR_FIELD(isAbsorbable);
+
+	return newnode;
+}
+
 
 /*
  * copyObjectImpl -- implementation of copyObject(); see nodes/nodes.h
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 3d1a1adf86e..9410790e3d3 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -20,6 +20,7 @@
 #include "postgres.h"
 
 #include "miscadmin.h"
+#include "nodes/plannodes.h"
 #include "utils/datum.h"
 
 
@@ -149,6 +150,38 @@ _equalBitmapset(const Bitmapset *a, const Bitmapset *b)
 	return bms_equal(a, b);
 }
 
+static bool
+_equalRPRPattern(const RPRPattern *a, const RPRPattern *b)
+{
+	COMPARE_SCALAR_FIELD(numVars);
+	COMPARE_SCALAR_FIELD(maxDepth);
+	COMPARE_SCALAR_FIELD(numElements);
+
+	/* Compare varNames array */
+	if (a->numVars > 0)
+	{
+		if (a->varNames == NULL || b->varNames == NULL)
+			return false;
+		for (int i = 0; i < a->numVars; i++)
+		{
+			if (strcmp(a->varNames[i], b->varNames[i]) != 0)
+				return false;
+		}
+	}
+
+	/* Compare elements array */
+	if (a->numElements > 0)
+	{
+		if (a->elements == NULL || b->elements == NULL)
+			return false;
+		if (memcmp(a->elements, b->elements,
+				   a->numElements * sizeof(RPRPatternElement)) != 0)
+			return false;
+	}
+
+	return true;
+}
+
 /*
  * Lists are handled specially
  */
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index c8eef2c75d2..1a70b016770 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -23,6 +23,7 @@
 #include "nodes/bitmapset.h"
 #include "nodes/nodes.h"
 #include "nodes/pg_list.h"
+#include "nodes/plannodes.h"
 #include "utils/datum.h"
 
 /* State flag that determines how nodeToStringInternal() should treat location fields */
@@ -718,6 +719,56 @@ _outA_Const(StringInfo str, const A_Const *node)
 	WRITE_LOCATION_FIELD(location);
 }
 
+static void
+_outRPRPattern(StringInfo str, const RPRPattern *node)
+{
+	WRITE_NODE_TYPE("RPRPATTERN");
+
+	WRITE_INT_FIELD(numVars);
+	WRITE_INT_FIELD(maxDepth);
+	WRITE_INT_FIELD(numElements);
+
+	/* Write varNames array as list of strings */
+	appendStringInfoString(str, " :varNames");
+	if (node->numVars > 0 && node->varNames != NULL)
+	{
+		appendStringInfoString(str, " (");
+		for (int i = 0; i < node->numVars; i++)
+		{
+			if (i > 0)
+				appendStringInfoChar(str, ' ');
+			outToken(str, node->varNames[i]);
+		}
+		appendStringInfoChar(str, ')');
+	}
+	else
+		appendStringInfoString(str, " <>");
+
+	/* Write elements array */
+	appendStringInfoString(str, " :elements");
+	if (node->numElements > 0 && node->elements != NULL)
+	{
+		appendStringInfoChar(str, ' ');
+		for (int i = 0; i < node->numElements; i++)
+		{
+			const RPRPatternElement *elem = &node->elements[i];
+
+			appendStringInfo(str, "(%d %u %d %d %d %d %d)",
+							 (int) elem->varId,
+							 (unsigned) elem->flags,
+							 (int) elem->depth,
+							 (int) elem->min,
+							 (int) elem->max,
+							 (int) elem->next,
+							 (int) elem->jump);
+		}
+	}
+	else
+		appendStringInfoString(str, " <>");
+
+	WRITE_BOOL_FIELD(isAbsorbable);
+}
+
 
 /*
  * outNode -
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 981ab9c34ef..0ba9afe45e6 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -28,6 +28,7 @@
 
 #include "miscadmin.h"
 #include "nodes/bitmapset.h"
+#include "nodes/plannodes.h"
 #include "nodes/readfuncs.h"
 
 
@@ -558,6 +559,84 @@ _readExtensibleNode(void)
 	READ_DONE();
 }
 
+static RPRPattern *
+_readRPRPattern(void)
+{
+	READ_LOCALS(RPRPattern);
+
+	READ_INT_FIELD(numVars);
+	READ_INT_FIELD(maxDepth);
+	READ_INT_FIELD(numElements);
+
+	/* Read varNames array */
+	token = pg_strtok(&length);		/* skip :varNames */
+	token = pg_strtok(&length);		/* get '(' or '<>' */
+	if (local_node->numVars > 0 && token[0] == '(')
+	{
+		local_node->varNames = palloc(local_node->numVars * sizeof(char *));
+		for (int i = 0; i < local_node->numVars; i++)
+		{
+			token = pg_strtok(&length);
+			local_node->varNames[i] = debackslash(token, length);
+		}
+		token = pg_strtok(&length);		/* skip ')' */
+	}
+	else
+	{
+		local_node->varNames = NULL;
+	}
+
+	/* Read elements array */
+	token = pg_strtok(&length);		/* skip :elements */
+	token = pg_strtok(&length);		/* get '(' or '<>' */
+	if (local_node->numElements > 0 && token[0] == '(')
+	{
+		local_node->elements = palloc0(local_node->numElements * sizeof(RPRPatternElement));
+		for (int i = 0; i < local_node->numElements; i++)
+		{
+			RPRPatternElement *elem = &local_node->elements[i];
+			int			varId, flags, depth, min, max, next, jump;
+
+			/* Parse "(varId flags depth min max next jump)" */
+			token = pg_strtok(&length);
+			varId = atoi(token);
+			token = pg_strtok(&length);
+			flags = atoi(token);
+			token = pg_strtok(&length);
+			depth = atoi(token);
+			token = pg_strtok(&length);
+			min = atoi(token);
+			token = pg_strtok(&length);
+			max = atoi(token);
+			token = pg_strtok(&length);
+			next = atoi(token);
+			token = pg_strtok(&length);
+			jump = atoi(token);
+			token = pg_strtok(&length);	/* skip ')' */
+
+			elem->varId = (RPRVarId) varId;
+			elem->flags = (RPRElemFlags) flags;
+			elem->depth = (RPRDepth) depth;
+			elem->min = (RPRQuantity) min;
+			elem->max = (RPRQuantity) max;
+			elem->next = (RPRElemIdx) next;
+			elem->jump = (RPRElemIdx) jump;
+
+			/* Read next element's '(' or end */
+			if (i < local_node->numElements - 1)
+				token = pg_strtok(&length);	/* get '(' */
+		}
+	}
+	else
+	{
+		local_node->elements = NULL;
+	}
+
+	READ_BOOL_FIELD(isAbsorbable);
+
+	READ_DONE();
+}
+
 
 /*
  * parseNodeString
diff --git a/src/backend/parser/Makefile b/src/backend/parser/Makefile
index 8c0fe28d63f..f4c7d605fe3 100644
--- a/src/backend/parser/Makefile
+++ b/src/backend/parser/Makefile
@@ -29,6 +29,7 @@ OBJS = \
 	parse_oper.o \
 	parse_param.o \
 	parse_relation.o \
+	parse_rpr.o \
 	parse_target.o \
 	parse_type.o \
 	parse_utilcmd.o \
diff --git a/src/backend/parser/README b/src/backend/parser/README
index e26eb437a9f..2baffa9517e 100644
--- a/src/backend/parser/README
+++ b/src/backend/parser/README
@@ -26,6 +26,7 @@ parse_node.c	create nodes for various structures
 parse_oper.c	handle operators in expressions
 parse_param.c	handle Params (for the cases used in the core backend)
 parse_relation.c support routines for tables and column handling
+parse_rpr.c	    handle Row Pattern Recognition
 parse_target.c	handle the result list of the query
 parse_type.c	support routines for data type handling
 parse_utilcmd.c	parse analysis for utility commands (done at execution time)
diff --git a/src/backend/parser/meson.build b/src/backend/parser/meson.build
index 924ee87a453..a07102b37a0 100644
--- a/src/backend/parser/meson.build
+++ b/src/backend/parser/meson.build
@@ -16,6 +16,7 @@ backend_sources += files(
   'parse_oper.c',
   'parse_param.c',
   'parse_relation.c',
+  'parse_rpr.c',
   'parse_target.c',
   'parse_type.c',
   'parse_utilcmd.c',
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 25ee0f87d93..5ed785ea0d5 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -584,6 +584,10 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 			errkind = true;
 			break;
 
+		case EXPR_KIND_RPR_DEFINE:
+			errkind = true;
+			break;
+
 			/*
 			 * There is intentionally no default: case here, so that the
 			 * compiler will warn if we add a new ParseExprKind without
@@ -1023,6 +1027,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_CYCLE_MARK:
 			errkind = true;
 			break;
+		case EXPR_KIND_RPR_DEFINE:
+			errkind = true;
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index e35fd25c9bb..f98ac7cfcb5 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -37,6 +37,7 @@
 #include "parser/parse_func.h"
 #include "parser/parse_oper.h"
 #include "parser/parse_relation.h"
+#include "parser/parse_rpr.h"
 #include "parser/parse_target.h"
 #include "parser/parse_type.h"
 #include "parser/parser.h"
@@ -84,8 +85,6 @@ static void checkExprIsVarFree(ParseState *pstate, Node *n,
 							   const char *constructName);
 static TargetEntry *findTargetlistEntrySQL92(ParseState *pstate, Node *node,
 											 List **tlist, ParseExprKind exprKind);
-static TargetEntry *findTargetlistEntrySQL99(ParseState *pstate, Node *node,
-											 List **tlist, ParseExprKind exprKind);
 static int	get_matching_location(int sortgroupref,
 								  List *sortgrouprefs, List *exprs);
 static List *resolve_unique_index_expr(ParseState *pstate, InferClause *infer,
@@ -97,7 +96,6 @@ static Node *transformFrameOffset(ParseState *pstate, int frameOptions,
 								  Oid rangeopfamily, Oid rangeopcintype, Oid *inRangeFunc,
 								  Node *clause);
 
-
 /*
  * transformFromClause -
  *	  Process the FROM clause and add items to the query's range table,
@@ -2168,7 +2166,7 @@ findTargetlistEntrySQL92(ParseState *pstate, Node *node, List **tlist,
  * tlist	the target list (passed by reference so we can append to it)
  * exprKind identifies clause type being processed
  */
-static TargetEntry *
+TargetEntry *
 findTargetlistEntrySQL99(ParseState *pstate, Node *node, List **tlist,
 						 ParseExprKind exprKind)
 {
@@ -3019,6 +3017,10 @@ transformWindowDefinitions(ParseState *pstate,
 											 rangeopfamily, rangeopcintype,
 											 &wc->endInRangeFunc,
 											 windef->endOffset);
+
+		/* Process Row Pattern Recognition related clauses */
+		transformRPR(pstate, wc, windef, targetlist);
+
 		wc->winref = winref;
 
 		result = lappend(result, wc);
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 56826db4c26..7ebcd15fa39 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -577,6 +577,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 		case EXPR_KIND_COPY_WHERE:
 		case EXPR_KIND_GENERATED_COLUMN:
 		case EXPR_KIND_CYCLE_MARK:
+		case EXPR_KIND_RPR_DEFINE:
 			/* okay */
 			break;
 
@@ -1862,6 +1863,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_GENERATED_COLUMN:
 			err = _("cannot use subquery in column generation expression");
 			break;
+		case EXPR_KIND_RPR_DEFINE:
+			err = _("cannot use subquery in DEFINE expression");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -3221,6 +3225,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "GENERATED AS";
 		case EXPR_KIND_CYCLE_MARK:
 			return "CYCLE";
+		case EXPR_KIND_RPR_DEFINE:
+			return "DEFINE";
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 24f6745923b..357b236a818 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2783,6 +2783,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
 		case EXPR_KIND_CYCLE_MARK:
 			errkind = true;
 			break;
+		case EXPR_KIND_RPR_DEFINE:
+			errkind = true;
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_rpr.c b/src/backend/parser/parse_rpr.c
new file mode 100644
index 00000000000..9f030d66ef2
--- /dev/null
+++ b/src/backend/parser/parse_rpr.c
@@ -0,0 +1,337 @@
+/*-------------------------------------------------------------------------
+ *
+ * parse_rpr.c
+ *	  handle Row Pattern Recognition in parser
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/parser/parse_rpr.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/rpr.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_collate.h"
+#include "parser/parse_expr.h"
+#include "parser/parse_rpr.h"
+#include "parser/parse_target.h"
+
+/* Forward declarations */
+static void collectRPRPatternVarNames(RPRPatternNode *node, List **varNames);
+static List *transformDefineClause(ParseState *pstate, WindowClause *wc,
+								   WindowDef *windef, List **targetlist);
+static void transformPatternClause(ParseState *pstate, WindowClause *wc,
+								   WindowDef *windef);
+static RPRPatternNode *copyRPRPatternNode(RPRPatternNode *node);
+
+/*
+ * transformRPR
+ *		Process Row Pattern Recognition related clauses
+ */
+void
+transformRPR(ParseState *pstate, WindowClause *wc, WindowDef *windef,
+			 List **targetlist)
+{
+	/*
+	 * Window definition exists?
+	 */
+	if (windef == NULL)
+		return;
+
+	/*
+	 * Row Pattern Common Syntax clause exists?
+	 */
+	if (windef->rpCommonSyntax == NULL)
+		return;
+
+	/* Check Frame options */
+	/* Frame must start at current row */
+	if ((wc->frameOptions & FRAMEOPTION_START_CURRENT_ROW) == 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("FRAME must start at current row when row pattern recognition is used"),
+				 parser_errposition(pstate, windef->location)));
+
+	/* EXCLUDE options are not permitted */
+	if ((wc->frameOptions & FRAMEOPTION_EXCLUSION) != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("EXCLUDE options are not permitted when row pattern recognition is used"),
+				 parser_errposition(pstate, windef->location)));
+
+	/* Transform AFTER MATCH SKIP TO clause */
+	wc->rpSkipTo = windef->rpCommonSyntax->rpSkipTo;
+
+	/* Transform SEEK or INITIAL clause */
+	wc->initial = windef->rpCommonSyntax->initial;
+
+	/* Transform DEFINE clause into list of TargetEntry's */
+	wc->defineClause = transformDefineClause(pstate, wc, windef, targetlist);
+
+	/* Check PATTERN clause and copy to patternClause */
+	transformPatternClause(pstate, wc, windef);
+}
+
+/*
+ * collectRPRPatternVarNames
+ *		Collect all variable names from RPRPatternNode tree.
+ */
+static void
+collectRPRPatternVarNames(RPRPatternNode *node, List **varNames)
+{
+	ListCell   *lc;
+
+	if (node == NULL)
+		return;
+
+	switch (node->nodeType)
+	{
+		case RPR_PATTERN_VAR:
+			/* Add variable name if not already in list */
+			{
+				bool		found = false;
+
+				foreach(lc, *varNames)
+				{
+					if (strcmp(strVal(lfirst(lc)), node->varName) == 0)
+					{
+						found = true;
+						break;
+					}
+				}
+				if (!found)
+					*varNames = lappend(*varNames, makeString(pstrdup(node->varName)));
+			}
+			break;
+
+		case RPR_PATTERN_SEQ:
+		case RPR_PATTERN_ALT:
+		case RPR_PATTERN_GROUP:
+			/* Recurse into children */
+			foreach(lc, node->children)
+			{
+				collectRPRPatternVarNames((RPRPatternNode *) lfirst(lc), varNames);
+			}
+			break;
+	}
+}
+
+/*
+ * transformDefineClause
+ *		Process DEFINE clause and transform ResTarget into list of
+ *		TargetEntry.
+ *
+ * XXX we only support column reference in row pattern definition search
+ * condition, e.g. "price". <row pattern definition variable name>.<column
+ * reference> is not supported, e.g. "A.price".
+ */
+static List *
+transformDefineClause(ParseState *pstate, WindowClause *wc, WindowDef *windef,
+					  List **targetlist)
+{
+	ListCell   *lc,
+			   *l;
+	ResTarget  *restarget,
+			   *r;
+	List	   *restargets;
+	List	   *defineClause;
+	char	   *name;
+	int			numinitials;
+	List	   *patternVarNames = NIL;
+
+	/*
+	 * If Row Definition Common Syntax exists, DEFINE clause must exist. (the
+	 * raw parser should have already checked it.)
+	 */
+	Assert(windef->rpCommonSyntax->rpDefs != NULL);
+
+	/*
+	 * Collect all pattern variable names from the pattern tree.
+	 */
+	collectRPRPatternVarNames(windef->rpCommonSyntax->rpPattern, &patternVarNames);
+
+	/*
+	 * Check and add "A AS A IS TRUE" if pattern variable is missing in DEFINE
+	 * per the SQL standard.
+	 */
+	restargets = NIL;
+	foreach(lc, patternVarNames)
+	{
+		bool		found = false;
+
+		name = strVal(lfirst(lc));
+
+		foreach(l, windef->rpCommonSyntax->rpDefs)
+		{
+			restarget = (ResTarget *) lfirst(l);
+
+			if (!strcmp(restarget->name, name))
+			{
+				found = true;
+				break;
+			}
+		}
+
+		if (!found)
+		{
+			/*
+			 * "name" is missing. So create "name AS name IS TRUE" ResTarget
+			 * node and add it to the temporary list.
+			 */
+			A_Const    *n;
+
+			restarget = makeNode(ResTarget);
+			n = makeNode(A_Const);
+			n->val.boolval.type = T_Boolean;
+			n->val.boolval.boolval = true;
+			n->location = -1;
+			restarget->name = pstrdup(name);
+			restarget->indirection = NIL;
+			restarget->val = (Node *) n;
+			restarget->location = -1;
+			restargets = lappend((List *) restargets, restarget);
+		}
+	}
+
+	if (list_length(restargets) >= 1)
+	{
+		/* add missing DEFINEs */
+		windef->rpCommonSyntax->rpDefs =
+			list_concat(windef->rpCommonSyntax->rpDefs, restargets);
+		list_free(restargets);
+	}
+
+	/*
+	 * Check for duplicate row pattern definition variables.  The standard
+	 * requires that no two row pattern definition variable names shall be
+	 * equivalent.
+	 */
+	restargets = NIL;
+	numinitials = 0;
+	foreach(lc, windef->rpCommonSyntax->rpDefs)
+	{
+		restarget = (ResTarget *) lfirst(lc);
+		name = restarget->name;
+
+		/*
+		 * Add DEFINE expression (Restarget->val) to the targetlist as a
+		 * TargetEntry if it does not exist yet. Planner will add the column
+		 * ref var node to the outer plan's target list later on. This makes
+		 * DEFINE expression could access the outer tuple while evaluating
+		 * PATTERN.
+		 *
+		 * XXX: adding whole expressions of DEFINE to the plan.targetlist is
+		 * not so good, because it's not necessary to evalute the expression
+		 * in the target list while running the plan. We should extract the
+		 * var nodes only then add them to the plan.targetlist.
+		 */
+		findTargetlistEntrySQL99(pstate, (Node *) restarget->val,
+								 targetlist, EXPR_KIND_RPR_DEFINE);
+
+		/*
+		 * Make sure that the row pattern definition search condition is a
+		 * boolean expression.
+		 */
+		transformWhereClause(pstate, restarget->val,
+							 EXPR_KIND_RPR_DEFINE, "DEFINE");
+
+		foreach(l, restargets)
+		{
+			char	   *n;
+
+			r = (ResTarget *) lfirst(l);
+			n = r->name;
+
+			if (!strcmp(n, name))
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("row pattern definition variable name \"%s\" appears more than once in DEFINE clause",
+								name),
+						 parser_errposition(pstate, exprLocation((Node *) r))));
+		}
+
+		/*
+		 * Check against RPR_VARID_MAX to ensure we don't exceed the limit.
+		 */
+		if (numinitials >= RPR_VARID_MAX)
+			ereport(ERROR,
+					(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+					 errmsg("too many pattern variables"),
+					 errdetail("Maximum is %d.", RPR_VARID_MAX),
+					 parser_errposition(pstate,
+										exprLocation((Node *) restarget))));
+		numinitials++;
+
+		restargets = lappend(restargets, restarget);
+	}
+	list_free(restargets);
+
+	/* turns a list of ResTarget's into a list of TargetEntry's */
+	defineClause = transformTargetList(pstate, windef->rpCommonSyntax->rpDefs,
+									   EXPR_KIND_RPR_DEFINE);
+
+	/* mark column origins */
+	markTargetListOrigins(pstate, defineClause);
+
+	/* mark all nodes in the DEFINE clause tree with collation information */
+	assign_expr_collations(pstate, (Node *) defineClause);
+
+	return defineClause;
+}
+
+/*
+ * transformPatternClause
+ *		Process PATTERN clause and return PATTERN clause in the raw parse tree
+ *
+ * windef->rpCommonSyntax must exist.
+ *
+ * The original (unoptimized) pattern is stored for deparsing (pg_get_viewdef).
+ * Optimization happens later in the planner phase (buildRPRPattern).
+ */
+static void
+transformPatternClause(ParseState *pstate, WindowClause *wc,
+					   WindowDef *windef)
+{
+	Assert(windef->rpCommonSyntax != NULL);
+
+	/* Store original AST for deparsing (no optimization here) */
+	wc->rpPattern = copyRPRPatternNode(windef->rpCommonSyntax->rpPattern);
+}
+
+/*
+ * copyRPRPatternNode
+ *		Deep copy an RPRPatternNode tree.
+ */
+static RPRPatternNode *
+copyRPRPatternNode(RPRPatternNode *node)
+{
+	RPRPatternNode *copy;
+	ListCell   *lc;
+
+	if (node == NULL)
+		return NULL;
+
+	copy = makeNode(RPRPatternNode);
+	copy->nodeType = node->nodeType;
+	copy->varName = node->varName ? pstrdup(node->varName) : NULL;
+	copy->min = node->min;
+	copy->max = node->max;
+	copy->reluctant = node->reluctant;
+	copy->children = NIL;
+
+	foreach(lc, node->children)
+	{
+		copy->children = lappend(copy->children,
+								 copyRPRPatternNode(lfirst(lc)));
+	}
+
+	return copy;
+}
diff --git a/src/include/parser/parse_clause.h b/src/include/parser/parse_clause.h
index fe234611007..8aaac881f2b 100644
--- a/src/include/parser/parse_clause.h
+++ b/src/include/parser/parse_clause.h
@@ -52,6 +52,9 @@ extern List *addTargetToSortList(ParseState *pstate, TargetEntry *tle,
 extern Index assignSortGroupRef(TargetEntry *tle, List *tlist);
 extern bool targetIsInSortList(TargetEntry *tle, Oid sortop, List *sortList);
 
+extern TargetEntry *findTargetlistEntrySQL99(ParseState *pstate, Node *node,
+											 List **tlist, ParseExprKind exprKind);
+
 /* functions in parse_jsontable.c */
 extern ParseNamespaceItem *transformJsonTable(ParseState *pstate, JsonTable *jt);
 
diff --git a/src/include/parser/parse_rpr.h b/src/include/parser/parse_rpr.h
new file mode 100644
index 00000000000..7fab6f292aa
--- /dev/null
+++ b/src/include/parser/parse_rpr.h
@@ -0,0 +1,22 @@
+/*-------------------------------------------------------------------------
+ *
+ * parse_rpr.h
+ *	  handle Row Pattern Recognition in parser
+ *
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/parser/parse_rpr.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARSE_RPR_H
+#define PARSE_RPR_H
+
+#include "parser/parse_node.h"
+
+extern void transformRPR(ParseState *pstate, WindowClause *wc,
+						 WindowDef *windef, List **targetlist);
+
+#endif							/* PARSE_RPR_H */
-- 
2.43.0