nocfbot-0012-qualified-refs.txt

text/plain

Filename: nocfbot-0012-qualified-refs.txt
Type: text/plain
Part: 1
Message: Re: Row pattern recognition
From f508104267e0c399fa48373e80ac51c8f4996103 Mon Sep 17 00:00:00 2001
From: Henson Choi <assam258@gmail.com>
Date: Sun, 1 Mar 2026 19:24:53 +0900
Subject: [PATCH] Reject qualified column references in RPR DEFINE clause

---
 src/backend/parser/parse_expr.c        | 36 ++++++++++++++
 src/backend/parser/parse_rpr.c         | 69 ++++++++++++++++++++------
 src/include/parser/parse_node.h        |  1 +
 src/test/regress/expected/rpr_base.out | 60 +++++++++++++++++-----
 src/test/regress/sql/rpr_base.sql      | 52 ++++++++++++++-----
 5 files changed, 178 insertions(+), 40 deletions(-)

diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 219681d6e88..99b7e4763aa 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -613,6 +613,42 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 			return node;
 	}
 
+	/*
+	 * Qualified column references in DEFINE are not supported.  This covers
+	 * both FROM-clause range variables (prohibited by §6.5) and pattern
+	 * variable qualified names (e.g. UP.price), which are valid per §4.16
+	 * but not yet implemented.
+	 */
+	if (pstate->p_expr_kind == EXPR_KIND_RPR_DEFINE &&
+		list_length(cref->fields) != 1)
+	{
+		char	   *qualifier = strVal(linitial(cref->fields));
+		ListCell   *lc;
+		bool		is_pattern_var = false;
+
+		foreach(lc, pstate->p_rpr_pattern_vars)
+		{
+			if (strcmp(strVal(lfirst(lc)), qualifier) == 0)
+			{
+				is_pattern_var = true;
+				break;
+			}
+		}
+
+		if (is_pattern_var)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("pattern variable qualified column reference \"%s\" is not supported in DEFINE clause",
+							NameListToString(cref->fields)),
+					 parser_errposition(pstate, cref->location)));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("range variable qualified column reference \"%s\" is not allowed in DEFINE clause",
+							NameListToString(cref->fields)),
+					 parser_errposition(pstate, cref->location)));
+	}
+
 	/*----------
 	 * The allowed syntaxes are:
 	 *
diff --git a/src/backend/parser/parse_rpr.c b/src/backend/parser/parse_rpr.c
index 027a3d2500a..dff91e439d2 100644
--- a/src/backend/parser/parse_rpr.c
+++ b/src/backend/parser/parse_rpr.c
@@ -37,7 +37,7 @@
 
 /* Forward declarations */
 static void validateRPRPatternVarCount(ParseState *pstate, RPRPatternNode *node,
-									   List **varNames);
+									   List *rpDefs, List **varNames);
 static List *transformDefineClause(ParseState *pstate, WindowClause *wc,
 								   WindowDef *windef, List **targetlist);
 
@@ -160,11 +160,15 @@ transformRPR(ParseState *pstate, WindowClause *wc, WindowDef *windef,
  * Recursively traverses the pattern tree, collecting unique variable names.
  * Throws an error if the number of unique variables exceeds RPR_VARID_MAX.
  *
+ * If rpDefs is non-NULL, DEFINE variable names are also collected into
+ * varNames so that transformColumnRef can distinguish pattern variable
+ * qualifiers from FROM-clause range variables.
+ *
  * varNames is both input and output: existing names are preserved, new ones added.
  */
 static void
 validateRPRPatternVarCount(ParseState *pstate, RPRPatternNode *node,
-						   List **varNames)
+						   List *rpDefs, List **varNames)
 {
 	ListCell   *lc;
 
@@ -209,18 +213,48 @@ validateRPRPatternVarCount(ParseState *pstate, RPRPatternNode *node,
 			foreach(lc, node->children)
 			{
 				validateRPRPatternVarCount(pstate, (RPRPatternNode *) lfirst(lc),
-										   varNames);
+										   NULL, varNames);
 			}
 			break;
 	}
