Skip to content

Commit e276b58

Browse files
committed
Fix parse_cte.c's failure to examine sub-WITHs in DML statements.
makeDependencyGraphWalker thought that only SelectStmt nodes could contain a WithClause. Which was true in our original implementation of WITH, but astonishingly we missed updating this code when we added the ability to attach WITH to INSERT/UPDATE/DELETE (and later MERGE). Moreover, since it was coded to deliberately block recursion to a WithClause, even updating raw_expression_tree_walker didn't save it. The upshot of this was that we didn't see references to outer CTE names appearing within an inner WITH, and would neither complain about disallowed recursion nor account for such references when sorting CTEs into a usable order. The lack of complaints about this is perhaps not so surprising, because typical usage of WITH wouldn't hit either case. Still, it's pretty broken; failing to detect recursion here leads to assert failures or worse later on. Fix by factoring out the processing of sub-WITHs into a new function WalkInnerWith, and invoking that for all the statement types that can have WITH. Bug: #18878 Reported-by: Yu Liang <luy70@psu.edu> Author: Tom Lane <tgl@sss.pgh.pa.us> Discussion: https://postgr.es/m/18878-a26fa5ab6be2f2cf@postgresql.org Backpatch-through: 13
1 parent b92482d commit e276b58

File tree

3 files changed

+111
-43
lines changed

3 files changed

+111
-43
lines changed

src/backend/parser/parse_cte.c

+96-43
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ static void analyzeCTE(ParseState *pstate, CommonTableExpr *cte);
8484
/* Dependency processing functions */
8585
static void makeDependencyGraph(CteState *cstate);
8686
static bool makeDependencyGraphWalker(Node *node, CteState *cstate);
87+
static void WalkInnerWith(Node *stmt, WithClause *withClause, CteState *cstate);
8788
static void TopologicalSort(ParseState *pstate, CteItem *items, int numitems);
8889

