From 929e5d50aa45ef6b73fb6231742d4050855e1229 Mon Sep 17 00:00:00 2001 From: jian he Date: Fri, 24 Jul 2026 05:50:08 +0900 Subject: [PATCH] Keep RPR subqueries un-flattened so DEFINE volatility is checked An RPR window's DEFINE clause must not contain volatile functions, which is enforced post-fold in subquery_planner(). An unreferenced RPR window inside a subquery that got pulled up, or flattened as an EXISTS, had its window (and DEFINE) dropped before that check ran, so a volatile DEFINE slipped through one level down while the same clause was rejected at the top level. Commit b48d10d49b2 handled this with a one-off check at the pull-up site. Replace it with a structural guard: record row pattern recognition use in Query.hasRPR, set in transformWindowDefinitions(), and have is_simple_subquery() and simplify_EXISTS_query() decline to flatten such subqueries. The DEFINE then always survives into a planned subquery and reaches the standard post-fold check. Declining these transformations changes only plan shape, never results. Also reword the dead-CASE-arm regression comment, which still described the removed pull-up check. --- src/backend/optimizer/plan/subselect.c | 3 ++- src/backend/optimizer/prep/prepjointree.c | 25 ++--------------------- src/backend/parser/analyze.c | 3 ++- src/backend/parser/parse_clause.c | 6 +++++- src/include/nodes/parsenodes.h | 2 ++ src/include/parser/parse_clause.h | 3 ++- src/test/regress/expected/rpr_base.out | 4 ++-- src/test/regress/sql/rpr_base.sql | 4 ++-- 8 files changed, 19 insertions(+), 31 deletions(-) diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c index 20422a48a8c..2e5410fa167 100644 --- a/src/backend/optimizer/plan/subselect.c +++ b/src/backend/optimizer/plan/subselect.c @@ -1814,7 +1814,8 @@ simplify_EXISTS_query(PlannerInfo *root, Query *query) query->hasModifyingCTE || query->havingQual || query->limitOffset || - query->rowMarks) + query->rowMarks || + query->hasRPR) return false; /* diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index 4342debe162..0d350633b75 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -1276,28 +1276,6 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode, int varno = ((RangeTblRef *) jtnode)->rtindex; RangeTblEntry *rte = rt_fetch(varno, root->parse->rtable); - if (rte->rtekind == RTE_SUBQUERY && - rte->subquery->windowClause != NIL) - { - foreach_node(WindowClause, wc, rte->subquery->windowClause) - { - /* - * An unused RPR WINDOW in a subquery is dropped when the - * subquery is flattened, so its DEFINE would never reach the - * volatility check in subquery_planner(). Check it here - * instead. Use the after-planning form so a DEFINE whose - * volatility folds away is accepted the same as at top level - * (post-fold), not rejected only because it sits one level - * down. - */ - if (wc->defineClause && - contain_volatile_functions_after_planning((Expr *) wc->defineClause)) - ereport(ERROR, - errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("volatile functions are not allowed in DEFINE clause")); - } - } - /* * Is this a subquery RTE, and if so, is the subquery simple enough to * pull up? @@ -2012,7 +1990,8 @@ is_simple_subquery(PlannerInfo *root, Query *subquery, RangeTblEntry *rte, subquery->limitOffset || subquery->limitCount || subquery->hasForUpdate || - subquery->cteList) + subquery->cteList || + subquery->hasRPR) return false; /* diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index ea97d236ea8..8e331ec1d8f 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -1856,7 +1856,8 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt, /* transform window clauses after we have seen all window functions */ qry->windowClause = transformWindowDefinitions(pstate, pstate->p_windowdefs, - &qry->targetList); + &qry->targetList, + &qry->hasRPR); /* resolve any still-unresolved output columns as being type text */ if (pstate->p_resolve_unknowns) diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index 3513c57823e..2ae29d3dfb8 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -2973,7 +2973,8 @@ transformSortClause(ParseState *pstate, List * transformWindowDefinitions(ParseState *pstate, List *windowdefs, - List **targetlist) + List **targetlist, + bool *hasRPR) { List *result = NIL; Index winref = 0; @@ -3172,6 +3173,9 @@ transformWindowDefinitions(ParseState *pstate, /* Process Row Pattern Recognition related clauses */ transformRPR(pstate, wc, windef, targetlist); + if (windef->rpCommonSyntax) + *hasRPR = true; + wc->winref = winref; result = lappend(result, wc); diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index f786a64e7b2..2dfebc1d748 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -227,6 +227,8 @@ typedef struct Query Node *havingQual; /* qualifications applied to groups */ List *windowClause; /* a list of WindowClause's */ + bool hasRPR; /* was ROW PATTERN RECOGNITION used in + * windowClause */ List *distinctClause; /* a list of SortGroupClause's */ diff --git a/src/include/parser/parse_clause.h b/src/include/parser/parse_clause.h index fe234611007..ba4e97fc5e4 100644 --- a/src/include/parser/parse_clause.h +++ b/src/include/parser/parse_clause.h @@ -36,7 +36,8 @@ extern List *transformSortClause(ParseState *pstate, List *orderlist, extern List *transformWindowDefinitions(ParseState *pstate, List *windowdefs, - List **targetlist); + List **targetlist, + bool *hasRPR); extern List *transformDistinctClause(ParseState *pstate, List **targetlist, List *sortClause, bool is_agg); diff --git a/src/test/regress/expected/rpr_base.out b/src/test/regress/expected/rpr_base.out index 8d5b5ea68dd..7d7c9fc331b 100644 --- a/src/test/regress/expected/rpr_base.out +++ b/src/test/regress/expected/rpr_base.out @@ -2197,8 +2197,8 @@ SELECT id FROM ( ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A+) DEFINE A AS random() > 0.5) OFFSET 0) sub; ERROR: volatile functions are not allowed in DEFINE clause --- accepted: the volatile is in a dead CASE arm that folds away, so a --- pulled-up subquery window is no stricter here than at top level +-- accepted: the volatile sits in a dead CASE arm that folds away +-- before the post-fold volatility check, so nothing volatile remains SELECT id FROM ( SELECT id FROM nt WINDOW w AS ( diff --git a/src/test/regress/sql/rpr_base.sql b/src/test/regress/sql/rpr_base.sql index 2a2df9fd7b5..d2cc5ce09b2 100644 --- a/src/test/regress/sql/rpr_base.sql +++ b/src/test/regress/sql/rpr_base.sql @@ -1554,8 +1554,8 @@ SELECT id FROM ( ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A+) DEFINE A AS random() > 0.5) OFFSET 0) sub; --- accepted: the volatile is in a dead CASE arm that folds away, so a --- pulled-up subquery window is no stricter here than at top level +-- accepted: the volatile sits in a dead CASE arm that folds away +-- before the post-fold volatility check, so nothing volatile remains SELECT id FROM ( SELECT id FROM nt WINDOW w AS ( -- 2.50.1 (Apple Git-155)