From e0a2a8420cf00a807784f0aa7dcf74382b66525e Mon Sep 17 00:00:00 2001 From: Henson Choi Date: Sun, 10 May 2026 14:42:09 +0900 Subject: [PATCH 13/15] Reject row pattern recognition in recursive queries Per ISO/IEC 9075-2:2016 7.17 Syntax Rule 3)e)f), every in a WITH RECURSIVE clause is "potentially recursive" and shall not contain a . ISO/IEC 19075-5 6.17.5 (R020) and 4.18.5 (R010) restate the prohibition for CREATE RECURSIVE VIEW, which makeRecursiveViewSelect() rewrites to WITH RECURSIVE so the same path catches both forms. The rejection runs in transformWithClause() against the raw parse tree, before per-CTE analysis, and reports the PATTERN keyword position via a new RPCommonSyntax.location field captured in gram.y. Flips both rpr_integration B7 cases (added in the preceding commit) from result rows to the new error. --- src/backend/parser/gram.y | 1 + src/backend/parser/parse_cte.c | 58 ++++++++++++++++++- src/include/nodes/parsenodes.h | 1 + src/test/regress/expected/rpr_integration.out | 23 ++------ src/test/regress/sql/rpr_integration.sql | 1 - src/tools/pgindent/typedefs.list | 1 + 6 files changed, 64 insertions(+), 21 deletions(-) diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index aa587e6aced..a2fafb717cd 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -17585,6 +17585,7 @@ opt_row_pattern_skip_to opt_row_pattern_initial_or_seek n->initial = $2; n->rpPattern = (RPRPatternNode *) $5; n->rpDefs = $8; + n->location = @3; $$ = (Node *) n; } | /*EMPTY*/ { $$ = NULL; } diff --git a/src/backend/parser/parse_cte.c b/src/backend/parser/parse_cte.c index ccde199319a..3830597bb2b 100644 --- a/src/backend/parser/parse_cte.c +++ b/src/backend/parser/parse_cte.c @@ -96,6 +96,14 @@ static void checkWellFormedRecursion(CteState *cstate); static bool checkWellFormedRecursionWalker(Node *node, CteState *cstate); static void checkWellFormedSelectStmt(SelectStmt *stmt, CteState *cstate); +/* Recursive-WITH RPR rejection */ +typedef struct +{ + ParseLoc location; /* location of first RPR window, or -1 */ +} ContainRPRContext; + +static bool contain_rpr_walker(Node *node, void *context); + /* * transformWithClause - @@ -157,13 +165,31 @@ transformWithClause(ParseState *pstate, WithClause *withClause) if (withClause->recursive) { /* - * For WITH RECURSIVE, we rearrange the list elements if needed to - * eliminate forward references. First, build a work array and set up - * the data structure needed by the tree walkers. + * Per ISO/IEC 9075-2:2016 7.17 Syntax Rule 3)e)f), every in a WITH RECURSIVE clause is "potentially recursive" and + * shall not contain a . (PostgreSQL does + * not implement , so only the common syntax + * needs to be checked.) ISO/IEC 19075-5 6.17.5 (R020) and 4.18.5 + * (R010) restate the prohibition for CREATE RECURSIVE VIEW, which is + * rewritten to WITH RECURSIVE by makeRecursiveViewSelect() and so + * flows through here as well. */ CteState cstate; int i; + foreach(lc, withClause->ctes) + { + CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc); + ContainRPRContext ctx; + + ctx.location = -1; + if (contain_rpr_walker(cte->ctequery, &ctx)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot use row pattern recognition in a recursive query"), + parser_errposition(pstate, ctx.location)); + } + cstate.pstate = pstate; cstate.numitems = list_length(withClause->ctes); cstate.items = (CteItem *) palloc0(cstate.numitems * sizeof(CteItem)); @@ -1268,3 +1294,29 @@ checkWellFormedSelectStmt(SelectStmt *stmt, CteState *cstate) } } } + + +/* + * contain_rpr_walker + * Returns true if the raw parse tree contains any -- i.e., any WindowDef with PATTERN/DEFINE attached. Used + * by transformWithClause() to enforce ISO/IEC 9075-2:2016 7.17 SR 3)f) + * on WITH RECURSIVE elements. + */ +static bool +contain_rpr_walker(Node *node, void *context) +{ + if (node == NULL) + return false; + if (IsA(node, WindowDef)) + { + WindowDef *wd = (WindowDef *) node; + + if (wd->rpCommonSyntax != NULL) + { + ((ContainRPRContext *) context)->location = wd->rpCommonSyntax->location; + return true; + } + } + return raw_expression_tree_walker(node, contain_rpr_walker, context); +} diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index adefb1d5bad..5200182aa46 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -646,6 +646,7 @@ typedef struct RPCommonSyntax RPRPatternNode *rpPattern; /* PATTERN clause AST */ List *rpDefs; /* row pattern definitions clause (list of * ResTarget) */ + ParseLoc location; /* PATTERN keyword location, or -1 */ } RPCommonSyntax; /* diff --git a/src/test/regress/expected/rpr_integration.out b/src/test/regress/expected/rpr_integration.out index 0b05a826a27..b598ef95776 100644 --- a/src/test/regress/expected/rpr_integration.out +++ b/src/test/regress/expected/rpr_integration.out @@ -1292,22 +1292,9 @@ WITH RECURSIVE seq AS ( SELECT id + 100, val, cnt FROM seq WHERE id < 3 ) SELECT id, val, cnt FROM seq ORDER BY id; - id | val | cnt ------+-----+----- - 1 | 10 | 2 - 2 | 20 | 0 - 3 | 15 | 2 - 4 | 25 | 0 - 5 | 5 | 3 - 6 | 30 | 0 - 7 | 35 | 0 - 8 | 20 | 3 - 9 | 40 | 0 - 10 | 45 | 0 - 101 | 10 | 2 - 102 | 20 | 0 -(12 rows) - +ERROR: cannot use row pattern recognition in a recursive query +LINE 6: PATTERN (A B+) + ^ -- CREATE RECURSIVE VIEW: rewritten by makeRecursiveViewSelect() -- into WITH RECURSIVE, so the same rejection applies. This is -- the form ISO/IEC 19075-5 6.17.5 cites verbatim. @@ -1318,7 +1305,9 @@ CREATE RECURSIVE VIEW rpr_recv(id, val, cnt) AS ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A B+) DEFINE B AS val > PREV(val)); -DROP VIEW rpr_recv; +ERROR: cannot use row pattern recognition in a recursive query +LINE 6: PATTERN (A B+) + ^ -- ============================================================ -- B8. RPR + Incremental sort -- ============================================================ diff --git a/src/test/regress/sql/rpr_integration.sql b/src/test/regress/sql/rpr_integration.sql index bc8f4712bcb..5f3853becba 100644 --- a/src/test/regress/sql/rpr_integration.sql +++ b/src/test/regress/sql/rpr_integration.sql @@ -818,7 +818,6 @@ CREATE RECURSIVE VIEW rpr_recv(id, val, cnt) AS ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING PATTERN (A B+) DEFINE B AS val > PREV(val)); -DROP VIEW rpr_recv; -- ============================================================ -- B8. RPR + Incremental sort diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index d23b392800e..da10b4ac546 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -534,6 +534,7 @@ Constraint ConstraintCategory ConstraintInfo ConstraintsSetStmt +ContainRPRContext ControlData ControlFileData ConvInfo -- 2.50.1 (Apple Git-155)