From 37ae9552adb03842984f4305b9b72bb26af3591d Mon Sep 17 00:00:00 2001 From: Henson Choi Date: Sun, 7 Jun 2026 18:27:16 +0900 Subject: [PATCH 56/68] Reject invalid column references in row pattern DEFINE clauses A DEFINE expression could reference an outer query's column (a correlated reference), which surfaced as the internal "Upper-level Var found where not expected" error (XX000) raised by pull_var_clause. Separately, a schema- or catalog-qualified column reference (three or more name parts) slipped past the existing two-part classifier and was wrongly accepted. Reject both in transformColumnRef, right after the reference is resolved: an outer-level Var (varlevelsup > 0) with ERRCODE_FEATURE_NOT_SUPPORTED, and a reference with three or more name parts with ERRCODE_SYNTAX_ERROR. The latter uses "qualified expression" wording because a composite-typed qualifier (e.g. "a.items" in (a.items).amount) is not a column name. --- src/backend/parser/parse_expr.c | 24 ++++++++++ src/test/regress/expected/rpr.out | 74 +++++++++++++++++++++++++++++++ src/test/regress/sql/rpr.sql | 61 +++++++++++++++++++++++++ 3 files changed, 159 insertions(+) diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 2344aaef9ae..f65a270d20e 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -947,6 +947,30 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref) } } + /* + * Restrict column references in a row pattern DEFINE clause. node is now + * a successfully resolved reference, so reject the two forms RPR does not + * allow: a correlated reference to an outer query's column, and a + * schema/catalog-qualified reference (three or more name parts). Simple + * two-part qualifiers (pattern or range variable) are handled earlier, + * before resolution. + */ + if (pstate->p_expr_kind == EXPR_KIND_RPR_DEFINE) + { + if (IsA(node, Var) && ((Var *) node)->varlevelsup > 0) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot use outer query column in DEFINE clause"), + parser_errposition(pstate, cref->location)); + + if (list_length(cref->fields) >= 3) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("qualified expression \"%s\" is not allowed in DEFINE clause", + NameListToString(cref->fields)), + parser_errposition(pstate, cref->location)); + } + return node; } diff --git a/src/test/regress/expected/rpr.out b/src/test/regress/expected/rpr.out index 550113700a9..1b409b923dd 100644 --- a/src/test/regress/expected/rpr.out +++ b/src/test/regress/expected/rpr.out @@ -1295,6 +1295,80 @@ ERROR: volatile functions are not allowed in DEFINE clause LINE 7: DEFINE A AS price > nextval('rpr_seq') ^ DROP SEQUENCE rpr_seq; +-- DEFINE cannot reference an outer query's column. A correlated outer +-- reference must produce a clean error, not the internal "Upper-level Var" +-- elog that pull_var_clause would otherwise raise. +-- Qualified outer reference (o.threshold): +SELECT * FROM (VALUES (95)) AS o(threshold), +LATERAL ( + SELECT price FROM stock + WINDOW w AS ( + PARTITION BY company + ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING + INITIAL + PATTERN (A) + DEFINE A AS price > o.threshold + ) +) s; +ERROR: cannot use outer query column in DEFINE clause +LINE 9: DEFINE A AS price > o.threshold + ^ +-- Unqualified name resolving to the outer column (threshold): +SELECT * FROM (VALUES (95)) AS o(threshold), +LATERAL ( + SELECT price FROM stock + WINDOW w AS ( + PARTITION BY company + ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING + INITIAL + PATTERN (A) + DEFINE A AS price > threshold + ) +) s; +ERROR: cannot use outer query column in DEFINE clause +LINE 9: DEFINE A AS price > threshold + ^ +-- Outer reference inside a navigation argument is rejected too: +SELECT * FROM (VALUES (95)) AS o(threshold), +LATERAL ( + SELECT price FROM stock + WINDOW w AS ( + PARTITION BY company ORDER BY tdate + ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING + PATTERN (A+) + DEFINE A AS PREV(o.threshold, 1) > 0 + ) +) s; +ERROR: cannot use outer query column in DEFINE clause +LINE 8: DEFINE A AS PREV(o.threshold, 1) > 0 + ^ +-- DEFINE rejects a schema-qualified column reference (three or more name +-- parts) once it resolves; the qualified form itself is not allowed. (stock +-- is a temp table, so it is qualified with pg_temp here.) +-- 3-part (schema.table.column): +SELECT price FROM stock +WINDOW w AS ( + PARTITION BY company + ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING + INITIAL + PATTERN (A) + DEFINE A AS pg_temp.stock.price > 0 +); +ERROR: qualified expression "pg_temp.stock.price" is not allowed in DEFINE clause +LINE 7: DEFINE A AS pg_temp.stock.price > 0 + ^ +-- whole-row variant (schema.table.*): +SELECT price FROM stock +WINDOW w AS ( + PARTITION BY company + ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING + INITIAL + PATTERN (A) + DEFINE A AS (pg_temp.stock.*) IS NOT NULL +); +ERROR: qualified expression "pg_temp.stock.*" is not allowed in DEFINE clause +LINE 7: DEFINE A AS (pg_temp.stock.*) IS NOT NULL + ^ -- -- 2-arg PREV/NEXT: functional tests -- diff --git a/src/test/regress/sql/rpr.sql b/src/test/regress/sql/rpr.sql index 0aa17f01e84..56dff9b6725 100644 --- a/src/test/regress/sql/rpr.sql +++ b/src/test/regress/sql/rpr.sql @@ -667,6 +667,67 @@ WINDOW w AS ( ); DROP SEQUENCE rpr_seq; +-- DEFINE cannot reference an outer query's column. A correlated outer +-- reference must produce a clean error, not the internal "Upper-level Var" +-- elog that pull_var_clause would otherwise raise. +-- Qualified outer reference (o.threshold): +SELECT * FROM (VALUES (95)) AS o(threshold), +LATERAL ( + SELECT price FROM stock + WINDOW w AS ( + PARTITION BY company + ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING + INITIAL + PATTERN (A) + DEFINE A AS price > o.threshold + ) +) s; +-- Unqualified name resolving to the outer column (threshold): +SELECT * FROM (VALUES (95)) AS o(threshold), +LATERAL ( + SELECT price FROM stock + WINDOW w AS ( + PARTITION BY company + ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING + INITIAL + PATTERN (A) + DEFINE A AS price > threshold + ) +) s; +-- Outer reference inside a navigation argument is rejected too: +SELECT * FROM (VALUES (95)) AS o(threshold), +LATERAL ( + SELECT price FROM stock + WINDOW w AS ( + PARTITION BY company ORDER BY tdate + ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING + PATTERN (A+) + DEFINE A AS PREV(o.threshold, 1) > 0 + ) +) s; + +-- DEFINE rejects a schema-qualified column reference (three or more name +-- parts) once it resolves; the qualified form itself is not allowed. (stock +-- is a temp table, so it is qualified with pg_temp here.) +-- 3-part (schema.table.column): +SELECT price FROM stock +WINDOW w AS ( + PARTITION BY company + ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING + INITIAL + PATTERN (A) + DEFINE A AS pg_temp.stock.price > 0 +); +-- whole-row variant (schema.table.*): +SELECT price FROM stock +WINDOW w AS ( + PARTITION BY company + ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING + INITIAL + PATTERN (A) + DEFINE A AS (pg_temp.stock.*) IS NOT NULL +); + -- -- 2-arg PREV/NEXT: functional tests -- -- 2.50.1 (Apple Git-155)