Fix testing of parallel-safety of SubPlans.
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 18 Apr 2017 19:43:56 +0000 (15:43 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 18 Apr 2017 19:43:56 +0000 (15:43 -0400)
is_parallel_safe() supposed that the only relevant property of a SubPlan
was the parallel safety of the referenced subplan tree.  This is wrong:
the testexpr or args subtrees might contain parallel-unsafe stuff, as
demonstrated by the test case added here.  However, just recursing into the
subtrees fails in a different way: we'll typically find PARAM_EXEC Params
representing the subplan's output columns in the testexpr.  The previous
coding supposed that any Param must be treated as parallel-restricted, so
that a naive attempt at fixing this disabled parallel pushdown of SubPlans
altogether.  We must instead determine, for any visited Param, whether it
is one that would be computed by a surrounding SubPlan node; if so, it's
safe to push down along with the SubPlan node.

We might later be able to extend this logic to cope with Params used for
correlated subplans and other cases; but that's a task for v11 or beyond.

Tom Lane and Amit Kapila

Discussion: https://postgr.es/m/7064.1492022469@sss.pgh.pa.us

src/backend/optimizer/util/clauses.c
src/include/nodes/primnodes.h
src/test/regress/expected/select_parallel.out
src/test/regress/sql/select_parallel.sql

index e196c5e2b5d83c49c8ac1d443ffbb61c42c0520e..a1dafc8e0f83f033297cccbab654d5ec8fc28dca 100644 (file)
@@ -93,6 +93,7 @@ typedef struct
 {
    char        max_hazard;     /* worst proparallel hazard found so far */
    char        max_interesting;    /* worst proparallel hazard of interest */
+   List       *safe_param_ids; /* PARAM_EXEC Param IDs to treat as safe */
 } max_parallel_hazard_context;
 
 static bool contain_agg_clause_walker(Node *node, void *context);
@@ -1056,6 +1057,7 @@ max_parallel_hazard(Query *parse)
 
    context.max_hazard = PROPARALLEL_SAFE;
    context.max_interesting = PROPARALLEL_UNSAFE;
+   context.safe_param_ids = NIL;
    (void) max_parallel_hazard_walker((Node *) parse, &context);
    return context.max_hazard;
 }
@@ -1084,6 +1086,7 @@ is_parallel_safe(PlannerInfo *root, Node *node)
    /* Else use max_parallel_hazard's search logic, but stop on RESTRICTED */
    context.max_hazard = PROPARALLEL_SAFE;
    context.max_interesting = PROPARALLEL_RESTRICTED;
+   context.safe_param_ids = NIL;
    return !max_parallel_hazard_walker(node, &context);
 }
 
@@ -1171,18 +1174,49 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
            return true;
    }
 
-   /* We can push the subplans only if they are parallel-safe. */
+   /*
+    * Only parallel-safe SubPlans can be sent to workers.  Within the
+    * testexpr of the SubPlan, Params representing the output columns of the
+    * subplan can be treated as parallel-safe, so temporarily add their IDs
+    * to the safe_param_ids list while examining the testexpr.
+    */
    else if (IsA(node, SubPlan))
-       return !((SubPlan *) node)->parallel_safe;
+   {
+       SubPlan    *subplan = (SubPlan *) node;
+       List       *save_safe_param_ids;
+
+       if (!subplan->parallel_safe &&
+           max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
+           return true;
+       save_safe_param_ids = context->safe_param_ids;
+       context->safe_param_ids = list_concat(list_copy(subplan->paramIds),
+                                             context->safe_param_ids);
+       if (max_parallel_hazard_walker(subplan->testexpr, context))
+           return true;        /* no need to restore safe_param_ids */
+       context->safe_param_ids = save_safe_param_ids;
+       /* we must also check args, but no special Param treatment there */
+       if (max_parallel_hazard_walker((Node *) subplan->args, context))
+           return true;
+       /* don't want to recurse normally, so we're done */
+       return false;
+   }
 
    /*
     * We can't pass Params to workers at the moment either, so they are also
-    * parallel-restricted.
+    * parallel-restricted, unless they are PARAM_EXEC Params listed in
+    * safe_param_ids, meaning they could be generated within the worker.
     */
    else if (IsA(node, Param))
    {
-       if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
-           return true;
+       Param      *param = (Param *) node;
+
+       if (param->paramkind != PARAM_EXEC ||
+           !list_member_int(context->safe_param_ids, param->paramid))
+       {
+           if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
+               return true;
+       }
+       return false;           /* nothing to recurse to */
    }
 
    /*
index b87fe845458edc39a5d2ea63620d7e12304b9393..86ec82eaaae8637918b3d1a1df84ed20978b8192 100644 (file)
@@ -699,7 +699,8 @@ typedef struct SubPlan
    bool        unknownEqFalse; /* TRUE if it's okay to return FALSE when the
                                 * spec result is UNKNOWN; this allows much
                                 * simpler handling of null values */
-   bool        parallel_safe;  /* OK to use as part of parallel plan? */
+   bool        parallel_safe;  /* is the subplan parallel-safe? */
+   /* Note: parallel_safe does not consider contents of testexpr or args */
    /* Information for passing params into and out of the subselect: */
    /* setParam and parParam are lists of integers (param IDs) */
    List       *setParam;       /* initplan subqueries have to set these
index 0e9bc1a70784d320a5eec62e9af9578249fc1f09..3e35e96c4b3a8f88a03fedaee9783bee224d5776 100644 (file)
@@ -126,6 +126,18 @@ select count(*) from tenk1 where (two, four) not in
  10000
 (1 row)
 
+-- this is not parallel-safe due to use of random() within SubLink's testexpr:
+explain (costs off)
+   select * from tenk1 where (unique1 + random())::integer not in
+   (select ten from tenk2);
+             QUERY PLAN             
+------------------------------------
+ Seq Scan on tenk1
+   Filter: (NOT (hashed SubPlan 1))
+   SubPlan 1
+     ->  Seq Scan on tenk2
+(4 rows)
+
 alter table tenk2 reset (parallel_workers);
 -- test parallel index scans.
 set enable_seqscan to off;
index 67bc82e83477df1ab5194b8c2972d94ac2429801..d2d262c7249599cd10156f3402d0983f990dfdc9 100644 (file)
@@ -46,6 +46,10 @@ explain (costs off)
    (select hundred, thousand from tenk2 where thousand > 100);
 select count(*) from tenk1 where (two, four) not in
    (select hundred, thousand from tenk2 where thousand > 100);
+-- this is not parallel-safe due to use of random() within SubLink's testexpr:
+explain (costs off)
+   select * from tenk1 where (unique1 + random())::integer not in
+   (select ten from tenk2);
 alter table tenk2 reset (parallel_workers);
 
 -- test parallel index scans.