v41-0002-Row-pattern-recognition-patch-parse-analysis.patch
application/octet-stream
Filename: v41-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 v41-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 | 85 | 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 | 341 | 0 |
| src/backend/parser/README | 1 | 0 |
| src/include/parser/parse_clause.h | 3 | 0 |
| src/include/parser/parse_rpr.h | 22 | 0 |
From 87427687675c2922bf209747c01e7a2940f4fafb Mon Sep 17 00:00:00 2001
From: Tatsuo Ishii <ishii@postgresql.org>
Date: Tue, 20 Jan 2026 19:00:01 +0900
Subject: [PATCH v41 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 | 85 ++++++++
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 | 341 ++++++++++++++++++++++++++++++
src/include/parser/parse_clause.h | 3 +
src/include/parser/parse_rpr.h | 22 ++
14 files changed, 599 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..dc633dc895c 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,90 @@ _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 dcfe1acc4c3..219681d6e88 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;
@@ -1871,6 +1872,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
@@ -3230,6 +3234,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..2c6afded4de
--- /dev/null
+++ b/src/backend/parser/parse_rpr.c
@@ -0,0 +1,341 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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_coerce.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 numVars;
+ 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;
+ numVars = 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.
+ *
+ * Note: findTargetlistEntrySQL99 does Expr transformation and clobber
+ * restarget->val. To avoid it, pass a copy of restarget->val.
+ *
+ * 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 *) copyObject(restarget->val),
+ targetlist, EXPR_KIND_RPR_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 (numVars >= 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))));
+ numVars++;
+
+ 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);
+
+ /*
+ * Make sure that the row pattern definition search condition is a boolean
+ * expression.
+ */
+ foreach_ptr(TargetEntry, te, defineClause)
+ (void) coerce_to_boolean(pstate, (Node *) te->expr, "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