Skip to content

Commit 41e9990

Browse files
committed
Fix possible crash with nested SubLinks.
An expression such as WHERE (... x IN (SELECT ...) ...) IN (SELECT ...) could produce an invalid plan that results in a crash at execution time, if the planner attempts to flatten the outer IN into a semi-join. This happens because convert_testexpr() was not expecting any nested SubLinks and would wrongly replace any PARAM_SUBLINK Params belonging to the inner SubLink. (I think the comment denying that this case could happen was wrong when written; it's certainly been wrong for quite a long time, since very early versions of the semijoin flattening logic.) Per report from Teodor Sigaev. Back-patch to all supported branches.
1 parent 9057adc commit 41e9990

File tree

3 files changed

+62
-5
lines changed

3 files changed

+62
-5
lines changed

src/backend/optimizer/plan/subselect.c

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -766,11 +766,6 @@ generate_subquery_vars(PlannerInfo *root, List *tlist, Index varno)
766766
* with Params or Vars representing the results of the sub-select. The
767767
* nodes to be substituted are passed in as the List result from
768768
* generate_subquery_params or generate_subquery_vars.
769-
*
770-
* The given testexpr has already been recursively processed by
771-
* process_sublinks_mutator. Hence it can no longer contain any
772-
* PARAM_SUBLINK Params for lower SubLink nodes; we can safely assume that
773-
* any we find are for our own level of SubLink.
774769
*/
775770
static Node *
776771
convert_testexpr(PlannerInfo *root,
@@ -809,6 +804,28 @@ convert_testexpr_mutator(Node *node,
809804
param->paramid - 1));
810805
}
811806
}
807+
if (IsA(node, SubLink))
808+
{
809+
/*
810+
* If we come across a nested SubLink, it is neither necessary nor
811+
* correct to recurse into it: any PARAM_SUBLINKs we might find inside
812+
* belong to the inner SubLink not the outer. So just return it as-is.
813+
*
814+
* This reasoning depends on the assumption that nothing will pull
815+
* subexpressions into or out of the testexpr field of a SubLink, at
816+
* least not without replacing PARAM_SUBLINKs first. If we did want
817+
* to do that we'd need to rethink the parser-output representation
818+
* altogether, since currently PARAM_SUBLINKs are only unique per
819+
* SubLink not globally across the query. The whole point of
820+
* replacing them with Vars or PARAM_EXEC nodes is to make them
821+
* globally unique before they escape from the SubLink's testexpr.
822+
*
823+
* Note: this can't happen when called during SS_process_sublinks,
824+
* because that recursively processes inner SubLinks first. It can
825+
* happen when called from convert_ANY_sublink_to_join, though.
826+
*/
827+
return node;
828+
}
812829
return expression_tree_mutator(node,
813830
convert_testexpr_mutator,
814831
(void *) context);

src/test/regress/expected/subselect.out

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -703,3 +703,32 @@ explain (verbose, costs off)
703703
One-Time Filter: ($0 = $0)
704704
(8 rows)
705705

706+
--
707+
-- Check sane behavior with nested IN SubLinks
708+
--
709+
explain (verbose, costs off)
710+
select * from int4_tbl where
711+
(case when f1 in (select unique1 from tenk1 a) then f1 else null end) in
712+
(select ten from tenk1 b);
713+
QUERY PLAN
714+
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
715+
Nested Loop Semi Join
716+
Output: int4_tbl.f1
717+
Join Filter: (CASE WHEN (hashed SubPlan 1) THEN int4_tbl.f1 ELSE NULL::integer END = b.ten)
718+
-> Seq Scan on public.int4_tbl
719+
Output: int4_tbl.f1
720+
-> Seq Scan on public.tenk1 b
721+
Output: b.unique1, b.unique2, b.two, b.four, b.ten, b.twenty, b.hundred, b.thousand, b.twothousand, b.fivethous, b.tenthous, b.odd, b.even, b.stringu1, b.stringu2, b.string4
722+
SubPlan 1
723+
-> Seq Scan on public.tenk1 a
724+
Output: a.unique1
725+
(10 rows)
726+
727+
select * from int4_tbl where
728+
(case when f1 in (select unique1 from tenk1 a) then f1 else null end) in
729+
(select ten from tenk1 b);
730+
f1
731+
----
732+
0
733+
(1 row)
734+

src/test/regress/sql/subselect.sql

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,3 +405,14 @@ explain (verbose, costs off)
405405
explain (verbose, costs off)
406406
select x, x from
407407
(select (select random() where y=y) as x from (values(1),(2)) v(y)) ss;
408+
409+
--
410+
-- Check sane behavior with nested IN SubLinks
411+
--
412+
explain (verbose, costs off)
413+
select * from int4_tbl where
414+
(case when f1 in (select unique1 from tenk1 a) then f1 else null end) in
415+
(select ten from tenk1 b);
416+
select * from int4_tbl where
417+
(case when f1 in (select unique1 from tenk1 a) then f1 else null end) in
418+
(select ten from tenk1 b);

0 commit comments

Comments
 (0)