Skip to content

Commit 1980ec2

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 ca54f9b commit 1980ec2

File tree

3 files changed

+124
-43
lines changed

3 files changed

+124
-43
lines changed

src/backend/parser/parse_cte.c

Lines changed: 109 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ static void analyzeCTE(ParseState *pstate, CommonTableExpr *cte);
8888
/* Dependency processing functions */
8989
static void makeDependencyGraph(CteState *cstate);
9090
static bool makeDependencyGraphWalker(Node *node, CteState *cstate);
91+
static void WalkInnerWith(Node *stmt, WithClause *withClause, CteState *cstate);
9192
static void TopologicalSort(ParseState *pstate, CteItem *items, int numitems);
9293

9394
/* Recursion validity checker functions */
@@ -731,58 +732,69 @@ makeDependencyGraphWalker(Node *node, CteState *cstate)
731732
if (IsA(node, SelectStmt))
732733
{
733734
SelectStmt *stmt = (SelectStmt *) node;
734-
ListCell *lc;
735735

736736
if (stmt->withClause)
737737
{
738-
if (stmt->withClause->recursive)
739-
{
740-
/*
741-
* In the RECURSIVE case, all query names of the WITH are
742-
* visible to all WITH items as well as the main query. So
743-
* push them all on, process, pop them all off.
744-
*/
745-
cstate->innerwiths = lcons(stmt->withClause->ctes,
746-
cstate->innerwiths);
747-
foreach(lc, stmt->withClause->ctes)
748-
{
749-
CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
738+
/* Examine the WITH clause and the SelectStmt */
739+
WalkInnerWith(node, stmt->withClause, cstate);
740+
/* We're done examining the SelectStmt */
741+
return false;
742+
}
743+
/* if no WITH clause, just fall through for normal processing */
744+
}
745+
else if (IsA(node, InsertStmt))
746+
{
747+
InsertStmt *stmt = (InsertStmt *) node;
750748

751-
(void) makeDependencyGraphWalker(cte->ctequery, cstate);
752-
}
753-
(void) raw_expression_tree_walker(node,
754-
makeDependencyGraphWalker,
755-
(void *) cstate);
756-
cstate->innerwiths = list_delete_first(cstate->innerwiths);
757-
}
758-
else
759-
{
760-
/*
761-
* In the non-RECURSIVE case, query names are visible to the
762-
* WITH items after them and to the main query.
763-
*/
764-
cstate->innerwiths = lcons(NIL, cstate->innerwiths);
765-
foreach(lc, stmt->withClause->ctes)
766-
{
767-
CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
768-
ListCell *cell1;
749+
if (stmt->withClause)
750+
{
751+
/* Examine the WITH clause and the InsertStmt */
752+
WalkInnerWith(node, stmt->withClause, cstate);
753+
/* We're done examining the InsertStmt */
754+
return false;
755+
}
756+
/* if no WITH clause, just fall through for normal processing */
757+
}
758+
else if (IsA(node, DeleteStmt))
759+
{
760+
DeleteStmt *stmt = (DeleteStmt *) node;
769761

770-
(void) makeDependencyGraphWalker(cte->ctequery, cstate);
771-
/* note that recursion could mutate innerwiths list */
772-
cell1 = list_head(cstate->innerwiths);
773-
lfirst(cell1) = lappend((List *) lfirst(cell1), cte);
774-
}
775-
(void) raw_expression_tree_walker(node,
776-
makeDependencyGraphWalker,
777-
(void *) cstate);
778-
cstate->innerwiths = list_delete_first(cstate->innerwiths);
779-
}
780-
/* We're done examining the SelectStmt */
762+
if (stmt->withClause)
763+
{
764+
/* Examine the WITH clause and the DeleteStmt */
765+
WalkInnerWith(node, stmt->withClause, cstate);
766+
/* We're done examining the DeleteStmt */
781767
return false;
782768
}
783769
/* if no WITH clause, just fall through for normal processing */
784770
}
785-
if (IsA(node, WithClause))
771+
else if (IsA(node, UpdateStmt))
772+
{
773+
UpdateStmt *stmt = (UpdateStmt *) node;
774+
775+
if (stmt->withClause)
776+
{
777+
/* Examine the WITH clause and the UpdateStmt */
778+
WalkInnerWith(node, stmt->withClause, cstate);
779+
/* We're done examining the UpdateStmt */
780+
return false;
781+
}
782+
/* if no WITH clause, just fall through for normal processing */
783+
}
784+
else if (IsA(node, MergeStmt))
785+
{
786+
MergeStmt *stmt = (MergeStmt *) node;
787+
788+
if (stmt->withClause)
789+
{
790+
/* Examine the WITH clause and the MergeStmt */
791+
WalkInnerWith(node, stmt->withClause, cstate);
792+
/* We're done examining the MergeStmt */
793+
return false;
794+
}
795+
/* if no WITH clause, just fall through for normal processing */
796+
}
797+
else if (IsA(node, WithClause))
786798
{
787799
/*
788800
* Prevent raw_expression_tree_walker from recursing directly into a
@@ -796,6 +808,60 @@ makeDependencyGraphWalker(Node *node, CteState *cstate)
796808
(void *) cstate);
797809
}
798810

811+
/*
812+
* makeDependencyGraphWalker's recursion into a statement having a WITH clause.
813+
*
814+
* This subroutine is concerned with updating the innerwiths list correctly
815+
* based on the visibility rules for CTE names.
816+
*/
817+
static void
818+
WalkInnerWith(Node *stmt, WithClause *withClause, CteState *cstate)
819+
{
820+
ListCell *lc;
821+
822+
if (withClause->recursive)
823+
{
824+
/*
825+
* In the RECURSIVE case, all query names of the WITH are visible to
826+
* all WITH items as well as the main query. So push them all on,
827+
* process, pop them all off.
828+
*/
829+
cstate->innerwiths = lcons(withClause->ctes, cstate->innerwiths);
830+
foreach(lc, withClause->ctes)
831+
{
832+
CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
833+
834+
(void) makeDependencyGraphWalker(cte->ctequery, cstate);
835+
}
836+
(void) raw_expression_tree_walker(stmt,
837+
makeDependencyGraphWalker,
838+
(void *) cstate);
839+
cstate->innerwiths = list_delete_first(cstate->innerwiths);
840+
}
841+
else
842+
{
843+
/*
844+
* In the non-RECURSIVE case, query names are visible to the WITH
845+
* items after them and to the main query.
846+
*/
847+
cstate->innerwiths = lcons(NIL, cstate->innerwiths);
848+
foreach(lc, withClause->ctes)
849+
{
850+
CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
851+
ListCell *cell1;
852+
853+
(void) makeDependencyGraphWalker(cte->ctequery, cstate);
854+
/* note that recursion could mutate innerwiths list */
855+
cell1 = list_head(cstate->innerwiths);
856+
lfirst(cell1) = lappend((List *) lfirst(cell1), cte);
857+
}
858+
(void) raw_expression_tree_walker(stmt,
859+
makeDependencyGraphWalker,
860+
(void *) cstate);
861+
cstate->innerwiths = list_delete_first(cstate->innerwiths);
862+
}
863+
}
864+
799865
/*
800866
* Sort by dependencies, using a standard topological sort operation
801867
*/

src/test/regress/expected/with.out

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2031,6 +2031,14 @@ WITH RECURSIVE x(n) AS (
20312031
ERROR: ORDER BY in a recursive query is not implemented
20322032
LINE 3: ORDER BY (SELECT n FROM x))
20332033
^
2034+
-- and this
2035+
WITH RECURSIVE x(n) AS (
2036+
WITH sub_cte AS (SELECT * FROM x)
2037+
DELETE FROM graph RETURNING f)
2038+
SELECT * FROM x;
2039+
ERROR: recursive query "x" must not contain data-modifying statements
2040+
LINE 1: WITH RECURSIVE x(n) AS (
2041+
^
20342042
CREATE TEMPORARY TABLE y (a INTEGER);
20352043
INSERT INTO y SELECT generate_series(1, 10);
20362044
-- LEFT JOIN

src/test/regress/sql/with.sql

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -930,6 +930,13 @@ WITH RECURSIVE x(n) AS (
930930
ORDER BY (SELECT n FROM x))
931931
SELECT * FROM x;
932932

933+
-- and this
934+
WITH RECURSIVE x(n) AS (
935+
WITH sub_cte AS (SELECT * FROM x)
936+
DELETE FROM graph RETURNING f)
937+
SELECT * FROM x;
938+
939+
933940
CREATE TEMPORARY TABLE y (a INTEGER);
934941
INSERT INTO y SELECT generate_series(1, 10);
935942

0 commit comments

Comments
 (0)