Skip to content

Commit f5d9fdc

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 7f4ef62 commit f5d9fdc

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
@@ -855,11 +855,6 @@ generate_subquery_vars(PlannerInfo *root, List *tlist, Index varno)
855855
* with Params or Vars representing the results of the sub-select. The
856856
* nodes to be substituted are passed in as the List result from
857857
* generate_subquery_params or generate_subquery_vars.
858-
*
859-
* The given testexpr has already been recursively processed by
860-
* process_sublinks_mutator. Hence it can no longer contain any
861-
* PARAM_SUBLINK Params for lower SubLink nodes; we can safely assume that
862-
* any we find are for our own level of SubLink.
863858
*/
864859
static Node *
865860
convert_testexpr(PlannerInfo *root,
@@ -898,6 +893,28 @@ convert_testexpr_mutator(Node *node,
898893
param->paramid - 1));
899894
}
900895
}
896+
if (IsA(node, SubLink))
897+
{
898+
/*
899+
* If we come across a nested SubLink, it is neither necessary nor
900+
* correct to recurse into it: any PARAM_SUBLINKs we might find inside
901+
* belong to the inner SubLink not the outer. So just return it as-is.
902+
*
903+
* This reasoning depends on the assumption that nothing will pull
904+
* subexpressions into or out of the testexpr field of a SubLink, at
905+
* least not without replacing PARAM_SUBLINKs first. If we did want
906+
* to do that we'd need to rethink the parser-output representation
907+
* altogether, since currently PARAM_SUBLINKs are only unique per
908+
* SubLink not globally across the query. The whole point of
909+
* replacing them with Vars or PARAM_EXEC nodes is to make them
910+
* globally unique before they escape from the SubLink's testexpr.
911+
*
912+
* Note: this can't happen when called during SS_process_sublinks,
913+
* because that recursively processes inner SubLinks first. It can
914+
* happen when called from convert_ANY_sublink_to_join, though.
915+
*/
916+
return node;
917+
}
901918
return expression_tree_mutator(node,
902919
convert_testexpr_mutator,
903920
(void *) context);

src/test/regress/expected/subselect.out

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -713,3 +713,32 @@ select exists(select * from nocolumns);
713713
f
714714
(1 row)
715715

716+
--
717+
-- Check sane behavior with nested IN SubLinks
718+
--
719+
explain (verbose, costs off)
720+
select * from int4_tbl where
721+
(case when f1 in (select unique1 from tenk1 a) then f1 else null end) in
722+
(select ten from tenk1 b);
723+
QUERY PLAN
724+
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
725+
Nested Loop Semi Join
726+
Output: int4_tbl.f1
727+
Join Filter: (CASE WHEN (hashed SubPlan 1) THEN int4_tbl.f1 ELSE NULL::integer END = b.ten)
728+
-> Seq Scan on public.int4_tbl
729+
Output: int4_tbl.f1
730+
-> Seq Scan on public.tenk1 b
731+
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
732+
SubPlan 1
733+
-> Index Only Scan using tenk1_unique1 on public.tenk1 a
734+
Output: a.unique1
735+
(10 rows)
736+
737+
select * from int4_tbl where
738+
(case when f1 in (select unique1 from tenk1 a) then f1 else null end) in
739+
(select ten from tenk1 b);
740+
f1
741+
----
742+
0
743+
(1 row)
744+

src/test/regress/sql/subselect.sql

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,3 +411,14 @@ explain (verbose, costs off)
411411
--
412412
create temp table nocolumns();
413413
select exists(select * from nocolumns);
414+
415+
--
416+
-- Check sane behavior with nested IN SubLinks
417+
--
418+
explain (verbose, costs off)
419+
select * from int4_tbl where
420+
(case when f1 in (select unique1 from tenk1 a) then f1 else null end) in
421+
(select ten from tenk1 b);
422+
select * from int4_tbl where
423+
(case when f1 in (select unique1 from tenk1 a) then f1 else null end) in
424+
(select ten from tenk1 b);

0 commit comments

Comments
 (0)