+
+	/*
+	 * After the top-level call, also collect DEFINE variable names that are
+	 * not already in the list.  This is only done once at the outermost
+	 * recursion level, detected by rpDefs being non-NULL (recursive calls
+	 * pass NULL).
+	 */
+	if (rpDefs)
+	{
+		foreach(lc, rpDefs)
+		{
+			ResTarget  *rt = (ResTarget *) lfirst(lc);
+			ListCell   *lc2;
+			bool		found = false;
+
+			foreach(lc2, *varNames)
+			{
+				if (strcmp(strVal(lfirst(lc2)), rt->name) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+			if (!found)
+				*varNames = lappend(*varNames,
+									makeString(pstrdup(rt->name)));
+		}
+	}
 }
 
 /*
  * transformDefineClause
  *		Process DEFINE clause and transform ResTarget into list of TargetEntry.
  *
- * For each DEFINE variable:
- *   1. Validates PATTERN variable count via validateRPRPatternVarCount
+ * First:
+ *   1. Validates PATTERN variable count and collects RPR variable names
+ *
+ * Then for each DEFINE variable:
  *   2. Checks for duplicate variable names in DEFINE clause
  *   3. Transforms expressions and adds to targetlist via findTargetlistEntrySQL99
  *   4. Creates defineClause entry with proper resname (pattern variable name)
@@ -230,9 +264,9 @@ validateRPRPatternVarCount(ParseState *pstate, RPRPatternNode *node,
  * Note: Variables not in DEFINE are evaluated as TRUE by the executor.
  * Variables in DEFINE but not in PATTERN are filtered out by the planner.
  *
- * 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".
+ * XXX Pattern variable qualified column references in DEFINE (e.g.
+ * "A.price") are not yet supported.  Currently rejected by
+ * transformColumnRef in parse_expr.c via the p_rpr_pattern_vars check.
  */
 static List *
 transformDefineClause(ParseState *pstate, WindowClause *wc, WindowDef *windef,
@@ -253,9 +287,14 @@ transformDefineClause(ParseState *pstate, WindowClause *wc, WindowDef *windef,
 	 */
 	Assert(windef->rpCommonSyntax->rpDefs != NULL);
 
-	/* Validate PATTERN variable count (max RPR_VARID_MAX) */
+	/*
+	 * Validate PATTERN variable count and collect all RPR variable names
+	 * (PATTERN + DEFINE) for use in transformColumnRef.
+	 */
 	validateRPRPatternVarCount(pstate, windef->rpCommonSyntax->rpPattern,
+							   windef->rpCommonSyntax->rpDefs,
 							   &patternVarNames);
+	pstate->p_rpr_pattern_vars = patternVarNames;
 
 	/*
 	 * Check for duplicate row pattern definition variables.  The standard
@@ -290,13 +329,12 @@ transformDefineClause(ParseState *pstate, WindowClause *wc, WindowDef *windef,
 		restargets = lappend(restargets, restarget);
 
 		/*
-		 * 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.
+		 * Transform the DEFINE expression (restarget->val) and add it to the
+		 * targetlist as a TargetEntry if not already present, so the planner
+		 * can propagate the referenced columns to the outer plan's
+		 * targetlist.
 		 *
-		 * Note: findTargetlistEntrySQL99 does Expr transformation and clobber
+		 * Note: findTargetlistEntrySQL99 transforms and clobbers
 		 * restarget->val.
 		 */
 
@@ -347,6 +385,7 @@ transformDefineClause(ParseState *pstate, WindowClause *wc, WindowDef *windef,
 		defineClause = lappend(defineClause, teDefine);
 	}
 	list_free(restargets);
+	pstate->p_rpr_pattern_vars = NIL;
 
 	/*
 	 * Make sure that the row pattern definition search condition is a boolean
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 4dc7e5738ae..f55d4c14b0a 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -208,6 +208,7 @@ struct ParseState
 	ParseNamespaceItem *p_grouping_nsitem;	/* NSItem for grouping, or NULL */
 	List	   *p_windowdefs;	/* raw representations of window clauses */
 	ParseExprKind p_expr_kind;	/* what kind of expression we're parsing */
+	List	   *p_rpr_pattern_vars; /* RPR variable names for DEFINE clause */
 	int			p_next_resno;	/* next targetlist resno to assign */
 	List	   *p_multiassign_exprs;	/* junk tlist entries for multiassign */
 	List	   *p_locking_clause;	/* raw FOR UPDATE/FOR SHARE info */
diff --git a/src/test/regress/expected/rpr_base.out b/src/test/regress/expected/rpr_base.out
index 43eea32130f..ab878443379 100644
--- a/src/test/regress/expected/rpr_base.out
+++ b/src/test/regress/expected/rpr_base.out
@@ -2682,9 +2682,7 @@ LINE 6:     DEFINE A AS val > 0
             ^
 -- Expected: Syntax error
 -- Qualified column references (NOT SUPPORTED)
--- Pattern variables in DEFINE clause cannot use qualified references (A.price)
--- This gives a confusing error about missing FROM-clause entry
--- Qualified reference in DEFINE clause
+-- Pattern variable qualified name: not supported (valid per §4.16, not yet implemented)
 SELECT COUNT(*) OVER w
 FROM rpr_err
 WINDOW w AS (
@@ -2693,10 +2691,45 @@ WINDOW w AS (
     PATTERN (A+)
     DEFINE A AS A.val > 0
 );
-ERROR:  missing FROM-clause entry for table "a"
+ERROR:  pattern variable qualified column reference "a.val" is not supported in DEFINE clause
 LINE 7:     DEFINE A AS A.val > 0
                         ^
--- Expected: ERROR: missing FROM-clause entry for table "a"
+-- PATTERN-only variable qualified name: not supported even without DEFINE entry
+SELECT COUNT(*) OVER w
+FROM rpr_err
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    PATTERN (A+ B+)
+    DEFINE A AS B.val > 0
+);
+ERROR:  pattern variable qualified column reference "b.val" is not supported in DEFINE clause
+LINE 7:     DEFINE A AS B.val > 0
+                        ^
+-- DEFINE-only variable qualified name: still a pattern variable, not a range variable
+SELECT COUNT(*) OVER w
+FROM rpr_err
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    PATTERN (A+)
+    DEFINE A AS val > 0, B AS B.val > 0
+);
+ERROR:  pattern variable qualified column reference "b.val" is not supported in DEFINE clause
+LINE 7:     DEFINE A AS val > 0, B AS B.val > 0
+                                      ^
+-- FROM-clause range variable qualified name: not allowed (prohibited by §6.5)
+SELECT COUNT(*) OVER w
+FROM rpr_err
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    PATTERN (A+)
+    DEFINE A AS rpr_err.val > 0
+);
+ERROR:  range variable qualified column reference "rpr_err.val" is not allowed in DEFINE clause
+LINE 7:     DEFINE A AS rpr_err.val > 0
+                        ^
 -- Semantic errors
 -- Undefined column in DEFINE
 SELECT COUNT(*) OVER w
@@ -4674,8 +4707,8 @@ WINDOW w AS (
     ORDER BY t1.id
     ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
     PATTERN (A+ B)
-    DEFINE A AS t1.val1 > 20,
-           B AS t2.val2 > 200
+    DEFINE A AS val1 > 20,
+           B AS val2 > 200
 )
 ORDER BY t1.id;
  id | val1 | val2 | cnt 
@@ -4709,17 +4742,18 @@ ORDER BY t1.id, t2.id;
 (4 rows)
 
 -- Self-Join with RPR
-SELECT a.id, a.val1, b.val1 as val1_next,
+SELECT id, val1, val1_next,
        COUNT(*) OVER w as cnt
-FROM rpr_join1 a
-INNER JOIN rpr_join1 b ON a.id + 1 = b.id
+FROM (SELECT a.id, a.val1, b.val1 as val1_next
+      FROM rpr_join1 a
+      INNER JOIN rpr_join1 b ON a.id + 1 = b.id) sub
 WINDOW w AS (
-    ORDER BY a.id
+    ORDER BY id
     ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
     PATTERN (X+)
-    DEFINE X AS a.val1 < b.val1
+    DEFINE X AS val1 < val1_next
 )
-ORDER BY a.id;
+ORDER BY id;
  id | val1 | val1_next | cnt 
 ----+------+-----------+-----
   1 |   10 |        20 |   4
diff --git a/src/test/regress/sql/rpr_base.sql b/src/test/regress/sql/rpr_base.sql
index 992c081205a..97b62a73a0e 100644
--- a/src/test/regress/sql/rpr_base.sql
+++ b/src/test/regress/sql/rpr_base.sql
@@ -1891,10 +1891,8 @@ WINDOW w AS (
 -- Expected: Syntax error
 
 -- Qualified column references (NOT SUPPORTED)
--- Pattern variables in DEFINE clause cannot use qualified references (A.price)
--- This gives a confusing error about missing FROM-clause entry
 
--- Qualified reference in DEFINE clause
+-- Pattern variable qualified name: not supported (valid per §4.16, not yet implemented)
 SELECT COUNT(*) OVER w
 FROM rpr_err
 WINDOW w AS (
@@ -1903,7 +1901,36 @@ WINDOW w AS (
     PATTERN (A+)
     DEFINE A AS A.val > 0
 );
--- Expected: ERROR: missing FROM-clause entry for table "a"
+
+-- PATTERN-only variable qualified name: not supported even without DEFINE entry
+SELECT COUNT(*) OVER w
+FROM rpr_err
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    PATTERN (A+ B+)
+    DEFINE A AS B.val > 0
+);
+
+-- DEFINE-only variable qualified name: still a pattern variable, not a range variable
+SELECT COUNT(*) OVER w
+FROM rpr_err
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    PATTERN (A+)
+    DEFINE A AS val > 0, B AS B.val > 0
+);
+
+-- FROM-clause range variable qualified name: not allowed (prohibited by §6.5)
+SELECT COUNT(*) OVER w
+FROM rpr_err
+WINDOW w AS (
+    ORDER BY id
+    ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+    PATTERN (A+)
+    DEFINE A AS rpr_err.val > 0
+);
 
 -- Semantic errors
 
@@ -2983,8 +3010,8 @@ WINDOW w AS (
     ORDER BY t1.id
     ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
     PATTERN (A+ B)
-    DEFINE A AS t1.val1 > 20,
-           B AS t2.val2 > 200
+    DEFINE A AS val1 > 20,
+           B AS val2 > 200
 )
 ORDER BY t1.id;
 
@@ -3005,17 +3032,18 @@ ORDER BY t1.id, t2.id;
 
 -- Self-Join with RPR
 
-SELECT a.id, a.val1, b.val1 as val1_next,
+SELECT id, val1, val1_next,
        COUNT(*) OVER w as cnt
-FROM rpr_join1 a
-INNER JOIN rpr_join1 b ON a.id + 1 = b.id
+FROM (SELECT a.id, a.val1, b.val1 as val1_next
+      FROM rpr_join1 a
+      INNER JOIN rpr_join1 b ON a.id + 1 = b.id) sub
 WINDOW w AS (
-    ORDER BY a.id
+    ORDER BY id
     ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
     PATTERN (X+)
-    DEFINE X AS a.val1 < b.val1
+    DEFINE X AS val1 < val1_next
 )
-ORDER BY a.id;
+ORDER BY id;
 
 DROP TABLE rpr_join1, rpr_join2;
 
-- 
2.50.1 (Apple Git-155)