]> granicus.if.org Git - postgresql/commitdiff
Fix possible crash with nested SubLinks.
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 10 Dec 2013 21:10:24 +0000 (16:10 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 10 Dec 2013 21:10:24 +0000 (16:10 -0500)
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.

src/backend/optimizer/plan/subselect.c
src/test/regress/expected/subselect.out
src/test/regress/sql/subselect.sql

index 22f2a55b4ac8dfe0bdc28cc2e8c6dd700a937313..adb50c2edf11b3da976eb2d3e179146c54529c97 100644 (file)
@@ -855,11 +855,6 @@ generate_subquery_vars(PlannerInfo *root, List *tlist, Index varno)
  * with Params or Vars representing the results of the sub-select.     The
  * nodes to be substituted are passed in as the List result from
  * generate_subquery_params or generate_subquery_vars.
- *
- * The given testexpr has already been recursively processed by
- * process_sublinks_mutator.  Hence it can no longer contain any
- * PARAM_SUBLINK Params for lower SubLink nodes; we can safely assume that
- * any we find are for our own level of SubLink.
  */
 static Node *
 convert_testexpr(PlannerInfo *root,
@@ -898,6 +893,28 @@ convert_testexpr_mutator(Node *node,
                                                                                                param->paramid - 1));
                }
        }
+       if (IsA(node, SubLink))
+       {
+               /*
+                * If we come across a nested SubLink, it is neither necessary nor
+                * correct to recurse into it: any PARAM_SUBLINKs we might find inside
+                * belong to the inner SubLink not the outer. So just return it as-is.
+                *
+                * This reasoning depends on the assumption that nothing will pull
+                * subexpressions into or out of the testexpr field of a SubLink, at
+                * least not without replacing PARAM_SUBLINKs first.  If we did want
+                * to do that we'd need to rethink the parser-output representation
+                * altogether, since currently PARAM_SUBLINKs are only unique per
+                * SubLink not globally across the query.  The whole point of
+                * replacing them with Vars or PARAM_EXEC nodes is to make them
+                * globally unique before they escape from the SubLink's testexpr.
+                *
+                * Note: this can't happen when called during SS_process_sublinks,
+                * because that recursively processes inner SubLinks first.  It can
+                * happen when called from convert_ANY_sublink_to_join, though.
+                */
+               return node;
+       }
        return expression_tree_mutator(node,
                                                                   convert_testexpr_mutator,
                                                                   (void *) context);
index 440ea0c98ffa5ed870c5eb8740059c4f88874b5a..cde168e3c801f3af8c7828fec4520ef9bcd2640c 100644 (file)
@@ -713,3 +713,32 @@ select exists(select * from nocolumns);
  f
 (1 row)
 
+--
+-- Check sane behavior with nested IN SubLinks
+--
+explain (verbose, costs off)
+select * from int4_tbl where
+  (case when f1 in (select unique1 from tenk1 a) then f1 else null end) in
+  (select ten from tenk1 b);
+                                                                                      QUERY PLAN                                                                                       
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop Semi Join
+   Output: int4_tbl.f1
+   Join Filter: (CASE WHEN (hashed SubPlan 1) THEN int4_tbl.f1 ELSE NULL::integer END = b.ten)
+   ->  Seq Scan on public.int4_tbl
+         Output: int4_tbl.f1
+   ->  Seq Scan on public.tenk1 b
+         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
+   SubPlan 1
+     ->  Index Only Scan using tenk1_unique1 on public.tenk1 a
+           Output: a.unique1
+(10 rows)
+
+select * from int4_tbl where
+  (case when f1 in (select unique1 from tenk1 a) then f1 else null end) in
+  (select ten from tenk1 b);
+ f1 
+----
+  0
+(1 row)
+
index 278580797ecb41b81aef1984116334536e4fb721..326fd70e4a06bea8db43f24b878e1a79377c02b4 100644 (file)
@@ -411,3 +411,14 @@ explain (verbose, costs off)
 --
 create temp table nocolumns();
 select exists(select * from nocolumns);
+
+--
+-- Check sane behavior with nested IN SubLinks
+--
+explain (verbose, costs off)
+select * from int4_tbl where
+  (case when f1 in (select unique1 from tenk1 a) then f1 else null end) in
+  (select ten from tenk1 b);
+select * from int4_tbl where
+  (case when f1 in (select unique1 from tenk1 a) then f1 else null end) in
+  (select ten from tenk1 b);