mirror of
https://github.com/postgres/postgres.git
synced 2026-10-23 13:29:09 +03:00
Flatten join alias Vars before rechecking subquery pullup safety
pull_up_simple_subquery() rechecks is_simple_subquery() after pulling up the subquery's own subqueries, but it flattened join alias Vars in the subquery's targetlist only after that, and never in its quals. A join alias Var within the subquery, such as a whole-row Var of a join, can expand to an expression containing lateral references to the outer query once the join's inputs have been pulled up, and until it is flattened is_simple_subquery() cannot see them. We could thus pull up a LATERAL subquery whose targetlist or quals reference rels outside the lowest outer join above it. This leads to assertion failures in distribute_qual_to_rels() or "wrong phnullingrels" errors, and in some cases to wrong query results, where rows that should have been preserved by an outer join above the subquery are discarded. To fix, flatten join alias Vars in the targetlist, and in the jointree quals if the subquery is LATERAL, before the recheck. If the recheck fails, the flattened copy is discarded along with the rest of the modified subquery, so no harm is done. Back-patch to all supported branches, as this can lead to wrong query results. Author: Richard Guo <guofenglinux@gmail.com> Reviewed-by: Tatsuya Kawata <kawatatatsuya0913@gmail.com> Discussion: https://postgr.es/m/CAMbWs48GFZ=3Bjc1ug9JRsB0Qupg+CGcb9js5vmTM6kY2OGWOg@mail.gmail.com Backpatch-through: 14
This commit is contained in:
@@ -123,6 +123,8 @@ static bool is_safe_append_member(Query *subquery);
|
||||
static bool jointree_contains_lateral_outer_refs(PlannerInfo *root,
|
||||
Node *jtnode, bool restricted,
|
||||
Relids safe_upper_varnos);
|
||||
static void flatten_join_alias_vars_in_jointree(PlannerInfo *root,
|
||||
Node *jtnode);
|
||||
static void perform_pullup_replace_vars(PlannerInfo *root,
|
||||
pullup_replace_vars_context *rvcontext,
|
||||
AppendRelInfo *containing_appendrel);
|
||||
@@ -1221,6 +1223,26 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
|
||||
*/
|
||||
pull_up_subqueries(subroot);
|
||||
|
||||
/*
|
||||
* We must flatten any join alias Vars in the subquery's targetlist,
|
||||
* because pulling up the subquery's subqueries might have changed their
|
||||
* expansions into arbitrary expressions. That could affect
|
||||
* pullup_replace_vars' decisions about whether PlaceHolderVar wrappers
|
||||
* are needed for tlist entries, and if the subquery is LATERAL, the
|
||||
* expansions might contain lateral references that the is_simple_subquery
|
||||
* recheck below has to see. The latter also requires flattening join
|
||||
* alias Vars in the jointree quals of a LATERAL subquery. (Likely it'd
|
||||
* be better to do flatten_join_alias_vars on the whole query tree at some
|
||||
* earlier stage, maybe even in the rewriter; but for now let's just fix
|
||||
* these cases here.)
|
||||
*/
|
||||
subquery->targetList = (List *)
|
||||
flatten_join_alias_vars(subroot, subroot->parse,
|
||||
(Node *) subquery->targetList);
|
||||
if (rte->lateral)
|
||||
flatten_join_alias_vars_in_jointree(subroot,
|
||||
(Node *) subquery->jointree);
|
||||
|
||||
/*
|
||||
* Now we must recheck whether the subquery is still simple enough to pull
|
||||
* up. If not, abandon processing it.
|
||||
@@ -1247,19 +1269,6 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
|
||||
return jtnode;
|
||||
}
|
||||
|
||||
/*
|
||||
* We must flatten any join alias Vars in the subquery's targetlist,
|
||||
* because pulling up the subquery's subqueries might have changed their
|
||||
* expansions into arbitrary expressions, which could affect
|
||||
* pullup_replace_vars' decisions about whether PlaceHolderVar wrappers
|
||||
* are needed for tlist entries. (Likely it'd be better to do
|
||||
* flatten_join_alias_vars on the whole query tree at some earlier stage,
|
||||
* maybe even in the rewriter; but for now let's just fix this case here.)
|
||||
*/
|
||||
subquery->targetList = (List *)
|
||||
flatten_join_alias_vars(subroot, subroot->parse,
|
||||
(Node *) subquery->targetList);
|
||||
|
||||
/*
|
||||
* Adjust level-0 varnos in subquery so that we can append its rangetable
|
||||
* to upper query's. We have to fix the subquery's append_rel_list as
|
||||
@@ -2261,6 +2270,40 @@ jointree_contains_lateral_outer_refs(PlannerInfo *root, Node *jtnode,
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* flatten_join_alias_vars_in_jointree
|
||||
* Apply flatten_join_alias_vars to all quals in the given jointree,
|
||||
* replacing them in place. The jointree must belong to root->parse.
|
||||
*/
|
||||
static void
|
||||
flatten_join_alias_vars_in_jointree(PlannerInfo *root, Node *jtnode)
|
||||
{
|
||||
if (jtnode == NULL)
|
||||
return;
|
||||
if (IsA(jtnode, RangeTblRef))
|
||||
return;
|
||||
else if (IsA(jtnode, FromExpr))
|
||||
{
|
||||
FromExpr *f = (FromExpr *) jtnode;
|
||||
ListCell *l;
|
||||
|
||||
foreach(l, f->fromlist)
|
||||
flatten_join_alias_vars_in_jointree(root, lfirst(l));
|
||||
f->quals = flatten_join_alias_vars(root, root->parse, f->quals);
|
||||
}
|
||||
else if (IsA(jtnode, JoinExpr))
|
||||
{
|
||||
JoinExpr *j = (JoinExpr *) jtnode;
|
||||
|
||||
flatten_join_alias_vars_in_jointree(root, j->larg);
|
||||
flatten_join_alias_vars_in_jointree(root, j->rarg);
|
||||
j->quals = flatten_join_alias_vars(root, root->parse, j->quals);
|
||||
}
|
||||
else
|
||||
elog(ERROR, "unrecognized node type: %d",
|
||||
(int) nodeTag(jtnode));
|
||||
}
|
||||
|
||||
/*
|
||||
* Perform pullup_replace_vars everyplace it's needed in the query tree.
|
||||
*
|
||||
|
||||
@@ -7955,6 +7955,106 @@ lateral (select * from int8_tbl t1,
|
||||
0 | 4567890123456789 | -4567890123456789 | 4567890123456789 | 4567890123456789
|
||||
(3 rows)
|
||||
|
||||
-- check that lateral references hidden in a whole-row join alias Var
|
||||
-- prevent pullup of a LATERAL subquery
|
||||
explain (verbose, costs off)
|
||||
select i4.f1, ss.x, i8.q1
|
||||
from int4_tbl i4,
|
||||
lateral (select (j is null)::int
|
||||
from ((select i4.f1) s left join (select 1) v on false) j) ss(x)
|
||||
left join int8_tbl i8 on ss.x = i8.q1
|
||||
order by 1;
|
||||
QUERY PLAN
|
||||
--------------------------------------------------------------------------------
|
||||
Sort
|
||||
Output: i4.f1, ((((i4.f1 IS NULL) AND ((1) IS NULL)))::integer), i8.q1
|
||||
Sort Key: i4.f1
|
||||
-> Hash Left Join
|
||||
Output: i4.f1, ((((i4.f1 IS NULL) AND ((1) IS NULL)))::integer), i8.q1
|
||||
Hash Cond: (((((i4.f1 IS NULL) AND ((1) IS NULL)))::integer) = i8.q1)
|
||||
-> Nested Loop
|
||||
Output: i4.f1, ((((i4.f1 IS NULL) AND ((1) IS NULL)))::integer)
|
||||
-> Seq Scan on public.int4_tbl i4
|
||||
Output: i4.f1
|
||||
-> Nested Loop Left Join
|
||||
Output: (((i4.f1 IS NULL) AND ((1) IS NULL)))::integer
|
||||
Join Filter: false
|
||||
-> Result
|
||||
-> Result
|
||||
Output: 1
|
||||
One-Time Filter: false
|
||||
-> Hash
|
||||
Output: i8.q1
|
||||
-> Seq Scan on public.int8_tbl i8
|
||||
Output: i8.q1
|
||||
(21 rows)
|
||||
|
||||
select i4.f1, ss.x, i8.q1
|
||||
from int4_tbl i4,
|
||||
lateral (select (j is null)::int
|
||||
from ((select i4.f1) s left join (select 1) v on false) j) ss(x)
|
||||
left join int8_tbl i8 on ss.x = i8.q1
|
||||
order by 1;
|
||||
f1 | x | q1
|
||||
-------------+---+----
|
||||
-2147483647 | 0 |
|
||||
-123456 | 0 |
|
||||
0 | 0 |
|
||||
123456 | 0 |
|
||||
2147483647 | 0 |
|
||||
(5 rows)
|
||||
|
||||
-- same, with the lateral reference hidden in the subquery's quals
|
||||
explain (verbose, costs off)
|
||||
select i4.f1, i8.q1, ss.y
|
||||
from int4_tbl i4,
|
||||
int8_tbl i8 left join
|
||||
lateral (select 1 from ((select i4.f1) s left join (select 1) v on false) j
|
||||
where length(j::text) > 3) ss(y) on true
|
||||
where i4.f1 = 0
|
||||
order by 1, 2;
|
||||
QUERY PLAN
|
||||
-------------------------------------------------------------------------
|
||||
Sort
|
||||
Output: i4.f1, i8.q1, (1)
|
||||
Sort Key: i8.q1
|
||||
-> Nested Loop
|
||||
Output: i4.f1, i8.q1, (1)
|
||||
-> Seq Scan on public.int4_tbl i4
|
||||
Output: i4.f1
|
||||
Filter: (i4.f1 = 0)
|
||||
-> Nested Loop Left Join
|
||||
Output: i8.q1, (1)
|
||||
-> Seq Scan on public.int8_tbl i8
|
||||
Output: i8.q1, i8.q2
|
||||
-> Materialize
|
||||
Output: (1)
|
||||
-> Nested Loop Left Join
|
||||
Output: 1
|
||||
Join Filter: false
|
||||
Filter: (length((ROW(i4.f1, (1)))::text) > 3)
|
||||
-> Result
|
||||
-> Result
|
||||
Output: 1
|
||||
One-Time Filter: false
|
||||
(22 rows)
|
||||
|
||||
select i4.f1, i8.q1, ss.y
|
||||
from int4_tbl i4,
|
||||
int8_tbl i8 left join
|
||||
lateral (select 1 from ((select i4.f1) s left join (select 1) v on false) j
|
||||
where length(j::text) > 3) ss(y) on true
|
||||
where i4.f1 = 0
|
||||
order by 1, 2;
|
||||
f1 | q1 | y
|
||||
----+------------------+---
|
||||
0 | 123 | 1
|
||||
0 | 123 | 1
|
||||
0 | 4567890123456789 | 1
|
||||
0 | 4567890123456789 | 1
|
||||
0 | 4567890123456789 | 1
|
||||
(5 rows)
|
||||
|
||||
-- test some error cases where LATERAL should have been used but wasn't
|
||||
select f1,g from int4_tbl a, (select f1 as g) ss;
|
||||
ERROR: column "f1" does not exist
|
||||
|
||||
@@ -2877,6 +2877,39 @@ lateral (select * from int8_tbl t1,
|
||||
and (select v.id=0)) offset 0) ss2) ss
|
||||
where t1.q1 = ss.q2) ss0;
|
||||
|
||||
-- check that lateral references hidden in a whole-row join alias Var
|
||||
-- prevent pullup of a LATERAL subquery
|
||||
explain (verbose, costs off)
|
||||
select i4.f1, ss.x, i8.q1
|
||||
from int4_tbl i4,
|
||||
lateral (select (j is null)::int
|
||||
from ((select i4.f1) s left join (select 1) v on false) j) ss(x)
|
||||
left join int8_tbl i8 on ss.x = i8.q1
|
||||
order by 1;
|
||||
select i4.f1, ss.x, i8.q1
|
||||
from int4_tbl i4,
|
||||
lateral (select (j is null)::int
|
||||
from ((select i4.f1) s left join (select 1) v on false) j) ss(x)
|
||||
left join int8_tbl i8 on ss.x = i8.q1
|
||||
order by 1;
|
||||
|
||||
-- same, with the lateral reference hidden in the subquery's quals
|
||||
explain (verbose, costs off)
|
||||
select i4.f1, i8.q1, ss.y
|
||||
from int4_tbl i4,
|
||||
int8_tbl i8 left join
|
||||
lateral (select 1 from ((select i4.f1) s left join (select 1) v on false) j
|
||||
where length(j::text) > 3) ss(y) on true
|
||||
where i4.f1 = 0
|
||||
order by 1, 2;
|
||||
select i4.f1, i8.q1, ss.y
|
||||
from int4_tbl i4,
|
||||
int8_tbl i8 left join
|
||||
lateral (select 1 from ((select i4.f1) s left join (select 1) v on false) j
|
||||
where length(j::text) > 3) ss(y) on true
|
||||
where i4.f1 = 0
|
||||
order by 1, 2;
|
||||
|
||||
-- test some error cases where LATERAL should have been used but wasn't
|
||||
select f1,g from int4_tbl a, (select f1 as g) ss;
|
||||
select f1,g from int4_tbl a, (select a.f1 as g) ss;
|
||||
|
||||
Reference in New Issue
Block a user