mirror of
https://github.com/postgres/postgres.git
synced 2025-07-28 23:42:10 +03:00
Prevent inlining of multiply-referenced CTEs with outer recursive refs.
This has to be prevented because inlining would result in multiple
self-references, which we don't support (and in fact that's disallowed
by the SQL spec, see statements about linearly vs. nonlinearly
recursive queries). Bug fix for commit 608b167f9
.
Per report from Yaroslav Schekin (via Andrew Gierth)
Discussion: https://postgr.es/m/87wolmg60q.fsf@news-spur.riddles.org.uk
This commit is contained in:
@ -85,6 +85,8 @@ static bool testexpr_is_hashable(Node *testexpr);
|
|||||||
static bool hash_ok_operator(OpExpr *expr);
|
static bool hash_ok_operator(OpExpr *expr);
|
||||||
static bool contain_dml(Node *node);
|
static bool contain_dml(Node *node);
|
||||||
static bool contain_dml_walker(Node *node, void *context);
|
static bool contain_dml_walker(Node *node, void *context);
|
||||||
|
static bool contain_outer_selfref(Node *node);
|
||||||
|
static bool contain_outer_selfref_walker(Node *node, Index *depth);
|
||||||
static void inline_cte(PlannerInfo *root, CommonTableExpr *cte);
|
static void inline_cte(PlannerInfo *root, CommonTableExpr *cte);
|
||||||
static bool inline_cte_walker(Node *node, inline_cte_walker_context *context);
|
static bool inline_cte_walker(Node *node, inline_cte_walker_context *context);
|
||||||
static bool simplify_EXISTS_query(PlannerInfo *root, Query *query);
|
static bool simplify_EXISTS_query(PlannerInfo *root, Query *query);
|
||||||
@ -867,6 +869,10 @@ SS_process_ctes(PlannerInfo *root)
|
|||||||
* SELECT, or containing volatile functions. Inlining might change
|
* SELECT, or containing volatile functions. Inlining might change
|
||||||
* the side-effects, which would be bad.
|
* the side-effects, which would be bad.
|
||||||
*
|
*
|
||||||
|
* 4. The CTE is multiply-referenced and contains a self-reference to
|
||||||
|
* a recursive CTE outside itself. Inlining would result in multiple
|
||||||
|
* recursive self-references, which we don't support.
|
||||||
|
*
|
||||||
* Otherwise, we have an option whether to inline or not. That should
|
* Otherwise, we have an option whether to inline or not. That should
|
||||||
* always be a win if there's just a single reference, but if the CTE
|
* always be a win if there's just a single reference, but if the CTE
|
||||||
* is multiply-referenced then it's unclear: inlining adds duplicate
|
* is multiply-referenced then it's unclear: inlining adds duplicate
|
||||||
@ -876,6 +882,9 @@ SS_process_ctes(PlannerInfo *root)
|
|||||||
* the user express a preference. Our default behavior is to inline
|
* the user express a preference. Our default behavior is to inline
|
||||||
* only singly-referenced CTEs, but a CTE marked CTEMaterializeNever
|
* only singly-referenced CTEs, but a CTE marked CTEMaterializeNever
|
||||||
* will be inlined even if multiply referenced.
|
* will be inlined even if multiply referenced.
|
||||||
|
*
|
||||||
|
* Note: we check for volatile functions last, because that's more
|
||||||
|
* expensive than the other tests needed.
|
||||||
*/
|
*/
|
||||||
if ((cte->ctematerialized == CTEMaterializeNever ||
|
if ((cte->ctematerialized == CTEMaterializeNever ||
|
||||||
(cte->ctematerialized == CTEMaterializeDefault &&
|
(cte->ctematerialized == CTEMaterializeDefault &&
|
||||||
@ -883,6 +892,8 @@ SS_process_ctes(PlannerInfo *root)
|
|||||||
!cte->cterecursive &&
|
!cte->cterecursive &&
|
||||||
cmdType == CMD_SELECT &&
|
cmdType == CMD_SELECT &&
|
||||||
!contain_dml(cte->ctequery) &&
|
!contain_dml(cte->ctequery) &&
|
||||||
|
(cte->cterefcount <= 1 ||
|
||||||
|
!contain_outer_selfref(cte->ctequery)) &&
|
||||||
!contain_volatile_functions(cte->ctequery))
|
!contain_volatile_functions(cte->ctequery))
|
||||||
{
|
{
|
||||||
inline_cte(root, cte);
|
inline_cte(root, cte);
|
||||||
@ -1016,6 +1027,61 @@ contain_dml_walker(Node *node, void *context)
|
|||||||
return expression_tree_walker(node, contain_dml_walker, context);
|
return expression_tree_walker(node, contain_dml_walker, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* contain_outer_selfref: is there an external recursive self-reference?
|
||||||
|
*/
|
||||||
|
static bool
|
||||||
|
contain_outer_selfref(Node *node)
|
||||||
|
{
|
||||||
|
Index depth = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We should be starting with a Query, so that depth will be 1 while
|
||||||
|
* examining its immediate contents.
|
||||||
|
*/
|
||||||
|
Assert(IsA(node, Query));
|
||||||
|
|
||||||
|
return contain_outer_selfref_walker(node, &depth);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
contain_outer_selfref_walker(Node *node, Index *depth)
|
||||||
|
{
|
||||||
|
if (node == NULL)
|
||||||
|
return false;
|
||||||
|
if (IsA(node, RangeTblEntry))
|
||||||
|
{
|
||||||
|
RangeTblEntry *rte = (RangeTblEntry *) node;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check for a self-reference to a CTE that's above the Query that our
|
||||||
|
* search started at.
|
||||||
|
*/
|
||||||
|
if (rte->rtekind == RTE_CTE &&
|
||||||
|
rte->self_reference &&
|
||||||
|
rte->ctelevelsup >= *depth)
|
||||||
|
return true;
|
||||||
|
return false; /* allow range_table_walker to continue */
|
||||||
|
}
|
||||||
|
if (IsA(node, Query))
|
||||||
|
{
|
||||||
|
/* Recurse into subquery, tracking nesting depth properly */
|
||||||
|
Query *query = (Query *) node;
|
||||||
|
bool result;
|
||||||
|
|
||||||
|
(*depth)++;
|
||||||
|
|
||||||
|
result = query_tree_walker(query, contain_outer_selfref_walker,
|
||||||
|
(void *) depth, QTW_EXAMINE_RTES_BEFORE);
|
||||||
|
|
||||||
|
(*depth)--;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return expression_tree_walker(node, contain_outer_selfref_walker,
|
||||||
|
(void *) depth);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* inline_cte: convert RTE_CTE references to given CTE into RTE_SUBQUERYs
|
* inline_cte: convert RTE_CTE references to given CTE into RTE_SUBQUERYs
|
||||||
*/
|
*/
|
||||||
|
@ -1299,6 +1299,106 @@ select * from x, x x2 where x.n = x2.n;
|
|||||||
Output: subselect_tbl_1.f1
|
Output: subselect_tbl_1.f1
|
||||||
(11 rows)
|
(11 rows)
|
||||||
|
|
||||||
|
-- Multiply-referenced CTEs can't be inlined if they contain outer self-refs
|
||||||
|
explain (verbose, costs off)
|
||||||
|
with recursive x(a) as
|
||||||
|
((values ('a'), ('b'))
|
||||||
|
union all
|
||||||
|
(with z as not materialized (select * from x)
|
||||||
|
select z.a || z1.a as a from z cross join z as z1
|
||||||
|
where length(z.a || z1.a) < 5))
|
||||||
|
select * from x;
|
||||||
|
QUERY PLAN
|
||||||
|
----------------------------------------------------------
|
||||||
|
CTE Scan on x
|
||||||
|
Output: x.a
|
||||||
|
CTE x
|
||||||
|
-> Recursive Union
|
||||||
|
-> Values Scan on "*VALUES*"
|
||||||
|
Output: "*VALUES*".column1
|
||||||
|
-> Nested Loop
|
||||||
|
Output: (z.a || z1.a)
|
||||||
|
Join Filter: (length((z.a || z1.a)) < 5)
|
||||||
|
CTE z
|
||||||
|
-> WorkTable Scan on x x_1
|
||||||
|
Output: x_1.a
|
||||||
|
-> CTE Scan on z
|
||||||
|
Output: z.a
|
||||||
|
-> CTE Scan on z z1
|
||||||
|
Output: z1.a
|
||||||
|
(16 rows)
|
||||||
|
|
||||||
|
with recursive x(a) as
|
||||||
|
((values ('a'), ('b'))
|
||||||
|
union all
|
||||||
|
(with z as not materialized (select * from x)
|
||||||
|
select z.a || z1.a as a from z cross join z as z1
|
||||||
|
where length(z.a || z1.a) < 5))
|
||||||
|
select * from x;
|
||||||
|
a
|
||||||
|
------
|
||||||
|
a
|
||||||
|
b
|
||||||
|
aa
|
||||||
|
ab
|
||||||
|
ba
|
||||||
|
bb
|
||||||
|
aaaa
|
||||||
|
aaab
|
||||||
|
aaba
|
||||||
|
aabb
|
||||||
|
abaa
|
||||||
|
abab
|
||||||
|
abba
|
||||||
|
abbb
|
||||||
|
baaa
|
||||||
|
baab
|
||||||
|
baba
|
||||||
|
babb
|
||||||
|
bbaa
|
||||||
|
bbab
|
||||||
|
bbba
|
||||||
|
bbbb
|
||||||
|
(22 rows)
|
||||||
|
|
||||||
|
explain (verbose, costs off)
|
||||||
|
with recursive x(a) as
|
||||||
|
((values ('a'), ('b'))
|
||||||
|
union all
|
||||||
|
(with z as not materialized (select * from x)
|
||||||
|
select z.a || z.a as a from z
|
||||||
|
where length(z.a || z.a) < 5))
|
||||||
|
select * from x;
|
||||||
|
QUERY PLAN
|
||||||
|
--------------------------------------------------------
|
||||||
|
CTE Scan on x
|
||||||
|
Output: x.a
|
||||||
|
CTE x
|
||||||
|
-> Recursive Union
|
||||||
|
-> Values Scan on "*VALUES*"
|
||||||
|
Output: "*VALUES*".column1
|
||||||
|
-> WorkTable Scan on x x_1
|
||||||
|
Output: (x_1.a || x_1.a)
|
||||||
|
Filter: (length((x_1.a || x_1.a)) < 5)
|
||||||
|
(9 rows)
|
||||||
|
|
||||||
|
with recursive x(a) as
|
||||||
|
((values ('a'), ('b'))
|
||||||
|
union all
|
||||||
|
(with z as not materialized (select * from x)
|
||||||
|
select z.a || z.a as a from z
|
||||||
|
where length(z.a || z.a) < 5))
|
||||||
|
select * from x;
|
||||||
|
a
|
||||||
|
------
|
||||||
|
a
|
||||||
|
b
|
||||||
|
aa
|
||||||
|
bb
|
||||||
|
aaaa
|
||||||
|
bbbb
|
||||||
|
(6 rows)
|
||||||
|
|
||||||
-- Check handling of outer references
|
-- Check handling of outer references
|
||||||
explain (verbose, costs off)
|
explain (verbose, costs off)
|
||||||
with x as (select * from int4_tbl)
|
with x as (select * from int4_tbl)
|
||||||
|
@ -690,6 +690,41 @@ explain (verbose, costs off)
|
|||||||
with x as not materialized (select * from (select f1, now() as n from subselect_tbl) ss)
|
with x as not materialized (select * from (select f1, now() as n from subselect_tbl) ss)
|
||||||
select * from x, x x2 where x.n = x2.n;
|
select * from x, x x2 where x.n = x2.n;
|
||||||
|
|
||||||
|
-- Multiply-referenced CTEs can't be inlined if they contain outer self-refs
|
||||||
|
explain (verbose, costs off)
|
||||||
|
with recursive x(a) as
|
||||||
|
((values ('a'), ('b'))
|
||||||
|
union all
|
||||||
|
(with z as not materialized (select * from x)
|
||||||
|
select z.a || z1.a as a from z cross join z as z1
|
||||||
|
where length(z.a || z1.a) < 5))
|
||||||
|
select * from x;
|
||||||
|
|
||||||
|
with recursive x(a) as
|
||||||
|
((values ('a'), ('b'))
|
||||||
|
union all
|
||||||
|
(with z as not materialized (select * from x)
|
||||||
|
select z.a || z1.a as a from z cross join z as z1
|
||||||
|
where length(z.a || z1.a) < 5))
|
||||||
|
select * from x;
|
||||||
|
|
||||||
|
explain (verbose, costs off)
|
||||||
|
with recursive x(a) as
|
||||||
|
((values ('a'), ('b'))
|
||||||
|
union all
|
||||||
|
(with z as not materialized (select * from x)
|
||||||
|
select z.a || z.a as a from z
|
||||||
|
where length(z.a || z.a) < 5))
|
||||||
|
select * from x;
|
||||||
|
|
||||||
|
with recursive x(a) as
|
||||||
|
((values ('a'), ('b'))
|
||||||
|
union all
|
||||||
|
(with z as not materialized (select * from x)
|
||||||
|
select z.a || z.a as a from z
|
||||||
|
where length(z.a || z.a) < 5))
|
||||||
|
select * from x;
|
||||||
|
|
||||||
-- Check handling of outer references
|
-- Check handling of outer references
|
||||||
explain (verbose, costs off)
|
explain (verbose, costs off)
|
||||||
with x as (select * from int4_tbl)
|
with x as (select * from int4_tbl)
|
||||||
|
Reference in New Issue
Block a user