8990
/* Recursion validity checker functions */
@@ -507,58 +508,56 @@ makeDependencyGraphWalker(Node *node, CteState *cstate)
507508
if (IsA(node, SelectStmt))
508509
{
509510
SelectStmt *stmt = (SelectStmt *) node;
510-
ListCell *lc;
511511

512512
if (stmt->withClause)
513513
{
514-
if (stmt->withClause->recursive)
515-
{
516-
/*
517-
* In the RECURSIVE case, all query names of the WITH are
518-
* visible to all WITH items as well as the main query. So
519-
* push them all on, process, pop them all off.
520-
*/
521-
cstate->innerwiths = lcons(stmt->withClause->ctes,
522-
cstate->innerwiths);
523-
foreach(lc, stmt->withClause->ctes)
524-
{
525-
CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
514+
/* Examine the WITH clause and the SelectStmt */
515+
WalkInnerWith(node, stmt->withClause, cstate);
516+
/* We're done examining the SelectStmt */
517+
return false;
518+
}
519+
/* if no WITH clause, just fall through for normal processing */
520+
}
521+
else if (IsA(node, InsertStmt))
522+
{
523+
InsertStmt *stmt = (InsertStmt *) node;
526524

527-
(void) makeDependencyGraphWalker(cte->ctequery, cstate);
528-
}
529-
(void) raw_expression_tree_walker(node,
530-
makeDependencyGraphWalker,
531-
(void *) cstate);
532-
cstate->innerwiths = list_delete_first(cstate->innerwiths);
533-
}
534-
else
535-
{
536-
/*
537-
* In the non-RECURSIVE case, query names are visible to the
538-
* WITH items after them and to the main query.
539-
*/
540-
cstate->innerwiths = lcons(NIL, cstate->innerwiths);
541-
foreach(lc, stmt->withClause->ctes)
542-
{
543-
CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
544-
ListCell *cell1;
525+
if (stmt->withClause)
526+
{
527+
/* Examine the WITH clause and the InsertStmt */
528+
WalkInnerWith(node, stmt->withClause, cstate);
529+
/* We're done examining the InsertStmt */
530+
return false;
531+
}
532+
/* if no WITH clause, just fall through for normal processing */
533+
}
534+
else if (IsA(node, DeleteStmt))
535+
{
536+
DeleteStmt *stmt = (DeleteStmt *) node;
545537

546-
(void) makeDependencyGraphWalker(cte->ctequery, cstate);
547-
/* note that recursion could mutate innerwiths list */
548-
cell1 = list_head(cstate->innerwiths);
549-
lfirst(cell1) = lappend((List *) lfirst(cell1), cte);
550-
}
551-
(void) raw_expression_tree_walker(node,
552-
makeDependencyGraphWalker,
553-
(void *) cstate);
554-
cstate->innerwiths = list_delete_first(cstate->innerwiths);
555-
}
556-
/* We're done examining the SelectStmt */
538+
if (stmt->withClause)
539+
{
540+
/* Examine the WITH clause and the DeleteStmt */
541+
WalkInnerWith(node, stmt->withClause, cstate);
542+
/* We're done examining the DeleteStmt */
557543
return false;
558544
}
559545
/* if no WITH clause, just fall through for normal processing */
560546
}
561-
if (IsA(node, WithClause))
547+
else if (IsA(node, UpdateStmt))
548+
{
549+
UpdateStmt *stmt = (UpdateStmt *) node;
550+
551+
if (stmt->withClause)
552+
{
553+
/* Examine the WITH clause and the UpdateStmt */
554+
WalkInnerWith(node, stmt->withClause, cstate);
555+
/* We're done examining the UpdateStmt */
556+
return false;
557+
}
558+
/* if no WITH clause, just fall through for normal processing */
559+
}
560+
else if (IsA(node, WithClause))
562561
{
563562
/*
564563
* Prevent raw_expression_tree_walker from recursing directly into a
@@ -572,6 +571,60 @@ makeDependencyGraphWalker(Node *node, CteState *cstate)
572571
(void *) cstate);
573572
}
574573

574+
/*
575+
* makeDependencyGraphWalker's recursion into a statement having a WITH clause.
576+
*
577+
* This subroutine is concerned with updating the innerwiths list correctly
578+
* based on the visibility rules for CTE names.
579+
*/
580+
static void
581+
WalkInnerWith(Node *stmt, WithClause *withClause, CteState *cstate)
582+
{
583+
ListCell *lc;
584+
585+
if (withClause->recursive)
586+
{
587+
/*
588+
* In the RECURSIVE case, all query names of the WITH are visible to
589+
* all WITH items as well as the main query. So push them all on,
590+
* process, pop them all off.
591+
*/
592+
cstate->innerwiths = lcons(withClause->ctes, cstate->innerwiths);
593+
foreach(lc, withClause->ctes)
594+
{
595+
CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
596+
597+
(void) makeDependencyGraphWalker(cte->ctequery, cstate);
598+
}
599+
(void) raw_expression_tree_walker(stmt,
600+
makeDependencyGraphWalker,
601+
(void *) cstate);
602+
cstate->innerwiths = list_delete_first(cstate->innerwiths);
603+
}
604+
else
605+
{
606+
/*
607+
* In the non-RECURSIVE case, query names are visible to the WITH
608+
* items after them and to the main query.
609+
*/
610+
cstate->innerwiths = lcons(NIL, cstate->innerwiths);
611+
foreach(lc, withClause->ctes)
612+
{
613+
CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
614+
ListCell *cell1;
615+
616+
(void) makeDependencyGraphWalker(cte->ctequery, cstate);
617+
/* note that recursion could mutate innerwiths list */
618+
cell1 = list_head(cstate->innerwiths);
619+
lfirst(cell1) = lappend((List *) lfirst(cell1), cte);
620+
}
621+
(void) raw_expression_tree_walker(stmt,
622+
makeDependencyGraphWalker,
623+
(void *) cstate);
624+
cstate->innerwiths = list_delete_first(cstate->innerwiths);
625+
}
626+
}
627+
575628
/*
576629
* Sort by dependencies, using a standard topological sort operation
577630
*/

src/test/regress/expected/with.out

+8
Original file line numberDiff line numberDiff line change
@@ -1109,6 +1109,14 @@ WITH RECURSIVE x(n) AS (
11091109
ERROR: ORDER BY in a recursive query is not implemented
11101110
LINE 3: ORDER BY (SELECT n FROM x))
11111111
^
1112+
-- and this
1113+
WITH RECURSIVE x(n) AS (
1114+
WITH sub_cte AS (SELECT * FROM x)
1115+
DELETE FROM graph RETURNING f)
1116+
SELECT * FROM x;
1117+
ERROR: recursive query "x" must not contain data-modifying statements
1118+
LINE 1: WITH RECURSIVE x(n) AS (
1119+
^
11121120
CREATE TEMPORARY TABLE y (a INTEGER);
11131121
INSERT INTO y SELECT generate_series(1, 10);
11141122
-- LEFT JOIN

src/test/regress/sql/with.sql

+7
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,13 @@ WITH RECURSIVE x(n) AS (
520520
ORDER BY (SELECT n FROM x))
521521
SELECT * FROM x;
522522

523+
-- and this
524+
WITH RECURSIVE x(n) AS (
525+
WITH sub_cte AS (SELECT * FROM x)
526+
DELETE FROM graph RETURNING f)
527+
SELECT * FROM x;
528+
529+
523530
CREATE TEMPORARY TABLE y (a INTEGER);
524531
INSERT INTO y SELECT generate_series(1, 10);
525532

0 commit comments

Comments
 (0)