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)