nocfbot-0056-Reject-invalid-column-references-in-row-pattern-D.txt

text/plain

Filename: nocfbot-0056-Reject-invalid-column-references-in-row-pattern-D.txt
Type: text/plain
Part: 55
Message: Re: Row pattern recognition
From 37ae9552adb03842984f4305b9b72bb26af3591d Mon Sep 17 00:00:00 2001
From: Henson Choi <assam258@gmail.com>
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)