From f508104267e0c399fa48373e80ac51c8f4996103 Mon Sep 17 00:00:00 2001 From: Henson Choi 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". . 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)