Skip to content

Commit 9ec6199

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 53685d7 commit 9ec6199

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

src/test/regress/expected/subselect.out

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -710,3 +710,32 @@ select exists(select * from nocolumns);
710710
f
711711
(1 row)
712712

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

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)