Fix possible crash with nested SubLinks.
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 10 Dec 2013 21:10:36 +0000 (16:10 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 10 Dec 2013 21:10:36 +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 896d3865ab7fb38d80ff1203d44e1a4469116119..bf0f25d78132b9fae48605ca057a9ce7d6261ef3 100644 (file)
@@ -772,11 +772,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,
@@ -815,6 +810,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 850777acd5cab0613b794d570a2d3c942b0d6baf..6194d259a1072a6d9449c85340179113c5d6d0ee 100644 (file)
@@ -639,3 +639,14 @@ where a.thousand = b.thousand
 ----------
 (0 rows)
 
+--
+-- Check sane behavior with nested IN SubLinks
+--
+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 8ca7a3bd2fb4838c1874f0dfdbcf29459bc61640..33b894c2b5e2b57e42f4b1766f1b695c25970417 100644 (file)
@@ -389,3 +389,10 @@ where a.thousand = b.thousand
   and exists ( select 1 from tenk1 c where b.hundred = c.hundred
                    and not exists ( select 1 from tenk1 d
                                     where a.thousand = d.thousand ) );
+
+--
+-- Check sane behavior with nested IN SubLinks
+--
+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);