parallelize queries containing initplans
By seeing the subject line, one might wonder why we need to consider
parallelizing the queries containing initplans differently from
queries containing subplans considering that I have posted a mail to
achieve later a few hours back. The reason is that both are treated
differently with respect to parallelism and otherwise as well and both
can be parallelized in a different way depending on the design we
choose. InitPlans can be used in three forms (a) a Param node
representing a single scalar result (b) a row comparison tree
containing multiple Param nodes (c) NULL constant for MULTIEXPR
subquery whereas SubPlans are used as SubPlan nodes. Here, I am
primarily interested in parallelizing queries that contain InitPlans
of the form (a) and the reason is that I have seen that form used more
as compared to other forms (primarily based on a study of TPC-H and
TPC-DS workloads). However, if we find that parallelizing other forms
can be done along with it easily, then that is excellent. To start
with let us see the plan of TPC-H query (Q-22) and understand how it
can be improved.
Limit
InitPlan 1 (returns $0)
-> Finalize Aggregate
-> Gather
Workers Planned: 2
-> Partial Aggregate
-> Parallel Seq Scan on customer customer_1
Filter: (...)
-> GroupAggregate
Group Key: ("substring"((customer.c_phone)::text, 1, 2))
-> Sort
Sort Key: ("substring"((customer.c_phone)::text, 1, 2))
-> Nested Loop Anti Join
-> Seq Scan on customer
Filter: ((c_acctbal > $0) AND (...)))
-> Index Only Scan using idx_orders_custkey on orders
Index Cond: (o_custkey = customer.c_custkey)
In the above plan, we can see that the join on customer and orders
table (Nested Loop Anti Join) is not parallelised even though we have
the capability to parallelize Nested Loop Joins. The reason for not
choosing the parallel plan is that one of the nodes (Seq Scan on
customer) is referring to initplan and we consider such nodes as
parallel-restricted which means they can't be parallelised. Now, I
could see three ways of parallelizing such a query. The first way is
that we just push parallel-safe initplans to workers and allow them to
execute it, the drawback of this approach is that it won't be able to
push initplans in cases as shown above where initplan is
parallel-unsafe (contains Gather node) and second is we will lose the
expectation of single evaluation. The second way is that we always
execute the initplan in the master backend and pass the resultant
value to the worker, this will allow above form of plans to push
initplans to workers and hence can help in enabling parallelism for
other nodes in plan tree. The drawback of the second approach is
that we need to evaluate the initplan before it is actually required
which means that we might evaluate it even when it is not required. I
am not sure if it is always safe to assume that we can evaluate the
initplan before pushing it to workers especially for the cases when it
is far enough down in the plan tree which we are parallelizing,
however, I think we can assume it when the iniplan is above the plan
tree where it is used (like in the above case). The third way is that
we allow Gather node to be executed below another Gather node, but I
think that will be bad especially for the plans like above because
each worker needs to further spawn another set of workers to evaluate
the iniplan which could be done once. Now we can build some way such
that only one of the workers executes such an initplan and share the
values with other workers, but I think overall this requires much more
effort than first or second approach.
Among all the three approaches, first seems to be simpler than the
other two, but I feel if we just do that then we leave a lot on the
table. Another way to accomplish this project could be that we do a
mix of first and second such that when the initplan is above the plan
tree to be parallelized, then use the second approach (one-time
evaluation by master backend and share the result with workers),
otherwise use the first approach of pushing down the initplan to
workers.
Thoughts?
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Dec 28, 2016 at 5:20 PM, Amit Kapila <amit.kapila16@gmail.com> wrote:
To start
with let us see the plan of TPC-H query (Q-22) and understand how it
can be improved.Limit
InitPlan 1 (returns $0)
-> Finalize Aggregate
-> Gather
Workers Planned: 2
-> Partial Aggregate
-> Parallel Seq Scan on customer customer_1
Filter: (...)
-> GroupAggregate
Group Key: ("substring"((customer.c_phone)::text, 1, 2))
-> Sort
Sort Key: ("substring"((customer.c_phone)::text, 1, 2))
-> Nested Loop Anti Join
-> Seq Scan on customer
Filter: ((c_acctbal > $0) AND (...)))
-> Index Only Scan using idx_orders_custkey on orders
Index Cond: (o_custkey = customer.c_custkey)In the above plan, we can see that the join on customer and orders
table (Nested Loop Anti Join) is not parallelised even though we have
the capability to parallelize Nested Loop Joins. The reason for not
choosing the parallel plan is that one of the nodes (Seq Scan on
customer) is referring to initplan and we consider such nodes as
parallel-restricted which means they can't be parallelised. Now, I
could see three ways of parallelizing such a query. The first way is
that we just push parallel-safe initplans to workers and allow them to
execute it, the drawback of this approach is that it won't be able to
push initplans in cases as shown above where initplan is
parallel-unsafe (contains Gather node) and second is we will lose the
expectation of single evaluation. The second way is that we always
execute the initplan in the master backend and pass the resultant
value to the worker, this will allow above form of plans to push
initplans to workers and hence can help in enabling parallelism for
other nodes in plan tree.
I have used the second way to parallelize queries containing initplans
as that can help in cases where initplans in itself also uses
parallelism and it will also retain an existing expectation of single
evaluation for initplans. The basic idea as mentioned in above mail is
to evaluate the initplans at Gather node and pass the value to worker
backends which can use it as required. The patch has used
*plan->allParam* bitmapset to evaluate the initplans at Gather node
(we traverse the planstate tree to find params at each node and we
take care to avoid multiple evaluations of same initplan). To
identify initplan params among other params in *allParams*, the patch
has added an additional bool variable (isinitplan) in ParamExecData.
We can do it in some other way as well if there is any better
suggestion.
The patch has also changed the explain output of queries where
initplan param is evaluated at Gather node. For ex.
postgres=# explain (costs off) select t1.i from t1, t2 where t1.j=t2.j
and t1.k < (select max(k) from t3) and t1.k < (select max(k) from t3);
QUERY PLAN
--------------------------------------------------------
Hash Join
Hash Cond: (t2.j = t1.j)
InitPlan 1 (returns $0)
-> Finalize Aggregate
-> Gather
Workers Planned: 1
-> Partial Aggregate
-> Parallel Seq Scan on t3
InitPlan 2 (returns $1)
-> Finalize Aggregate
-> Gather
Workers Planned: 1
-> Partial Aggregate
-> Parallel Seq Scan on t3 t3_1
-> Gather
Workers Planned: 1
-> Parallel Seq Scan on t2
-> Hash
-> Gather
Workers Planned: 1
Params Evaluated: $0, $1
-> Parallel Seq Scan on t1
Filter: ((k < $0) AND (k < $1))
(23 rows)
In the above plan, you can notice a line (Params Evaluated: $0, $1)
which indicates the params evaluated at Gather node. As of now,
explain just uses the *allParam* params present at the Gather node,
but we need to traverse the planstate tree as we do during execution.
This patch gives 2.5~3x performance benefit for Q-22 of TPC-H.
The drawback of the second approach is
that we need to evaluate the initplan before it is actually required
which means that we might evaluate it even when it is not required. I
am not sure if it is always safe to assume that we can evaluate the
initplan before pushing it to workers especially for the cases when it
is far enough down in the plan tree which we are parallelizing,
I think we can always pull up un-correlated initplans at Gather node,
however, if there is a correlated initplan, then it is better not to
allow such initplans for being pushed below gather. Ex. of correlated
initplans:
postgres=# explain (costs off) select * from t1 where t1.i in (select
t2.i from t2 where t1.k = (select max(k) from t3 where t3.i=t1.i));
QUERY PLAN
----------------------------------------------
Seq Scan on t1
Filter: (SubPlan 2)
SubPlan 2
-> Gather
Workers Planned: 1
Params Evaluated: $1
InitPlan 1 (returns $1)
-> Aggregate
-> Seq Scan on t3
Filter: (i = t1.i)
-> Result
One-Time Filter: (t1.k = $1)
-> Parallel Seq Scan on t2
(13 rows)
It might be safe to allow above plan, but in general, such plans
should not be allowed, because it might not be feasible to compute
such initplan references at Gather node. I am still thinking on the
best way to deal with such initplans.
Thoughts?
Thanks to Kuntal who is a co-author of this patch for doing the
investigation along with me of different plans which contain
references to initplans.
Note - This patch needs to be applied on top of subplan patches [1]/messages/by-id/CAA4eK1KYQjQzQMpEz+QRA2fmim386gQLQBEf+p2Wmtqjh1rjwg@mail.gmail.com[2]/messages/by-id/CAA4eK1LK3NjNY4ghHUOwYfBFa+Ab2SvccTKAxTHbOdW1NhUjvQ@mail.gmail.com.
[1]: /messages/by-id/CAA4eK1KYQjQzQMpEz+QRA2fmim386gQLQBEf+p2Wmtqjh1rjwg@mail.gmail.com
[2]: /messages/by-id/CAA4eK1LK3NjNY4ghHUOwYfBFa+Ab2SvccTKAxTHbOdW1NhUjvQ@mail.gmail.com
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
pq_pushdown_initplan_v1.patchapplication/octet-stream; name=pq_pushdown_initplan_v1.patchDownload
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 0a67be0..89b89bb 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -106,6 +106,7 @@ static void show_tidbitmap_info(BitmapHeapScanState *planstate,
static void show_instrumentation_count(const char *qlabel, int which,
PlanState *planstate, ExplainState *es);
static void show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es);
+static void show_gather_eval_params(PlanState *plastate, ExplainState *es);
static const char *explain_get_index_name(Oid indexId);
static void show_buffer_usage(ExplainState *es, const BufferUsage *usage);
static void ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir,
@@ -619,7 +620,17 @@ ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc)
*/
ps = queryDesc->planstate;
if (IsA(ps, GatherState) &&((Gather *) ps->plan)->invisible)
+ {
+ List *initPlanState = NULL;
+ PlanState *save_ps;
+
+ /* initplans are always attached to top node (cf standard_planner) */
+ save_ps = ps;
+ initPlanState = ps->initPlan;
ps = outerPlanState(ps);
+ ps->initPlan = initPlanState;
+ save_ps->initPlan = NIL;
+ }
ExplainNode(ps, NIL, NULL, NULL, es);
}
@@ -1382,6 +1393,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
planstate, es);
ExplainPropertyInteger("Workers Planned",
gather->num_workers, es);
+
+ /* Show params evaluated at gather node */
+ if (planstate->plan->allParam)
+ show_gather_eval_params(planstate, es);
+
if (es->analyze)
{
int nworkers;
@@ -2331,6 +2347,34 @@ show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es)
}
/*
+ * Show initplan params evaluated at gather node.
+ */
+static void
+show_gather_eval_params(PlanState *planstate, ExplainState *es)
+{
+ ParamExecData *prm;
+ int paramid = -1;
+ Bitmapset *bms_params = planstate->plan->allParam;
+ List *params = NIL;
+ EState *estate = planstate->state;
+
+ while ((paramid = bms_next_member(bms_params, paramid)) >= 0)
+ {
+ char param[32];
+
+ prm = &(estate->es_param_exec_vals[paramid]);
+ if (!prm->isinitplan)
+ continue;
+
+ snprintf(param, sizeof(param), "$%d", paramid);
+ params = lappend(params, pstrdup(param));
+ }
+
+ if (params)
+ ExplainPropertyList("Params Evaluated", params, es);
+}
+
+/*
* Fetch the name of an index in an EXPLAIN
*
* We allow plugins to get control here so that plans involving hypothetical
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index 3081316..63928a0 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -28,6 +28,7 @@
#include "executor/nodeCustom.h"
#include "executor/nodeForeignscan.h"
#include "executor/nodeSeqscan.h"
+#include "executor/nodeSubplan.h"
#include "executor/tqueue.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/planmain.h"
@@ -49,6 +50,8 @@
#define PARALLEL_KEY_TUPLE_QUEUE UINT64CONST(0xE000000000000004)
#define PARALLEL_KEY_INSTRUMENTATION UINT64CONST(0xE000000000000005)
#define PARALLEL_KEY_DSA UINT64CONST(0xE000000000000006)
+#define PARALLEL_KEY_INITPLAN_PARAMS UINT64CONST(0xE000000000000007)
+#define PARALLEL_KEY_INITPLAN_IDS UINT64CONST(0xE000000000000008)
#define PARALLEL_TUPLE_QUEUE_SIZE 65536
@@ -77,6 +80,14 @@ struct SharedExecutorInstrumentation
int plan_node_id[FLEXIBLE_ARRAY_MEMBER];
/* array of num_plan_nodes * num_workers Instrumentation objects follows */
};
+
+/* Context object for SharedExecutorInstrumentation. */
+struct SharedExecutorInstrumentationContext
+{
+ SharedExecutorInstrumentation *instrumentation;
+ Bitmapset *init_plan_node_ids;
+};
+
#define GetInstrumentationArray(sei) \
(AssertVariableIsOfTypeMacro(sei, SharedExecutorInstrumentation *), \
(Instrumentation *) (((char *) sei) + sei->instrument_offset))
@@ -85,6 +96,7 @@ struct SharedExecutorInstrumentation
typedef struct ExecParallelEstimateContext
{
ParallelContext *pcxt;
+ Bitmapset *init_plan_node_ids;
int nnodes;
} ExecParallelEstimateContext;
@@ -93,9 +105,24 @@ typedef struct ExecParallelInitializeDSMContext
{
ParallelContext *pcxt;
SharedExecutorInstrumentation *instrumentation;
+ Bitmapset *init_plan_node_ids;
int nnodes;
} ExecParallelInitializeDSMContext;
+/* Context object for ExecEvalInitPlans. */
+typedef struct ExecParallelEvalInitPlan
+{
+ Bitmapset *params;
+ Bitmapset *init_plan_node_ids;
+} ExecParallelEvalInitPlan;
+
+/* Context object for ExecParallelInitializeWorker. */
+typedef struct ParallelWorkerContext
+{
+ Bitmapset *init_plan_node_ids;
+ shm_toc *toc;
+} ParallelWorkerContext;
+
/* Helper functions that run in the parallel leader. */
static char *ExecSerializePlan(Plan *plan, EState *estate);
static bool ExecParallelEstimate(PlanState *node,
@@ -105,7 +132,7 @@ static bool ExecParallelInitializeDSM(PlanState *node,
static shm_mq_handle **ExecParallelSetupTupleQueues(ParallelContext *pcxt,
bool reinitialize);
static bool ExecParallelRetrieveInstrumentation(PlanState *planstate,
- SharedExecutorInstrumentation *instrumentation);
+ SharedExecutorInstrumentationContext *sei_context);
/* Helper functions that run in the parallel worker. */
static void ParallelQueryMain(dsm_segment *seg, shm_toc *toc);
@@ -185,6 +212,11 @@ ExecParallelEstimate(PlanState *planstate, ExecParallelEstimateContext *e)
if (planstate == NULL)
return false;
+ /* Skip nodes that are already evaluated. */
+ if (!bms_is_empty(e->init_plan_node_ids) &&
+ bms_is_member(planstate->plan->plan_node_id, e->init_plan_node_ids))
+ return false;
+
/* Count this node. */
e->nnodes++;
@@ -224,6 +256,11 @@ ExecParallelInitializeDSM(PlanState *planstate,
if (planstate == NULL)
return false;
+ /* Skip nodes that are already evaluated. */
+ if (!bms_is_empty(d->init_plan_node_ids) &&
+ bms_is_member(planstate->plan->plan_node_id, d->init_plan_node_ids))
+ return false;
+
/* If instrumentation is enabled, initialize slot for this node. */
if (d->instrumentation != NULL)
d->instrumentation->plan_node_id[d->nnodes] =
@@ -266,6 +303,36 @@ ExecParallelInitializeDSM(PlanState *planstate,
}
/*
+ * Execute the iniPlans, if not done already. This is different from the way
+ * initPlans are evaluated (lazy evaluation) at other places as instead of
+ * sharing the initPlan to all the workers and let them execute, we pass the
+ * values which can be directly used by worker backends. We do remember the
+ * initPlans that are already processed at the upper level of the tree. This
+ * helps in tracking them uniquely across the plan tree sent for execution to
+ * workers.
+ */
+static bool
+ExecEvalInitPlans(PlanState *planstate,
+ ExecParallelEvalInitPlan *eip)
+{
+ if (planstate->plan->allParam)
+ {
+ EState *estate = planstate->state;
+
+ /*
+ * Evaluate the setParam of initPlan's which are included only in
+ * plans' allParams.
+ */
+ ExecEvalParamExecParams(estate,
+ planstate->plan->allParam,
+ &(eip->params),
+ &(eip->init_plan_node_ids));
+ }
+
+ return planstate_tree_walker(planstate, ExecEvalInitPlans, eip);
+}
+
+/*
* It sets up the response queues for backend workers to return tuples
* to the main backend and start the workers.
*/
@@ -340,13 +407,18 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers)
ParallelContext *pcxt;
ExecParallelEstimateContext e;
ExecParallelInitializeDSMContext d;
+ ExecParallelEvalInitPlan eip;
char *pstmt_data;
char *pstmt_space;
char *param_space;
+ char *initplan_param_space;
+ char *initplan_nodes_space;
BufferUsage *bufusage_space;
SharedExecutorInstrumentation *instrumentation = NULL;
int pstmt_len;
int param_len;
+ int initplan_param_len;
+ int initplan_nodes_len;
int instrumentation_len = 0;
int instrument_offset = 0;
Size dsa_minsize = dsa_minimum_size();
@@ -355,6 +427,9 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers)
pei = palloc0(sizeof(ParallelExecutorInfo));
pei->finished = false;
pei->planstate = planstate;
+ eip.params = NULL;
+ eip.init_plan_node_ids = NULL;
+ ExecEvalInitPlans(planstate, &eip);
/* Fix up and serialize plan to be sent to workers. */
pstmt_data = ExecSerializePlan(planstate->plan, estate);
@@ -379,6 +454,16 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers)
shm_toc_estimate_chunk(&pcxt->estimator, param_len);
shm_toc_estimate_keys(&pcxt->estimator, 1);
+ /* Estimate space for initplan params. */
+ initplan_param_len = EstimateInitPlanParamsSpace(estate->es_param_exec_vals, eip.params);
+ shm_toc_estimate_chunk(&pcxt->estimator, initplan_param_len);
+ shm_toc_estimate_keys(&pcxt->estimator, 1);
+
+ /* Estimate space for evaluated initplan plan node ids. */
+ initplan_nodes_len = EstimateInitPlanNodeIdsSpace(eip.init_plan_node_ids);
+ shm_toc_estimate_chunk(&pcxt->estimator, initplan_nodes_len);
+ shm_toc_estimate_keys(&pcxt->estimator, 1);
+
/*
* Estimate space for BufferUsage.
*
@@ -401,6 +486,7 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers)
*/
e.pcxt = pcxt;
e.nnodes = 0;
+ e.init_plan_node_ids = eip.init_plan_node_ids;
ExecParallelEstimate(planstate, &e);
/* Estimate space for instrumentation, if required. */
@@ -443,6 +529,16 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers)
shm_toc_insert(pcxt->toc, PARALLEL_KEY_PARAMS, param_space);
SerializeParamList(estate->es_param_list_info, ¶m_space);
+ /* Store serialized initplan params. */
+ initplan_param_space = shm_toc_allocate(pcxt->toc, initplan_param_len);
+ shm_toc_insert(pcxt->toc, PARALLEL_KEY_INITPLAN_PARAMS, initplan_param_space);
+ SerializeInitPlanParams(estate->es_param_exec_vals, eip.params, &initplan_param_space);
+
+ /* Store serialized evaluated initplan plan node ids. */
+ initplan_nodes_space = shm_toc_allocate(pcxt->toc, initplan_nodes_len);
+ shm_toc_insert(pcxt->toc, PARALLEL_KEY_INITPLAN_IDS, initplan_nodes_space);
+ SerializeInitPlanNodeIds(eip.init_plan_node_ids, &initplan_nodes_space);
+
/* Allocate space for each worker's BufferUsage; no need to initialize. */
bufusage_space = shm_toc_allocate(pcxt->toc,
mul_size(sizeof(BufferUsage), pcxt->nworkers));
@@ -472,7 +568,9 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers)
InstrInit(&instrument[i], estate->es_instrument);
shm_toc_insert(pcxt->toc, PARALLEL_KEY_INSTRUMENTATION,
instrumentation);
- pei->instrumentation = instrumentation;
+ pei->sei_context = palloc0(sizeof(SharedExecutorInstrumentationContext));
+ pei->sei_context->instrumentation = instrumentation;
+ pei->sei_context->init_plan_node_ids = eip.init_plan_node_ids;
}
/*
@@ -504,6 +602,7 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers)
*/
d.pcxt = pcxt;
d.instrumentation = instrumentation;
+ d.init_plan_node_ids = eip.init_plan_node_ids;
d.nnodes = 0;
ExecParallelInitializeDSM(planstate, &d);
@@ -524,15 +623,23 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers)
*/
static bool
ExecParallelRetrieveInstrumentation(PlanState *planstate,
- SharedExecutorInstrumentation *instrumentation)
+ SharedExecutorInstrumentationContext *sei_context)
{
Instrumentation *instrument;
int i;
int n;
int ibytes;
int plan_node_id = planstate->plan->plan_node_id;
+ SharedExecutorInstrumentation *instrumentation;
MemoryContext oldcontext;
+ instrumentation = sei_context->instrumentation;
+
+ /* Skip nodes that are already evaluated. */
+ if (!bms_is_empty(sei_context->init_plan_node_ids) &&
+ bms_is_member(planstate->plan->plan_node_id, sei_context->init_plan_node_ids))
+ return false;
+
/* Find the instumentation for this node. */
for (i = 0; i < instrumentation->num_plan_nodes; ++i)
if (instrumentation->plan_node_id[i] == plan_node_id)
@@ -563,7 +670,7 @@ ExecParallelRetrieveInstrumentation(PlanState *planstate,
memcpy(&planstate->worker_instrument->instrument, instrument, ibytes);
return planstate_tree_walker(planstate, ExecParallelRetrieveInstrumentation,
- instrumentation);
+ sei_context);
}
/*
@@ -586,9 +693,9 @@ ExecParallelFinish(ParallelExecutorInfo *pei)
InstrAccumParallelQuery(&pei->buffer_usage[i]);
/* Finally, accumulate instrumentation, if any. */
- if (pei->instrumentation)
+ if (pei->sei_context)
ExecParallelRetrieveInstrumentation(pei->planstate,
- pei->instrumentation);
+ pei->sei_context);
pei->finished = true;
}
@@ -633,6 +740,35 @@ ExecParallelGetReceiver(dsm_segment *seg, shm_toc *toc)
}
/*
+ * Copy the ParamExecData params corresponding to initplans from dynamic
+ * shared memory. This has to be done once the params are allocated by
+ * executor; that is after ExecutorStart().
+ */
+static void
+ExecParallelInitializeInitPlanParams(shm_toc *toc, ParamExecData *params)
+{
+ char *paramspace;
+
+ /* Reconstruct initplan params. */
+ paramspace = shm_toc_lookup(toc, PARALLEL_KEY_INITPLAN_PARAMS);
+ RestoreInitPlanParams(¶mspace, params);
+}
+
+/*
+ * Copy the evaluated plan node ids corresponding to initplans from dynamic
+ * shared memory.
+ */
+static void
+ExecParallelInitializeInitPlanNodes(shm_toc *toc, Bitmapset **plan_node_ids)
+{
+ char *plan_nodes_space;
+
+ /* Reconstruct evaluated initplan plan node ids. */
+ plan_nodes_space = shm_toc_lookup(toc, PARALLEL_KEY_INITPLAN_IDS);
+ *plan_node_ids = RestoreInitPlanNodeIds(&plan_nodes_space);
+}
+
+/*
* Create a QueryDesc for the PlannedStmt we are to execute, and return it.
*/
static QueryDesc *
@@ -672,11 +808,19 @@ ExecParallelGetQueryDesc(shm_toc *toc, DestReceiver *receiver,
*/
static bool
ExecParallelReportInstrumentation(PlanState *planstate,
- SharedExecutorInstrumentation *instrumentation)
+ SharedExecutorInstrumentationContext *sei_context)
{
int i;
int plan_node_id = planstate->plan->plan_node_id;
Instrumentation *instrument;
+ SharedExecutorInstrumentation *instrumentation;
+
+ instrumentation = sei_context->instrumentation;
+
+ /* Skip nodes that are already evaluated. */
+ if (!bms_is_empty(sei_context->init_plan_node_ids) &&
+ bms_is_member(planstate->plan->plan_node_id, sei_context->init_plan_node_ids))
+ return false;
InstrEndLoop(planstate->instrument);
@@ -703,7 +847,7 @@ ExecParallelReportInstrumentation(PlanState *planstate,
InstrAggNode(&instrument[ParallelWorkerNumber], planstate->instrument);
return planstate_tree_walker(planstate, ExecParallelReportInstrumentation,
- instrumentation);
+ sei_context);
}
/*
@@ -712,33 +856,38 @@ ExecParallelReportInstrumentation(PlanState *planstate,
* is allocated and initialized by executor; that is, after ExecutorStart().
*/
static bool
-ExecParallelInitializeWorker(PlanState *planstate, shm_toc *toc)
+ExecParallelInitializeWorker(PlanState *planstate, ParallelWorkerContext *pw_context)
{
if (planstate == NULL)
return false;
+ /* Skip nodes that are already evaluated. */
+ if (!bms_is_empty(pw_context->init_plan_node_ids) &&
+ bms_is_member(planstate->plan->plan_node_id, pw_context->init_plan_node_ids))
+ return false;
+
/* Call initializers for parallel-aware plan nodes. */
if (planstate->plan->parallel_aware)
{
switch (nodeTag(planstate))
{
case T_SeqScanState:
- ExecSeqScanInitializeWorker((SeqScanState *) planstate, toc);
+ ExecSeqScanInitializeWorker((SeqScanState *) planstate, pw_context->toc);
break;
case T_ForeignScanState:
ExecForeignScanInitializeWorker((ForeignScanState *) planstate,
- toc);
+ pw_context->toc);
break;
case T_CustomScanState:
ExecCustomScanInitializeWorker((CustomScanState *) planstate,
- toc);
+ pw_context->toc);
break;
default:
break;
}
}
- return planstate_tree_walker(planstate, ExecParallelInitializeWorker, toc);
+ return planstate_tree_walker(planstate, ExecParallelInitializeWorker, pw_context);
}
/*
@@ -764,15 +913,23 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
DestReceiver *receiver;
QueryDesc *queryDesc;
SharedExecutorInstrumentation *instrumentation;
+ SharedExecutorInstrumentationContext *sei_context = NULL;
int instrument_options = 0;
void *area_space;
dsa_area *area;
+ ParallelWorkerContext *pw_context;
+ Bitmapset *init_plan_node_ids;
/* Set up DestReceiver, SharedExecutorInstrumentation, and QueryDesc. */
receiver = ExecParallelGetReceiver(seg, toc);
instrumentation = shm_toc_lookup(toc, PARALLEL_KEY_INSTRUMENTATION);
if (instrumentation != NULL)
+ {
+ sei_context = palloc0(sizeof(SharedExecutorInstrumentationContext));
+ sei_context->instrumentation = instrumentation;
instrument_options = instrumentation->instrument_options;
+ }
+
queryDesc = ExecParallelGetQueryDesc(toc, receiver, instrument_options);
/* Prepare to track buffer usage during query execution. */
@@ -787,7 +944,14 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
/* Special executor initialization steps for parallel workers */
queryDesc->planstate->state->es_query_dsa = area;
- ExecParallelInitializeWorker(queryDesc->planstate, toc);
+ ExecParallelInitializeInitPlanParams(toc, queryDesc->estate->es_param_exec_vals);
+ ExecParallelInitializeInitPlanNodes(toc, &init_plan_node_ids);
+
+ pw_context = palloc0(sizeof(ParallelWorkerContext));
+ pw_context->toc = toc;
+ pw_context->init_plan_node_ids = init_plan_node_ids;
+
+ ExecParallelInitializeWorker(queryDesc->planstate, pw_context);
/* Run the plan */
ExecutorRun(queryDesc, ForwardScanDirection, 0L);
@@ -801,8 +965,11 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
/* Report instrumentation data if any instrumentation options are set. */
if (instrumentation != NULL)
+ {
+ sei_context->init_plan_node_ids = init_plan_node_ids;
ExecParallelReportInstrumentation(queryDesc->planstate,
- instrumentation);
+ sei_context);
+ }
/* Must do this after capturing instrumentation. */
ExecutorEnd(queryDesc);
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 4566219..df5cfdd 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -1092,6 +1092,56 @@ ExecEvalParamExec(ExprState *exprstate, ExprContext *econtext,
return prm->value;
}
+/*
+ * ExecEvalParamExecParams
+ *
+ * Execute the subplan stored in PARAM_EXEC initplans params, if not executed
+ * till now.
+ *
+ * eval_params is the list of params that are already processed.
+ * init_plan_node_ids is the list of plan node ids that are processed.
+ */
+void
+ExecEvalParamExecParams(EState *estate, Bitmapset *params,
+ Bitmapset **eval_params,
+ Bitmapset **init_plan_node_ids)
+{
+ ParamExecData *prm;
+ int paramid;
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ if (bms_is_member(paramid, *eval_params))
+ continue;
+
+ /*
+ * PARAM_EXEC params (internal executor parameters) are stored in the
+ * ecxt_param_exec_vals array, and can be accessed by array index.
+ */
+ prm = &(estate->es_param_exec_vals[paramid]);
+
+ if (!prm->isinitplan)
+ continue;
+
+ if (prm->execPlan != NULL)
+ {
+ SubPlanState *node = (SubPlanState *) prm->execPlan;
+
+ /* Include plan node id which is about to get evaluated. */
+ *init_plan_node_ids = bms_add_member(*init_plan_node_ids,
+ node->planstate->plan->plan_node_id);
+
+ /* Parameter not evaluated yet, so go do it */
+ ExecSetParamPlan(prm->execPlan, GetPerTupleExprContext(estate));
+ /* ExecSetParamPlan should have processed this param... */
+ Assert(prm->execPlan == NULL);
+ }
+
+ *eval_params = bms_add_member(*eval_params, paramid);
+ }
+}
+
/* ----------------------------------------------------------------
* ExecEvalParamExtern
*
diff --git a/src/backend/executor/nodeNestloop.c b/src/backend/executor/nodeNestloop.c
index cac7ba1..11aeaaf 100644
--- a/src/backend/executor/nodeNestloop.c
+++ b/src/backend/executor/nodeNestloop.c
@@ -126,6 +126,7 @@ ExecNestLoop(NestLoopState *node)
{
NestLoopParam *nlp = (NestLoopParam *) lfirst(lc);
int paramno = nlp->paramno;
+ TupleDesc tdesc = outerTupleSlot->tts_tupleDescriptor;
ParamExecData *prm;
prm = &(econtext->ecxt_param_exec_vals[paramno]);
@@ -136,6 +137,7 @@ ExecNestLoop(NestLoopState *node)
prm->value = slot_getattr(outerTupleSlot,
nlp->paramval->varattno,
&(prm->isnull));
+ prm->ptype = tdesc->attrs[nlp->paramval->varattno - 1]->atttypid;
/* Flag parameter value as changed */
innerPlan->chgParam = bms_add_member(innerPlan->chgParam,
paramno);
diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c
index 8f419a1..5cbaa24 100644
--- a/src/backend/executor/nodeSubplan.c
+++ b/src/backend/executor/nodeSubplan.c
@@ -30,11 +30,15 @@
#include <math.h>
#include "access/htup_details.h"
+#include "catalog/pg_type.h"
#include "executor/executor.h"
#include "executor/nodeSubplan.h"
#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
+#include "storage/shmem.h"
#include "utils/array.h"
+#include "utils/datum.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
@@ -249,6 +253,7 @@ ExecScanSubPlan(SubPlanState *node,
ParamExecData *prm = &(estate->es_param_exec_vals[paramid]);
prm->execPlan = node;
+ prm->isinitplan = subplan->isInitPlan;
}
*isNull = true;
return (Datum) 0;
@@ -276,11 +281,13 @@ ExecScanSubPlan(SubPlanState *node,
forboth(l, subplan->parParam, pvar, node->args)
{
int paramid = lfirst_int(l);
+ ExprState *exprstate = (ExprState *) lfirst(pvar);
ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]);
- prm->value = ExecEvalExprSwitchContext((ExprState *) lfirst(pvar),
+ prm->value = ExecEvalExprSwitchContext(exprstate,
econtext,
&(prm->isnull));
+ prm->ptype = exprType((Node *) exprstate->expr);
planstate->chgParam = bms_add_member(planstate->chgParam, paramid);
}
@@ -393,6 +400,7 @@ ExecScanSubPlan(SubPlanState *node,
prmdata = &(econtext->ecxt_param_exec_vals[paramid]);
Assert(prmdata->execPlan == NULL);
prmdata->value = slot_getattr(slot, col, &(prmdata->isnull));
+ prmdata->ptype = tdesc->attrs[col - 1]->atttypid;
col++;
}
@@ -550,6 +558,7 @@ buildSubPlanHash(SubPlanState *node, ExprContext *econtext)
int col = 1;
ListCell *plst;
bool isnew;
+ TupleDesc tdesc = slot->tts_tupleDescriptor;
/*
* Load up the Params representing the raw sub-select outputs, then
@@ -564,6 +573,7 @@ buildSubPlanHash(SubPlanState *node, ExprContext *econtext)
Assert(prmdata->execPlan == NULL);
prmdata->value = slot_getattr(slot, col,
&(prmdata->isnull));
+ prmdata->ptype = tdesc->attrs[col - 1]->atttypid;
col++;
}
slot = ExecProject(node->projRight);
@@ -748,6 +758,7 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent)
ParamExecData *prm = &(estate->es_param_exec_vals[paramid]);
prm->execPlan = sstate;
+ prm->isinitplan = subplan->isInitPlan;
}
}
@@ -943,6 +954,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
ListCell *l;
bool found = false;
ArrayBuildStateAny *astate = NULL;
+ Oid ptype;
if (subLinkType == ANY_SUBLINK ||
subLinkType == ALL_SUBLINK)
@@ -950,6 +962,8 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
if (subLinkType == CTE_SUBLINK)
elog(ERROR, "CTE subplans should not be executed via ExecSetParamPlan");
+ ptype = exprType((Node *) node->xprstate.expr);
+
/* Initialize ArrayBuildStateAny in caller's context, if needed */
if (subLinkType == ARRAY_SUBLINK)
astate = initArrayResultAny(subplan->firstColType,
@@ -972,11 +986,13 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
forboth(l, subplan->parParam, pvar, node->args)
{
int paramid = lfirst_int(l);
+ ExprState *exprstate = (ExprState *) lfirst(pvar);
ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]);
- prm->value = ExecEvalExprSwitchContext((ExprState *) lfirst(pvar),
+ prm->value = ExecEvalExprSwitchContext(exprstate,
econtext,
&(prm->isnull));
+ prm->ptype = exprType((Node *) exprstate->expr);
planstate->chgParam = bms_add_member(planstate->chgParam, paramid);
}
@@ -999,6 +1015,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = BoolGetDatum(true);
+ prm->ptype = ptype;
prm->isnull = false;
found = true;
break;
@@ -1050,6 +1067,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = heap_getattr(node->curTuple, i, tdesc,
&(prm->isnull));
+ prm->ptype = tdesc->attrs[i - 1]->atttypid;
i++;
}
}
@@ -1072,6 +1090,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
true);
prm->execPlan = NULL;
prm->value = node->curArray;
+ prm->ptype = ptype;
prm->isnull = false;
}
else if (!found)
@@ -1084,6 +1103,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = BoolGetDatum(false);
+ prm->ptype = ptype;
prm->isnull = false;
}
else
@@ -1096,6 +1116,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = (Datum) 0;
+ prm->ptype = VOIDOID;
prm->isnull = true;
}
}
@@ -1141,7 +1162,10 @@ ExecReScanSetParamPlan(SubPlanState *node, PlanState *parent)
ParamExecData *prm = &(estate->es_param_exec_vals[paramid]);
if (subplan->subLinkType != CTE_SUBLINK)
+ {
prm->execPlan = node;
+ prm->isinitplan = subplan->isInitPlan;
+ }
parent->chgParam = bms_add_member(parent->chgParam, paramid);
}
@@ -1220,3 +1244,206 @@ ExecAlternativeSubPlan(AlternativeSubPlanState *node,
return ExecSubPlan(activesp, econtext, isNull);
}
+
+/*
+ * Estimate the amount of space required to serialize the InitPlan params.
+ */
+Size
+EstimateInitPlanParamsSpace(ParamExecData *paramExecVals, Bitmapset *params)
+{
+ int paramid;
+ Size sz = sizeof(int);
+ ParamExecData *prm;
+
+ if (params == NULL)
+ return sz;
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ Oid typeOid;
+ int16 typLen;
+ bool typByVal;
+
+ prm = &(paramExecVals[paramid]);
+ typeOid = prm->ptype;
+
+ sz = add_size(sz, sizeof(int)); /* space for paramid */
+ sz = add_size(sz, sizeof(Oid)); /* space for type OID */
+
+ /* space for datum/isnull */
+ if (OidIsValid(typeOid))
+ get_typlenbyval(typeOid, &typLen, &typByVal);
+ else
+ {
+ /* If no type OID, assume by-value, like copyParamList does. */
+ typLen = sizeof(Datum);
+ typByVal = true;
+ }
+ sz = add_size(sz,
+ datumEstimateSpace(prm->value, prm->isnull, typByVal, typLen));
+ }
+ return sz;
+}
+
+/*
+ * Serialize ParamExecData params corresponding to initplans.
+ *
+ * We write the number of parameters first, as a 4-byte integer, and then
+ * write details for each parameter in turn. The details for each parameter
+ * consist of a 4-byte paramid (location of param in execution time internal
+ * parameter array), 4-byte type OID, and then the datum as serialized by
+ * datumSerialize().
+ *
+ * The above format is quite similar to the format used to serialize
+ * paramListInfo structure, so if we change either format, then consider to
+ * change at both the places.
+ */
+void
+SerializeInitPlanParams(ParamExecData *paramExecVals, Bitmapset *params,
+ char **start_address)
+{
+ int nparams;
+ int paramid;
+ ParamExecData *prm;
+
+ if (params == NULL)
+ nparams = 0;
+ else
+ nparams = bms_num_members(params);
+ memcpy(*start_address, &nparams, sizeof(int));
+ *start_address += sizeof(int);
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ Oid typeOid;
+ int16 typLen;
+ bool typByVal;
+
+ prm = &(paramExecVals[paramid]);
+ typeOid = prm->ptype;
+
+ /* Write paramid. */
+ memcpy(*start_address, ¶mid, sizeof(int));
+ *start_address += sizeof(int);
+
+ /* Write OID. */
+ memcpy(*start_address, &typeOid, sizeof(Oid));
+ *start_address += sizeof(Oid);
+
+ /* space for datum/isnull */
+ if (OidIsValid(typeOid))
+ get_typlenbyval(typeOid, &typLen, &typByVal);
+ else
+ {
+ /* If no type OID, assume by-value, like copyParamList does. */
+ typLen = sizeof(Datum);
+ typByVal = true;
+ }
+ datumSerialize(prm->value, prm->isnull, typByVal, typLen,
+ start_address);
+ }
+}
+
+/*
+ * Restore ParamExecData params corresponding to initplans.
+ */
+void
+RestoreInitPlanParams(char **start_address, ParamExecData *params)
+{
+ int nparams;
+ int i;
+ int paramid;
+
+ memcpy(&nparams, *start_address, sizeof(int));
+ *start_address += sizeof(int);
+
+ for (i = 0; i < nparams; i++)
+ {
+ ParamExecData *prm;
+
+ /* Read paramid */
+ memcpy(¶mid, *start_address, sizeof(int));
+ *start_address += sizeof(int);
+ prm = ¶ms[paramid];
+
+ /* Read type OID. */
+ memcpy(&prm->ptype, *start_address, sizeof(Oid));
+ *start_address += sizeof(Oid);
+
+ /* Read datum/isnull. */
+ prm->value = datumRestore(start_address, &prm->isnull);
+ prm->execPlan = NULL;
+ }
+}
+
+/*
+ * Estimate the amount of space required to serialize the evaluated
+ * plan node ids corresponding to InitPlans.
+ */
+Size
+EstimateInitPlanNodeIdsSpace(Bitmapset *plan_node_ids)
+{
+ Size sz = sizeof(int);
+
+ if (!plan_node_ids)
+ return sz;
+
+ sz += bms_num_members(plan_node_ids) * sizeof(int);
+ return sz;
+}
+
+/*
+ * Serialize evaluated plan node ids corresponding to initplans.
+ *
+ * We write the number of nodes first, as a 4-byte integer, and then
+ * write each plan node id in turn.
+ */
+void
+SerializeInitPlanNodeIds(Bitmapset *plan_node_ids, char **start_address)
+{
+ int nnodes;
+ int plan_id;
+
+ if (!plan_node_ids)
+ nnodes = 0;
+ else
+ nnodes = bms_num_members(plan_node_ids);
+ memcpy(*start_address, &nnodes, sizeof(int));
+ *start_address += sizeof(int);
+
+ plan_id = -1;
+ while ((plan_id = bms_next_member(plan_node_ids, plan_id)) >= 0)
+ {
+ /* Write plan id. */
+ memcpy(*start_address, &plan_id, sizeof(int));
+ *start_address += sizeof(int);
+ }
+}
+
+/*
+ * Restore evaluated plan node ids corresponding to initplans.
+ */
+Bitmapset *
+RestoreInitPlanNodeIds(char **start_address)
+{
+ int nnodes;
+ int plan_id;
+ int i;
+
+ Bitmapset *plan_node_ids = NULL;
+
+ memcpy(&nnodes, *start_address, sizeof(int));
+ *start_address += sizeof(int);
+
+ for (i = 0; i < nnodes; i++)
+ {
+ /* Read plan id */
+ memcpy(&plan_id, *start_address, sizeof(int));
+ *start_address += sizeof(int);
+ plan_node_ids = bms_add_member(plan_node_ids, plan_id);
+ }
+
+ return plan_node_ids;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 9082c25..64970ce 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1497,6 +1497,7 @@ _copySubPlan(const SubPlan *from)
COPY_SCALAR_FIELD(useHashTable);
COPY_SCALAR_FIELD(unknownEqFalse);
COPY_SCALAR_FIELD(parallel_safe);
+ COPY_SCALAR_FIELD(isInitPlan);
COPY_NODE_FIELD(setParam);
COPY_NODE_FIELD(parParam);
COPY_NODE_FIELD(args);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 997c759..0426cc7 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -425,6 +425,7 @@ _equalSubPlan(const SubPlan *a, const SubPlan *b)
COMPARE_SCALAR_FIELD(useHashTable);
COMPARE_SCALAR_FIELD(unknownEqFalse);
COMPARE_SCALAR_FIELD(parallel_safe);
+ COMPARE_SCALAR_FIELD(isInitPlan);
COMPARE_NODE_FIELD(setParam);
COMPARE_NODE_FIELD(parParam);
COMPARE_NODE_FIELD(args);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 0b354f0..cbe7d4c 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1228,6 +1228,7 @@ _outSubPlan(StringInfo str, const SubPlan *node)
WRITE_BOOL_FIELD(useHashTable);
WRITE_BOOL_FIELD(unknownEqFalse);
WRITE_BOOL_FIELD(parallel_safe);
+ WRITE_BOOL_FIELD(isInitPlan);
WRITE_NODE_FIELD(setParam);
WRITE_NODE_FIELD(parParam);
WRITE_NODE_FIELD(args);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index a60c6c3..20ff8fb 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2235,6 +2235,7 @@ _readSubPlan(void)
READ_BOOL_FIELD(useHashTable);
READ_BOOL_FIELD(unknownEqFalse);
READ_BOOL_FIELD(parallel_safe);
+ READ_BOOL_FIELD(isInitPlan);
READ_NODE_FIELD(setParam);
READ_NODE_FIELD(parParam);
READ_NODE_FIELD(args);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 4b5902f..1d19101 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -343,6 +343,14 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
{
Gather *gather = makeNode(Gather);
+ /*
+ * If there are any initPlans attached to the formerly-top plan node,
+ * move them up to the Gather node; same as we do for Material node
+ * above.
+ */
+ gather->plan.initPlan = top_plan->initPlan;
+ top_plan->initPlan = NIL;
+
gather->plan.targetlist = top_plan->targetlist;
gather->plan.qual = NIL;
gather->plan.lefttree = top_plan;
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index b85abbc..85988bc 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -60,7 +60,7 @@ static Node *build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot,
Node *testexpr, bool adjust_testexpr,
bool unknownEqFalse, bool parallel_safe);
static List *generate_subquery_params(PlannerInfo *root, List *tlist,
- List **paramIds);
+ List **paramIds, bool parallel_safe);
static List *generate_subquery_vars(PlannerInfo *root, List *tlist,
Index varno);
static Node *convert_testexpr(PlannerInfo *root,
@@ -406,7 +406,7 @@ replace_outer_grouping(PlannerInfo *root, GroupingFunc *grp)
*/
static Param *
generate_new_param(PlannerInfo *root, Oid paramtype, int32 paramtypmod,
- Oid paramcollation)
+ Oid paramcollation, bool parallel_safe)
{
Param *retval;
@@ -417,7 +417,7 @@ generate_new_param(PlannerInfo *root, Oid paramtype, int32 paramtypmod,
retval->paramtypmod = paramtypmod;
retval->paramcollid = paramcollation;
retval->location = -1;
- retval->parallel_safe = false;
+ retval->parallel_safe = parallel_safe;
return retval;
}
@@ -714,13 +714,15 @@ build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot,
* null constant: the resjunk targetlist item containing the SubLink does
* not need to return anything useful, since the referencing Params are
* elsewhere.
+ *
+ * Params generated for parallel_safe plans are marked as parallel_safe.
*/
if (splan->parParam == NIL && subLinkType == EXISTS_SUBLINK)
{
Param *prm;
Assert(testexpr == NULL);
- prm = generate_new_param(root, BOOLOID, -1, InvalidOid);
+ prm = generate_new_param(root, BOOLOID, -1, InvalidOid, true);
splan->setParam = list_make1_int(prm->paramid);
isInitPlan = true;
result = (Node *) prm;
@@ -735,7 +737,8 @@ build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot,
prm = generate_new_param(root,
exprType((Node *) te->expr),
exprTypmod((Node *) te->expr),
- exprCollation((Node *) te->expr));
+ exprCollation((Node *) te->expr),
+ true);
splan->setParam = list_make1_int(prm->paramid);
isInitPlan = true;
result = (Node *) prm;
@@ -755,7 +758,8 @@ build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot,
prm = generate_new_param(root,
arraytype,
exprTypmod((Node *) te->expr),
- exprCollation((Node *) te->expr));
+ exprCollation((Node *) te->expr),
+ true);
splan->setParam = list_make1_int(prm->paramid);
isInitPlan = true;
result = (Node *) prm;
@@ -768,7 +772,8 @@ build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot,
Assert(testexpr != NULL);
params = generate_subquery_params(root,
plan->targetlist,
- &splan->paramIds);
+ &splan->paramIds,
+ parallel_safe);
result = convert_testexpr(root,
testexpr,
params);
@@ -791,7 +796,8 @@ build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot,
Assert(testexpr == NULL);
params = generate_subquery_params(root,
plan->targetlist,
- &splan->setParam);
+ &splan->setParam,
+ parallel_safe);
/*
* Save the list of replacement Params in the n'th cell of
@@ -828,7 +834,8 @@ build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot,
params = generate_subquery_params(root,
plan->targetlist,
- &splan->paramIds);
+ &splan->paramIds,
+ parallel_safe);
splan->testexpr = convert_testexpr(root,
testexpr,
params);
@@ -875,7 +882,12 @@ build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot,
splan->plan_id = list_length(root->glob->subplans);
if (isInitPlan)
+ {
root->init_plans = lappend(root->init_plans, splan);
+ splan->isInitPlan = true;
+ }
+ else
+ splan->isInitPlan = false;
/*
* A parameterless subplan (not initplan) should be prepared to handle
@@ -919,7 +931,8 @@ build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot,
* We also return an integer list of the paramids of the Params.
*/
static List *
-generate_subquery_params(PlannerInfo *root, List *tlist, List **paramIds)
+generate_subquery_params(PlannerInfo *root, List *tlist, List **paramIds,
+ bool parallel_safe)
{
List *result;
List *ids;
@@ -937,7 +950,8 @@ generate_subquery_params(PlannerInfo *root, List *tlist, List **paramIds)
param = generate_new_param(root,
exprType((Node *) tent->expr),
exprTypmod((Node *) tent->expr),
- exprCollation((Node *) tent->expr));
+ exprCollation((Node *) tent->expr),
+ parallel_safe);
result = lappend(result, param);
ids = lappend_int(ids, param->paramid);
}
@@ -1270,6 +1284,8 @@ SS_process_ctes(PlannerInfo *root)
root->glob->subroots = lappend(root->glob->subroots, subroot);
splan->plan_id = list_length(root->glob->subplans);
+ splan->isInitPlan = true;
+
root->init_plans = lappend(root->init_plans, splan);
root->cte_plan_ids = lappend_int(root->cte_plan_ids, splan->plan_id);
@@ -1866,10 +1882,17 @@ convert_EXISTS_to_ANY(PlannerInfo *root, Query *subselect,
Param *param;
cc = lnext(cc);
+
+ /*
+ * it is not clear whether generating parallel safe param has any
+ * benefit, so not incurring the cost to identify the parallel-safety
+ * of an argument seems advisable.
+ */
param = generate_new_param(root,
exprType(rightarg),
exprTypmod(rightarg),
- exprCollation(rightarg));
+ exprCollation(rightarg),
+ false);
tlist = lappend(tlist,
makeTargetEntry((Expr *) rightarg,
resno++,
@@ -2156,13 +2179,11 @@ SS_identify_outer_params(PlannerInfo *root)
}
/*
- * SS_charge_for_initplans - account for initplans in Path costs & parallelism
+ * SS_charge_for_initplans - account for initplans in Path costs
*
* If any initPlans have been created in the current query level, they will
* get attached to the Plan tree created from whichever Path we select from
- * the given rel. Increment all that rel's Paths' costs to account for them,
- * and make sure the paths get marked as parallel-unsafe, since we can't
- * currently transmit initPlans to parallel workers.
+ * the given rel. Increment all that rel's Paths' costs to account for them.
*
* This is separate from SS_attach_initplans because we might conditionally
* create more initPlans during create_plan(), depending on which Path we
@@ -2194,7 +2215,7 @@ SS_charge_for_initplans(PlannerInfo *root, RelOptInfo *final_rel)
}
/*
- * Now adjust the costs and parallel_safe flags.
+ * Now adjust the costs.
*/
foreach(lc, final_rel->pathlist)
{
@@ -2202,7 +2223,6 @@ SS_charge_for_initplans(PlannerInfo *root, RelOptInfo *final_rel)
path->startup_cost += initplan_cost;
path->total_cost += initplan_cost;
- path->parallel_safe = false;
}
/* We needn't do set_cheapest() here, caller will do it */
@@ -2898,7 +2918,7 @@ SS_make_initplan_output_param(PlannerInfo *root,
Oid resulttype, int32 resulttypmod,
Oid resultcollation)
{
- return generate_new_param(root, resulttype, resulttypmod, resultcollation);
+ return generate_new_param(root, resulttype, resulttypmod, resultcollation, false);
}
/*
@@ -2935,6 +2955,8 @@ SS_make_initplan_from_plan(PlannerInfo *root,
&node->firstColCollation);
node->setParam = list_make1_int(prm->paramid);
+ node->isInitPlan = true;
+
root->init_plans = lappend(root->init_plans, node);
/*
diff --git a/src/include/executor/execParallel.h b/src/include/executor/execParallel.h
index 8bc4270..9cc44bd 100644
--- a/src/include/executor/execParallel.h
+++ b/src/include/executor/execParallel.h
@@ -20,13 +20,14 @@
#include "utils/dsa.h"
typedef struct SharedExecutorInstrumentation SharedExecutorInstrumentation;
+typedef struct SharedExecutorInstrumentationContext SharedExecutorInstrumentationContext;
typedef struct ParallelExecutorInfo
{
PlanState *planstate;
ParallelContext *pcxt;
BufferUsage *buffer_usage;
- SharedExecutorInstrumentation *instrumentation;
+ SharedExecutorInstrumentationContext *sei_context;
shm_mq_handle **tqueue;
dsa_area *area;
bool finished;
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 02dbe7b..5acd5e4 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -243,6 +243,9 @@ extern bool ExecShutdownNode(PlanState *node);
/*
* prototypes from functions in execQual.c
*/
+extern void ExecEvalParamExecParams(EState *estate, Bitmapset *params,
+ Bitmapset **eval_params,
+ Bitmapset **init_plan_node_ids);
extern Datum GetAttributeByNum(HeapTupleHeader tuple, AttrNumber attrno,
bool *isNull);
extern Datum GetAttributeByName(HeapTupleHeader tuple, const char *attname,
diff --git a/src/include/executor/nodeSubplan.h b/src/include/executor/nodeSubplan.h
index 0f821dc..823acef 100644
--- a/src/include/executor/nodeSubplan.h
+++ b/src/include/executor/nodeSubplan.h
@@ -24,4 +24,16 @@ extern void ExecReScanSetParamPlan(SubPlanState *node, PlanState *parent);
extern void ExecSetParamPlan(SubPlanState *node, ExprContext *econtext);
+extern Size EstimateInitPlanParamsSpace(ParamExecData *paramExecVals, Bitmapset *params);
+
+extern void SerializeInitPlanParams(ParamExecData *paramExecVals, Bitmapset *params, char **start_address);
+
+extern void RestoreInitPlanParams(char **start_address, ParamExecData *params);
+
+extern Size EstimateInitPlanNodeIdsSpace(Bitmapset *plan_node_ids);
+
+extern void SerializeInitPlanNodeIds(Bitmapset *plan_node_ids, char **start_address);
+
+extern Bitmapset *RestoreInitPlanNodeIds(char **start_address);
+
#endif /* NODESUBPLAN_H */
diff --git a/src/include/nodes/params.h b/src/include/nodes/params.h
index e19ac24..68a0435 100644
--- a/src/include/nodes/params.h
+++ b/src/include/nodes/params.h
@@ -98,7 +98,19 @@ typedef struct ParamExecData
{
void *execPlan; /* should be "SubPlanState *" */
Datum value;
+
+ /*
+ * parameter's datatype, or 0. This is required so that datum value can
+ * be read and used for other purposes like passing it to worker backend
+ * via shared memory. This is required only for initPlan's evaluation,
+ * however for consistency we set this for Subplan as well. We left it
+ * for other cases like CTE or RecursiveUnion cases where this structure
+ * is not used for evaluation of subplans.
+ */
+ Oid ptype;
bool isnull;
+ /* indicates if the param points to initplan */
+ bool isinitplan;
} ParamExecData;
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 4ade0e5..87b559d 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -679,6 +679,7 @@ typedef struct SubPlan
* spec result is UNKNOWN; this allows much
* simpler handling of null values */
bool parallel_safe; /* OK to use as part of parallel plan? */
+ bool isInitPlan; /* TRUE if it's an InitPlan */
/* 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
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index c4235ae..f2fb4ca 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -544,6 +544,7 @@ ExecForeignInsert_function
ExecForeignUpdate_function
ExecParallelEstimateContext
ExecParallelInitializeDSMContext
+ExecParallelEvalInitPlan
ExecPhraseData
ExecRowMark
ExecScanAccessMtd
@@ -1458,6 +1459,7 @@ ParallelExecutorInfo
ParallelHeapScanDesc
ParallelSlot
ParallelState
+ParallelWorkerContext
ParallelWorkerInfo
Param
ParamExecData
@@ -1914,6 +1916,7 @@ SetupWorkerPtr
SharedDependencyObjectType
SharedDependencyType
SharedExecutorInstrumentation
+SharedExecutorInstrumentationContext
SharedInvalCatalogMsg
SharedInvalCatcacheMsg
SharedInvalRelcacheMsg
On Tue, Jan 31, 2017 at 4:16 PM, Amit Kapila <amit.kapila16@gmail.com> wrote:
On Wed, Dec 28, 2016 at 5:20 PM, Amit Kapila <amit.kapila16@gmail.com> wrote:
The drawback of the second approach is
that we need to evaluate the initplan before it is actually required
which means that we might evaluate it even when it is not required. I
am not sure if it is always safe to assume that we can evaluate the
initplan before pushing it to workers especially for the cases when it
is far enough down in the plan tree which we are parallelizing,I think we can always pull up un-correlated initplans at Gather node,
however, if there is a correlated initplan, then it is better not to
allow such initplans for being pushed below gather. Ex. of correlated
initplans:postgres=# explain (costs off) select * from t1 where t1.i in (select
t2.i from t2 where t1.k = (select max(k) from t3 where t3.i=t1.i));
QUERY PLAN
----------------------------------------------
Seq Scan on t1
Filter: (SubPlan 2)
SubPlan 2
-> Gather
Workers Planned: 1
Params Evaluated: $1
InitPlan 1 (returns $1)
-> Aggregate
-> Seq Scan on t3
Filter: (i = t1.i)
-> Result
One-Time Filter: (t1.k = $1)
-> Parallel Seq Scan on t2
(13 rows)It might be safe to allow above plan, but in general, such plans
should not be allowed, because it might not be feasible to compute
such initplan references at Gather node. I am still thinking on the
best way to deal with such initplans.
I could see two possibilities to determine whether the plan (for which
we are going to generate an initplan) contains a reference to a
correlated var param node. One is to write a plan or path walker to
determine any such reference and the second is to keep the information
about the correlated param in path node. I think the drawback of the
first approach is that traversing path tree during generation of
initplan can be costly, so for now I have kept the information in path
node to prohibit generating parallel initplans which contain a
reference to correlated vars. I think we can go with first approach of
using path walker if people feel that is better than maintaining a
reference in path. Attached patch
prohibit_parallel_correl_params_v1.patch implements the second
approach of keeping the correlated var param reference in path node
and pq_pushdown_initplan_v2.patch uses that to generate parallel
initplans.
Thoughts?
These patches build on top of parallel subplan patch [1]/messages/by-id/CAA4eK1KYQjQzQMpEz+QRA2fmim386gQLQBEf+p2Wmtqjh1rjwg@mail.gmail.com.
[1]: /messages/by-id/CAA4eK1KYQjQzQMpEz+QRA2fmim386gQLQBEf+p2Wmtqjh1rjwg@mail.gmail.com
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
prohibit_parallel_correl_params_v1.patchapplication/octet-stream; name=prohibit_parallel_correl_params_v1.patchDownload
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 2d49a65..0982434 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -427,6 +427,8 @@ sql_fn_make_param(SQLFunctionParseInfoPtr pinfo,
param->paramtypmod = -1;
param->paramcollid = get_typcollation(param->paramtype);
param->location = location;
+ param->parallel_safe = false;
+ param->is_correlated = false;
/*
* If we have a function input collation, allow it to override the
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 12324ab..5d57138 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1237,6 +1237,8 @@ _copyParam(const Param *from)
COPY_SCALAR_FIELD(paramtypmod);
COPY_SCALAR_FIELD(paramcollid);
COPY_LOCATION_FIELD(location);
+ COPY_SCALAR_FIELD(parallel_safe);
+ COPY_SCALAR_FIELD(is_correlated);
return newnode;
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 6d1dabe..46f7c5b 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -183,6 +183,8 @@ _equalParam(const Param *a, const Param *b)
COMPARE_SCALAR_FIELD(paramtypmod);
COMPARE_SCALAR_FIELD(paramcollid);
COMPARE_LOCATION_FIELD(location);
+ COMPARE_SCALAR_FIELD(parallel_safe);
+ COMPARE_SCALAR_FIELD(is_correlated);
return true;
}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index b3802b4..69e1919 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1016,6 +1016,8 @@ _outParam(StringInfo str, const Param *node)
WRITE_INT_FIELD(paramtypmod);
WRITE_OID_FIELD(paramcollid);
WRITE_LOCATION_FIELD(location);
+ WRITE_BOOL_FIELD(parallel_safe);
+ WRITE_BOOL_FIELD(is_correlated);
}
static void
@@ -1616,6 +1618,7 @@ _outPathInfo(StringInfo str, const Path *node)
outBitmapset(str, NULL);
WRITE_BOOL_FIELD(parallel_aware);
WRITE_BOOL_FIELD(parallel_safe);
+ WRITE_BOOL_FIELD(contain_correl_param);
WRITE_INT_FIELD(parallel_workers);
WRITE_FLOAT_FIELD(rows, "%.0f");
WRITE_FLOAT_FIELD(startup_cost, "%.2f");
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index d2f69fe..2184f9e 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -536,6 +536,8 @@ _readParam(void)
READ_INT_FIELD(paramtypmod);
READ_OID_FIELD(paramcollid);
READ_LOCATION_FIELD(location);
+ READ_BOOL_FIELD(parallel_safe);
+ READ_BOOL_FIELD(is_correlated);
READ_DONE();
}
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 5c18987..5d11751 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -501,6 +501,9 @@ static void
set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte)
{
+ bool contain_correl_param = false;
+ bool save_correl_param = false;
+
/*
* The flag has previously been initialized to false, so we can just
* return if it becomes clear that we can't safely set it.
@@ -541,9 +544,9 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
char proparallel = func_parallel(rte->tablesample->tsmhandler);
if (proparallel != PROPARALLEL_SAFE)
- return;
- if (!is_parallel_safe(root, (Node *) rte->tablesample->args))
- return;
+ goto parallel_unsafe;
+ if (!is_parallel_safe(root, (Node *) rte->tablesample->args, &contain_correl_param))
+ goto parallel_unsafe;
}
/*
@@ -558,9 +561,9 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
{
Assert(rel->fdwroutine);
if (!rel->fdwroutine->IsForeignScanParallelSafe)
- return;
+ goto parallel_unsafe;
if (!rel->fdwroutine->IsForeignScanParallelSafe(root, rel, rte))
- return;
+ goto parallel_unsafe;
}
/*
@@ -591,18 +594,18 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
case RTE_JOIN:
/* Shouldn't happen; we're only considering baserels here. */
Assert(false);
- return;
+ goto parallel_unsafe;
case RTE_FUNCTION:
/* Check for parallel-restricted functions. */
- if (!is_parallel_safe(root, (Node *) rte->functions))
- return;
+ if (!is_parallel_safe(root, (Node *) rte->functions, &contain_correl_param))
+ goto parallel_unsafe;
break;
case RTE_VALUES:
/* Check for parallel-restricted functions. */
- if (!is_parallel_safe(root, (Node *) rte->values_lists))
- return;
+ if (!is_parallel_safe(root, (Node *) rte->values_lists, &contain_correl_param))
+ goto parallel_unsafe;
break;
case RTE_CTE:
@@ -617,6 +620,8 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
return;
}
+ save_correl_param = contain_correl_param;
+
/*
* If there's anything in baserestrictinfo that's parallel-restricted, we
* give up on parallelizing access to this relation. We could consider
@@ -626,18 +631,23 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
* outer join clauses work correctly. It would likely break equivalence
* classes, too.
*/
- if (!is_parallel_safe(root, (Node *) rel->baserestrictinfo))
- return;
+ if (!is_parallel_safe(root, (Node *) rel->baserestrictinfo, &contain_correl_param))
+ goto parallel_unsafe;
+
+ save_correl_param = save_correl_param || contain_correl_param;
/*
* Likewise, if the relation's outputs are not parallel-safe, give up.
* (Usually, they're just Vars, but sometimes they're not.)
*/
- if (!is_parallel_safe(root, (Node *) rel->reltarget->exprs))
- return;
+ if (!is_parallel_safe(root, (Node *) rel->reltarget->exprs, &contain_correl_param))
+ goto parallel_unsafe;
/* We have a winner. */
rel->consider_parallel = true;
+
+parallel_unsafe:
+ rel->contain_correl_param = save_correl_param || contain_correl_param;
}
/*
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
index 3c58d05..ac797b3 100644
--- a/src/backend/optimizer/plan/planmain.c
+++ b/src/backend/optimizer/plan/planmain.c
@@ -76,8 +76,13 @@ query_planner(PlannerInfo *root, List *tlist,
* the query tlist will be dealt with later.)
*/
if (root->glob->parallelModeOK)
+ {
+ bool contain_correl_param = false;
+
final_rel->consider_parallel =
- is_parallel_safe(root, parse->jointree->quals);
+ is_parallel_safe(root, parse->jointree->quals, &contain_correl_param);
+ final_rel->contain_correl_param = contain_correl_param;
+ }
/* The only path for it is a trivial Result path */
add_path(final_rel, (Path *)
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 881742f..caa3a4e 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -1394,6 +1394,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
RelOptInfo *current_rel;
RelOptInfo *final_rel;
ListCell *lc;
+ bool offset_correl_param = false;
+ bool count_correl_param = false;
/* Tweak caller-supplied tuple_fraction if have LIMIT/OFFSET */
if (parse->limitCount || parse->limitOffset)
@@ -1814,7 +1816,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
* computed by partial paths.
*/
if (current_rel->partial_pathlist &&
- is_parallel_safe(root, (Node *) scanjoin_target->exprs))
+ is_parallel_safe(root, (Node *) scanjoin_target->exprs, NULL))
{
/* Apply the scan/join target to each partial path */
foreach(lc, current_rel->partial_pathlist)
@@ -1951,11 +1953,20 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
* query.
*/
if (current_rel->consider_parallel &&
- is_parallel_safe(root, parse->limitOffset) &&
- is_parallel_safe(root, parse->limitCount))
+ is_parallel_safe(root, parse->limitOffset, &offset_correl_param) &&
+ is_parallel_safe(root, parse->limitCount, &count_correl_param))
final_rel->consider_parallel = true;
/*
+ * mark final_rel to indicate that it contains correlated params if there
+ * is any reference to correlated param in input rel or limit clause.
+ */
+ if (current_rel->contain_correl_param ||
+ offset_correl_param ||
+ count_correl_param)
+ final_rel->contain_correl_param = true;
+
+ /*
* If the current_rel belongs to a single FDW, so does the final_rel.
*/
final_rel->serverid = current_rel->serverid;
@@ -3314,6 +3325,8 @@ create_grouping_paths(PlannerInfo *root,
bool can_hash;
bool can_sort;
bool try_parallel_aggregation;
+ bool target_correl_param = false;
+ bool having_correl_param = false;
ListCell *lc;
@@ -3326,11 +3339,21 @@ create_grouping_paths(PlannerInfo *root,
* target list and HAVING quals are parallel-safe.
*/
if (input_rel->consider_parallel &&
- is_parallel_safe(root, (Node *) target->exprs) &&
- is_parallel_safe(root, (Node *) parse->havingQual))
+ is_parallel_safe(root, (Node *) target->exprs, &target_correl_param) &&
+ is_parallel_safe(root, (Node *) parse->havingQual, &having_correl_param))
grouped_rel->consider_parallel = true;
/*
+ * mark grouped_rel to indicate that it contains correlated params if
+ * there is any reference to correlated param in input rel, target list or
+ * having clause.
+ */
+ if (input_rel->contain_correl_param ||
+ target_correl_param ||
+ having_correl_param)
+ grouped_rel->contain_correl_param = true;
+
+ /*
* If the input rel belongs to a single FDW, so does the grouped rel.
*/
grouped_rel->serverid = input_rel->serverid;
@@ -3871,6 +3894,8 @@ create_window_paths(PlannerInfo *root,
{
RelOptInfo *window_rel;
ListCell *lc;
+ bool target_correl_param = false;
+ bool activeWindows_correl_param = false;
/* For now, do all work in the (WINDOW, NULL) upperrel */
window_rel = fetch_upper_rel(root, UPPERREL_WINDOW, NULL);
@@ -3881,11 +3906,21 @@ create_window_paths(PlannerInfo *root,
* target list and active windows for non-parallel-safe constructs.
*/
if (input_rel->consider_parallel &&
- is_parallel_safe(root, (Node *) output_target->exprs) &&
- is_parallel_safe(root, (Node *) activeWindows))
+ is_parallel_safe(root, (Node *) output_target->exprs, &target_correl_param) &&
+ is_parallel_safe(root, (Node *) activeWindows, &activeWindows_correl_param))
window_rel->consider_parallel = true;
/*
+ * mark window_rel to indicate that it contains correlated params if there
+ * is any reference to correlated param in input rel, target list or
+ * activeWindows.
+ */
+ if (input_rel->contain_correl_param ||
+ target_correl_param ||
+ activeWindows_correl_param)
+ window_rel->contain_correl_param = true;
+
+ /*
* If the input rel belongs to a single FDW, so does the window rel.
*/
window_rel->serverid = input_rel->serverid;
@@ -4262,6 +4297,7 @@ create_ordered_paths(PlannerInfo *root,
Path *cheapest_input_path = input_rel->cheapest_total_path;
RelOptInfo *ordered_rel;
ListCell *lc;
+ bool target_correl_param = false;
/* For now, do all work in the (ORDERED, NULL) upperrel */
ordered_rel = fetch_upper_rel(root, UPPERREL_ORDERED, NULL);
@@ -4272,10 +4308,17 @@ create_ordered_paths(PlannerInfo *root,
* target list is parallel-safe.
*/
if (input_rel->consider_parallel &&
- is_parallel_safe(root, (Node *) target->exprs))
+ is_parallel_safe(root, (Node *) target->exprs, &target_correl_param))
ordered_rel->consider_parallel = true;
/*
+ * mark ordered_rel to indicate that it contains correlated params if
+ * there is any reference to correlated param in input rel or target list.
+ */
+ if (input_rel->contain_correl_param || target_correl_param)
+ ordered_rel->contain_correl_param = true;
+
+ /*
* If the input rel belongs to a single FDW, so does the ordered_rel.
*/
ordered_rel->serverid = input_rel->serverid;
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 4130bba..41743e4 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -160,6 +160,10 @@ replace_outer_var(PlannerInfo *root, Var *var)
retval->paramcollid = var->varcollid;
retval->location = var->location;
+ /* mark correlated params as parallel unsafe */
+ retval->parallel_safe = false;
+ retval->is_correlated = true;
+
return retval;
}
@@ -188,6 +192,10 @@ assign_nestloop_param_var(PlannerInfo *root, Var *var)
retval->paramcollid = var->varcollid;
retval->location = var->location;
+ /* mark correlated params as parallel unsafe */
+ retval->parallel_safe = false;
+ retval->is_correlated = true;
+
return retval;
}
@@ -265,6 +273,10 @@ replace_outer_placeholdervar(PlannerInfo *root, PlaceHolderVar *phv)
retval->paramcollid = exprCollation((Node *) phv->phexpr);
retval->location = -1;
+ /* mark correlated params as parallel unsafe */
+ retval->parallel_safe = false;
+ retval->is_correlated = true;
+
return retval;
}
@@ -292,6 +304,10 @@ assign_nestloop_param_placeholdervar(PlannerInfo *root, PlaceHolderVar *phv)
retval->paramcollid = exprCollation((Node *) phv->phexpr);
retval->location = -1;
+ /* mark correlated params as parallel unsafe */
+ retval->parallel_safe = false;
+ retval->is_correlated = true;
+
return retval;
}
@@ -334,6 +350,10 @@ replace_outer_agg(PlannerInfo *root, Aggref *agg)
retval->paramcollid = agg->aggcollid;
retval->location = agg->location;
+ /* mark correlated params as parallel unsafe */
+ retval->parallel_safe = false;
+ retval->is_correlated = true;
+
return retval;
}
@@ -376,6 +396,10 @@ replace_outer_grouping(PlannerInfo *root, GroupingFunc *grp)
retval->paramcollid = InvalidOid;
retval->location = grp->location;
+ /* mark correlated params as parallel unsafe */
+ retval->parallel_safe = false;
+ retval->is_correlated = true;
+
return retval;
}
@@ -399,6 +423,8 @@ generate_new_param(PlannerInfo *root, Oid paramtype, int32 paramtypmod,
retval->paramtypmod = paramtypmod;
retval->paramcollid = paramcollation;
retval->location = -1;
+ retval->parallel_safe = false;
+ retval->is_correlated = false;
return retval;
}
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index c7be99c..27b6f85 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -93,6 +93,8 @@ typedef struct
{
char max_hazard; /* worst proparallel hazard found so far */
char max_interesting; /* worst proparallel hazard of interest */
+ bool contain_correl_param; /* true, if expr contains param for
+ * correlated var */
} max_parallel_hazard_context;
static bool contain_agg_clause_walker(Node *node, void *context);
@@ -1056,6 +1058,7 @@ max_parallel_hazard(Query *parse)
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_UNSAFE;
+ context.contain_correl_param = false;
(void) max_parallel_hazard_walker((Node *) parse, &context);
return context.max_hazard;
}
@@ -1068,9 +1071,10 @@ max_parallel_hazard(Query *parse)
* result of max_parallel_hazard() on the whole query.
*/
bool
-is_parallel_safe(PlannerInfo *root, Node *node)
+is_parallel_safe(PlannerInfo *root, Node *node, bool *contain_correl_param)
{
max_parallel_hazard_context context;
+ bool parallel_hazard;
/*
* Even if the original querytree contained nothing unsafe, we need to
@@ -1084,7 +1088,11 @@ 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;
- return !max_parallel_hazard_walker(node, &context);
+ context.contain_correl_param = false;
+ parallel_hazard = max_parallel_hazard_walker(node, &context);
+ if (contain_correl_param)
+ *contain_correl_param = context.contain_correl_param;
+ return !parallel_hazard;
}
/* core logic for all parallel-hazard checks */
@@ -1186,13 +1194,13 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
return false;
}
- /*
- * We can't pass Params to workers at the moment either, so they are also
- * parallel-restricted.
- */
+ /* only parallel-safe params can be passed to workers */
else if (IsA(node, Param))
{
- if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
+ Param *prm = (Param *) node;
+
+ context->contain_correl_param = prm->is_correlated;
+ if (!prm->parallel_safe)
return true;
}
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index f440875..43afa75 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -952,6 +952,7 @@ create_seqscan_path(PlannerInfo *root, RelOptInfo *rel,
required_outer);
pathnode->parallel_aware = parallel_workers > 0 ? true : false;
pathnode->parallel_safe = rel->consider_parallel;
+ pathnode->contain_correl_param = rel->contain_correl_param;
pathnode->parallel_workers = parallel_workers;
pathnode->pathkeys = NIL; /* seqscan has unordered result */
@@ -976,6 +977,7 @@ create_samplescan_path(PlannerInfo *root, RelOptInfo *rel, Relids required_outer
required_outer);
pathnode->parallel_aware = false;
pathnode->parallel_safe = rel->consider_parallel;
+ pathnode->contain_correl_param = rel->contain_correl_param;
pathnode->parallel_workers = 0;
pathnode->pathkeys = NIL; /* samplescan has unordered result */
@@ -1033,6 +1035,7 @@ create_index_path(PlannerInfo *root,
required_outer);
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = rel->consider_parallel;
+ pathnode->path.contain_correl_param = rel->contain_correl_param;
pathnode->path.parallel_workers = 0;
pathnode->path.pathkeys = pathkeys;
@@ -1082,6 +1085,7 @@ create_bitmap_heap_path(PlannerInfo *root,
required_outer);
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = rel->consider_parallel;
+ pathnode->path.contain_correl_param = rel->contain_correl_param;
pathnode->path.parallel_workers = 0;
pathnode->path.pathkeys = NIL; /* always unordered */
@@ -1118,6 +1122,7 @@ create_bitmap_and_path(PlannerInfo *root,
*/
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = rel->consider_parallel;
+ pathnode->path.contain_correl_param = rel->contain_correl_param;
pathnode->path.parallel_workers = 0;
pathnode->path.pathkeys = NIL; /* always unordered */
@@ -1154,6 +1159,7 @@ create_bitmap_or_path(PlannerInfo *root,
*/
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = rel->consider_parallel;
+ pathnode->path.contain_correl_param = rel->contain_correl_param;
pathnode->path.parallel_workers = 0;
pathnode->path.pathkeys = NIL; /* always unordered */
@@ -1183,6 +1189,7 @@ create_tidscan_path(PlannerInfo *root, RelOptInfo *rel, List *tidquals,
required_outer);
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = rel->consider_parallel;
+ pathnode->path.contain_correl_param = rel->contain_correl_param;
pathnode->path.parallel_workers = 0;
pathnode->path.pathkeys = NIL; /* always unordered */
@@ -1215,6 +1222,7 @@ create_append_path(RelOptInfo *rel, List *subpaths, Relids required_outer,
required_outer);
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = rel->consider_parallel;
+ pathnode->path.contain_correl_param = rel->contain_correl_param;
pathnode->path.parallel_workers = parallel_workers;
pathnode->path.pathkeys = NIL; /* result is always considered
* unsorted */
@@ -1274,6 +1282,7 @@ create_merge_append_path(PlannerInfo *root,
required_outer);
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = rel->consider_parallel;
+ pathnode->path.contain_correl_param = rel->contain_correl_param;
pathnode->path.parallel_workers = 0;
pathnode->path.pathkeys = pathkeys;
pathnode->subpaths = subpaths;
@@ -1357,6 +1366,7 @@ create_result_path(PlannerInfo *root, RelOptInfo *rel,
pathnode->path.param_info = NULL; /* there are no other rels... */
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = rel->consider_parallel;
+ pathnode->path.contain_correl_param = rel->contain_correl_param;
pathnode->path.parallel_workers = 0;
pathnode->path.pathkeys = NIL;
pathnode->quals = resconstantqual;
@@ -1398,6 +1408,8 @@ create_material_path(RelOptInfo *rel, Path *subpath)
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = rel->consider_parallel &&
subpath->parallel_safe;
+ pathnode->path.contain_correl_param = rel->contain_correl_param ||
+ subpath->contain_correl_param;
pathnode->path.parallel_workers = subpath->parallel_workers;
pathnode->path.pathkeys = subpath->pathkeys;
@@ -1463,6 +1475,8 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = rel->consider_parallel &&
subpath->parallel_safe;
+ pathnode->path.contain_correl_param = rel->contain_correl_param ||
+ subpath->contain_correl_param;
pathnode->path.parallel_workers = subpath->parallel_workers;
/*
@@ -1681,6 +1695,7 @@ create_gather_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
required_outer);
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = false;
+ pathnode->path.contain_correl_param = subpath->contain_correl_param;
pathnode->path.parallel_workers = subpath->parallel_workers;
pathnode->path.pathkeys = NIL; /* Gather has unordered result */
@@ -1718,6 +1733,8 @@ create_subqueryscan_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = rel->consider_parallel &&
subpath->parallel_safe;
+ pathnode->path.contain_correl_param = rel->contain_correl_param ||
+ subpath->contain_correl_param;
pathnode->path.parallel_workers = subpath->parallel_workers;
pathnode->path.pathkeys = pathkeys;
pathnode->subpath = subpath;
@@ -1745,6 +1762,7 @@ create_functionscan_path(PlannerInfo *root, RelOptInfo *rel,
required_outer);
pathnode->parallel_aware = false;
pathnode->parallel_safe = rel->consider_parallel;
+ pathnode->contain_correl_param = rel->contain_correl_param;
pathnode->parallel_workers = 0;
pathnode->pathkeys = pathkeys;
@@ -1771,6 +1789,7 @@ create_valuesscan_path(PlannerInfo *root, RelOptInfo *rel,
required_outer);
pathnode->parallel_aware = false;
pathnode->parallel_safe = rel->consider_parallel;
+ pathnode->contain_correl_param = rel->contain_correl_param;
pathnode->parallel_workers = 0;
pathnode->pathkeys = NIL; /* result is always unordered */
@@ -1796,6 +1815,7 @@ create_ctescan_path(PlannerInfo *root, RelOptInfo *rel, Relids required_outer)
required_outer);
pathnode->parallel_aware = false;
pathnode->parallel_safe = rel->consider_parallel;
+ pathnode->contain_correl_param = rel->contain_correl_param;
pathnode->parallel_workers = 0;
pathnode->pathkeys = NIL; /* XXX for now, result is always unordered */
@@ -1822,6 +1842,7 @@ create_worktablescan_path(PlannerInfo *root, RelOptInfo *rel,
required_outer);
pathnode->parallel_aware = false;
pathnode->parallel_safe = rel->consider_parallel;
+ pathnode->contain_correl_param = rel->contain_correl_param;
pathnode->parallel_workers = 0;
pathnode->pathkeys = NIL; /* result is always unordered */
@@ -1861,6 +1882,7 @@ create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel,
required_outer);
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = rel->consider_parallel;
+ pathnode->path.contain_correl_param = rel->contain_correl_param;
pathnode->path.parallel_workers = 0;
pathnode->path.rows = rows;
pathnode->path.startup_cost = startup_cost;
@@ -2001,6 +2023,8 @@ create_nestloop_path(PlannerInfo *root,
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = joinrel->consider_parallel &&
outer_path->parallel_safe && inner_path->parallel_safe;
+ pathnode->path.contain_correl_param = joinrel->contain_correl_param ||
+ outer_path->contain_correl_param || inner_path->contain_correl_param;
/* This is a foolish way to estimate parallel_workers, but for now... */
pathnode->path.parallel_workers = outer_path->parallel_workers;
pathnode->path.pathkeys = pathkeys;
@@ -2064,6 +2088,8 @@ create_mergejoin_path(PlannerInfo *root,
pathnode->jpath.path.parallel_aware = false;
pathnode->jpath.path.parallel_safe = joinrel->consider_parallel &&
outer_path->parallel_safe && inner_path->parallel_safe;
+ pathnode->jpath.path.contain_correl_param = joinrel->contain_correl_param ||
+ outer_path->contain_correl_param || inner_path->contain_correl_param;
/* This is a foolish way to estimate parallel_workers, but for now... */
pathnode->jpath.path.parallel_workers = outer_path->parallel_workers;
pathnode->jpath.path.pathkeys = pathkeys;
@@ -2126,6 +2152,8 @@ create_hashjoin_path(PlannerInfo *root,
pathnode->jpath.path.parallel_aware = false;
pathnode->jpath.path.parallel_safe = joinrel->consider_parallel &&
outer_path->parallel_safe && inner_path->parallel_safe;
+ pathnode->jpath.path.contain_correl_param = joinrel->contain_correl_param ||
+ outer_path->contain_correl_param || inner_path->contain_correl_param;
/* This is a foolish way to estimate parallel_workers, but for now... */
pathnode->jpath.path.parallel_workers = outer_path->parallel_workers;
@@ -2169,6 +2197,7 @@ create_projection_path(PlannerInfo *root,
{
ProjectionPath *pathnode = makeNode(ProjectionPath);
PathTarget *oldtarget = subpath->pathtarget;
+ bool contain_correl_param = false;
pathnode->path.pathtype = T_Result;
pathnode->path.parent = rel;
@@ -2178,7 +2207,9 @@ create_projection_path(PlannerInfo *root,
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = rel->consider_parallel &&
subpath->parallel_safe &&
- is_parallel_safe(root, (Node *) target->exprs);
+ is_parallel_safe(root, (Node *) target->exprs, &contain_correl_param);
+ pathnode->path.contain_correl_param = rel->contain_correl_param ||
+ subpath->contain_correl_param || contain_correl_param;
pathnode->path.parallel_workers = subpath->parallel_workers;
/* Projection does not change the sort order */
pathnode->path.pathkeys = subpath->pathkeys;
@@ -2259,6 +2290,8 @@ apply_projection_to_path(PlannerInfo *root,
PathTarget *target)
{
QualCost oldcost;
+ bool contain_correl_param = false;
+ bool save_correl_param;
/*
* If given path can't project, we might need a Result node, so make a
@@ -2278,6 +2311,8 @@ apply_projection_to_path(PlannerInfo *root,
path->total_cost += target->cost.startup - oldcost.startup +
(target->cost.per_tuple - oldcost.per_tuple) * path->rows;
+ save_correl_param = path->contain_correl_param;
+
/*
* If the path happens to be a Gather path, we'd like to arrange for the
* subpath to return the required target list so that workers can help
@@ -2285,7 +2320,7 @@ apply_projection_to_path(PlannerInfo *root,
* target expressions, then we can't.
*/
if (IsA(path, GatherPath) &&
- is_parallel_safe(root, (Node *) target->exprs))
+ is_parallel_safe(root, (Node *) target->exprs, &contain_correl_param))
{
GatherPath *gpath = (GatherPath *) path;
@@ -2306,7 +2341,7 @@ apply_projection_to_path(PlannerInfo *root,
target);
}
else if (path->parallel_safe &&
- !is_parallel_safe(root, (Node *) target->exprs))
+ !is_parallel_safe(root, (Node *) target->exprs, &contain_correl_param))
{
/*
* We're inserting a parallel-restricted target list into a path
@@ -2316,6 +2351,8 @@ apply_projection_to_path(PlannerInfo *root,
path->parallel_safe = false;
}
+ path->contain_correl_param = save_correl_param || contain_correl_param;
+
return path;
}
@@ -2337,6 +2374,7 @@ create_set_projection_path(PlannerInfo *root,
ProjectSetPath *pathnode = makeNode(ProjectSetPath);
double tlist_rows;
ListCell *lc;
+ bool contain_correl_param = false;
pathnode->path.pathtype = T_ProjectSet;
pathnode->path.parent = rel;
@@ -2346,7 +2384,9 @@ create_set_projection_path(PlannerInfo *root,
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = rel->consider_parallel &&
subpath->parallel_safe &&
- is_parallel_safe(root, (Node *) target->exprs);
+ is_parallel_safe(root, (Node *) target->exprs, &contain_correl_param);
+ pathnode->path.contain_correl_param = rel->contain_correl_param ||
+ subpath->contain_correl_param || contain_correl_param;
pathnode->path.parallel_workers = subpath->parallel_workers;
/* Projection does not change the sort order XXX? */
pathnode->path.pathkeys = subpath->pathkeys;
@@ -2413,6 +2453,8 @@ create_sort_path(PlannerInfo *root,
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = rel->consider_parallel &&
subpath->parallel_safe;
+ pathnode->path.contain_correl_param = rel->contain_correl_param ||
+ subpath->contain_correl_param;
pathnode->path.parallel_workers = subpath->parallel_workers;
pathnode->path.pathkeys = pathkeys;
@@ -2458,6 +2500,8 @@ create_group_path(PlannerInfo *root,
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = rel->consider_parallel &&
subpath->parallel_safe;
+ pathnode->path.contain_correl_param = rel->contain_correl_param ||
+ subpath->contain_correl_param;
pathnode->path.parallel_workers = subpath->parallel_workers;
/* Group doesn't change sort ordering */
pathnode->path.pathkeys = subpath->pathkeys;
@@ -2515,6 +2559,8 @@ create_upper_unique_path(PlannerInfo *root,
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = rel->consider_parallel &&
subpath->parallel_safe;
+ pathnode->path.contain_correl_param = rel->contain_correl_param ||
+ subpath->contain_correl_param;
pathnode->path.parallel_workers = subpath->parallel_workers;
/* Unique doesn't change the input ordering */
pathnode->path.pathkeys = subpath->pathkeys;
@@ -2571,6 +2617,8 @@ create_agg_path(PlannerInfo *root,
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = rel->consider_parallel &&
subpath->parallel_safe;
+ pathnode->path.contain_correl_param = rel->contain_correl_param ||
+ subpath->contain_correl_param;
pathnode->path.parallel_workers = subpath->parallel_workers;
if (aggstrategy == AGG_SORTED)
pathnode->path.pathkeys = subpath->pathkeys; /* preserves order */
@@ -2637,6 +2685,8 @@ create_groupingsets_path(PlannerInfo *root,
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = rel->consider_parallel &&
subpath->parallel_safe;
+ pathnode->path.contain_correl_param = rel->contain_correl_param ||
+ subpath->contain_correl_param;
pathnode->path.parallel_workers = subpath->parallel_workers;
pathnode->subpath = subpath;
@@ -2752,6 +2802,7 @@ create_minmaxagg_path(PlannerInfo *root,
pathnode->path.parallel_aware = false;
/* A MinMaxAggPath implies use of subplans, so cannot be parallel-safe */
pathnode->path.parallel_safe = false;
+ pathnode->path.contain_correl_param = rel->contain_correl_param;
pathnode->path.parallel_workers = 0;
/* Result is one unordered row */
pathnode->path.rows = 1;
@@ -2810,6 +2861,8 @@ create_windowagg_path(PlannerInfo *root,
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = rel->consider_parallel &&
subpath->parallel_safe;
+ pathnode->path.contain_correl_param = rel->contain_correl_param ||
+ subpath->contain_correl_param;
pathnode->path.parallel_workers = subpath->parallel_workers;
/* WindowAgg preserves the input sort order */
pathnode->path.pathkeys = subpath->pathkeys;
@@ -2878,6 +2931,8 @@ create_setop_path(PlannerInfo *root,
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = rel->consider_parallel &&
subpath->parallel_safe;
+ pathnode->path.contain_correl_param = rel->contain_correl_param ||
+ subpath->contain_correl_param;
pathnode->path.parallel_workers = subpath->parallel_workers;
/* SetOp preserves the input sort order if in sort mode */
pathnode->path.pathkeys =
@@ -2937,6 +2992,8 @@ create_recursiveunion_path(PlannerInfo *root,
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = rel->consider_parallel &&
leftpath->parallel_safe && rightpath->parallel_safe;
+ pathnode->path.contain_correl_param = rel->contain_correl_param ||
+ leftpath->contain_correl_param || rightpath->contain_correl_param;
/* Foolish, but we'll do it like joins for now: */
pathnode->path.parallel_workers = leftpath->parallel_workers;
/* RecursiveUnion result is always unsorted */
@@ -2976,6 +3033,7 @@ create_lockrows_path(PlannerInfo *root, RelOptInfo *rel,
pathnode->path.param_info = NULL;
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = false;
+ pathnode->path.contain_correl_param = false;
pathnode->path.parallel_workers = 0;
pathnode->path.rows = subpath->rows;
@@ -3047,6 +3105,7 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
pathnode->path.param_info = NULL;
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = false;
+ pathnode->path.contain_correl_param = false;
pathnode->path.parallel_workers = 0;
pathnode->path.pathkeys = NIL;
@@ -3134,6 +3193,8 @@ create_limit_path(PlannerInfo *root, RelOptInfo *rel,
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = rel->consider_parallel &&
subpath->parallel_safe;
+ pathnode->path.contain_correl_param = rel->contain_correl_param ||
+ subpath->contain_correl_param;
pathnode->path.parallel_workers = subpath->parallel_workers;
pathnode->path.rows = subpath->rows;
pathnode->path.startup_cost = subpath->startup_cost;
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index adc1db9..f5548ed 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -352,6 +352,8 @@ build_join_rel(PlannerInfo *root,
{
RelOptInfo *joinrel;
List *restrictlist;
+ bool restrict_correl_param = false;
+ bool target_correl_param = false;
/*
* See if we already have a joinrel for this set of base rels.
@@ -527,11 +529,22 @@ build_join_rel(PlannerInfo *root,
* here.
*/
if (inner_rel->consider_parallel && outer_rel->consider_parallel &&
- is_parallel_safe(root, (Node *) restrictlist) &&
- is_parallel_safe(root, (Node *) joinrel->reltarget->exprs))
+ is_parallel_safe(root, (Node *) restrictlist, &restrict_correl_param) &&
+ is_parallel_safe(root, (Node *) joinrel->reltarget->exprs, &target_correl_param))
joinrel->consider_parallel = true;
/*
+ * mark join_rel to indicate that it contains correlated params if there
+ * is any reference to correlated param in inner rel or outer rel or quals
+ * or targetlist.
+ */
+ if (inner_rel->contain_correl_param ||
+ outer_rel->contain_correl_param ||
+ restrict_correl_param ||
+ target_correl_param)
+ joinrel->contain_correl_param = true;
+
+ /*
* Add the joinrel to the query's joinrel list, and store it into the
* auxiliary hashtable if there is one. NB: GEQO requires us to append
* the new joinrel to the end of the list!
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index efe1c37..a55721a 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -2051,5 +2051,7 @@ make_agg_arg(Oid argtype, Oid argcollation)
argp->paramtypmod = -1;
argp->paramcollid = argcollation;
argp->location = -1;
+ argp->parallel_safe = false;
+ argp->is_correlated = false;
return (Node *) argp;
}
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 4b73272..15f3303 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1590,6 +1590,8 @@ transformMultiAssignRef(ParseState *pstate, MultiAssignRef *maref)
param->paramtypmod = exprTypmod((Node *) tle->expr);
param->paramcollid = exprCollation((Node *) tle->expr);
param->location = exprLocation((Node *) tle->expr);
+ param->parallel_safe = false;
+ param->is_correlated = false;
return (Node *) param;
}
@@ -1947,6 +1949,8 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
param->paramtypmod = exprTypmod((Node *) tent->expr);
param->paramcollid = exprCollation((Node *) tent->expr);
param->location = -1;
+ param->parallel_safe = false;
+ param->is_correlated = false;
right_list = lappend(right_list, param);
}
diff --git a/src/backend/parser/parse_param.c b/src/backend/parser/parse_param.c
index 2575e02..b247bf8 100644
--- a/src/backend/parser/parse_param.c
+++ b/src/backend/parser/parse_param.c
@@ -117,6 +117,8 @@ fixed_paramref_hook(ParseState *pstate, ParamRef *pref)
param->paramtypmod = -1;
param->paramcollid = get_typcollation(param->paramtype);
param->location = pref->location;
+ param->parallel_safe = false;
+ param->is_correlated = false;
return (Node *) param;
}
@@ -170,6 +172,8 @@ variable_paramref_hook(ParseState *pstate, ParamRef *pref)
param->paramtypmod = -1;
param->paramcollid = get_typcollation(param->paramtype);
param->location = pref->location;
+ param->parallel_safe = false;
+ param->is_correlated = false;
return (Node *) param;
}
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 235bc75..16860e9 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -225,6 +225,8 @@ typedef struct Param
int32 paramtypmod; /* typmod value, if known */
Oid paramcollid; /* OID of collation, or InvalidOid if none */
int location; /* token location, or -1 if unknown */
+ bool parallel_safe; /* OK to use as part of parallel plan? */
+ bool is_correlated; /* is param a correlated var? */
} Param;
/*
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 643be54..fa94c92 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -496,6 +496,8 @@ typedef struct RelOptInfo
bool consider_startup; /* keep cheap-startup-cost paths? */
bool consider_param_startup; /* ditto, for parameterized paths? */
bool consider_parallel; /* consider parallel paths? */
+ bool contain_correl_param; /* contains reference to correlated
+ * param? */
/* default result targetlist for Paths scanning this relation */
struct PathTarget *reltarget; /* list of Vars/Exprs, cost, width */
@@ -897,6 +899,8 @@ typedef struct Path
bool parallel_aware; /* engage parallel-aware logic? */
bool parallel_safe; /* OK to use as part of parallel plan? */
+ bool contain_correl_param; /* contain reference to correlated
+ * param */
int parallel_workers; /* desired # of workers; 0 = not
* parallel */
@@ -1313,7 +1317,7 @@ typedef struct ProjectSetPath
{
Path path;
Path *subpath; /* path representing input source */
-} ProjectSetPath;
+} ProjectSetPath;
/*
* SortPath represents an explicit sort step
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index cc0d7b0..0b6ace5 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -61,7 +61,7 @@ extern bool contain_mutable_functions(Node *clause);
extern bool contain_volatile_functions(Node *clause);
extern bool contain_volatile_functions_not_nextval(Node *clause);
extern char max_parallel_hazard(Query *parse);
-extern bool is_parallel_safe(PlannerInfo *root, Node *node);
+extern bool is_parallel_safe(PlannerInfo *root, Node *node, bool *contain_correl_param);
extern bool contain_nonstrict_functions(Node *clause);
extern bool contain_leaked_vars(Node *clause);
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index b25b3f1..8111b3c 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -1328,6 +1328,7 @@ make_datum_param(PLpgSQL_expr *expr, int dno, int location)
¶m->paramtypmod,
¶m->paramcollid);
param->location = location;
+ param->parallel_safe = false;
return (Node *) param;
}
pq_pushdown_initplan_v2.patchapplication/octet-stream; name=pq_pushdown_initplan_v2.patchDownload
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index c9e0a3e..272e61b 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -106,6 +106,7 @@ static void show_tidbitmap_info(BitmapHeapScanState *planstate,
static void show_instrumentation_count(const char *qlabel, int which,
PlanState *planstate, ExplainState *es);
static void show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es);
+static void show_gather_eval_params(PlanState *plastate, ExplainState *es);
static const char *explain_get_index_name(Oid indexId);
static void show_buffer_usage(ExplainState *es, const BufferUsage *usage);
static void ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir,
@@ -619,7 +620,17 @@ ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc)
*/
ps = queryDesc->planstate;
if (IsA(ps, GatherState) &&((Gather *) ps->plan)->invisible)
+ {
+ List *initPlanState = NULL;
+ PlanState *save_ps;
+
+ /* initplans are always attached to top node (cf standard_planner) */
+ save_ps = ps;
+ initPlanState = ps->initPlan;
ps = outerPlanState(ps);
+ ps->initPlan = initPlanState;
+ save_ps->initPlan = NIL;
+ }
ExplainNode(ps, NIL, NULL, NULL, es);
}
@@ -1382,6 +1393,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
planstate, es);
ExplainPropertyInteger("Workers Planned",
gather->num_workers, es);
+
+ /* Show params evaluated at gather node */
+ if (planstate->plan->allParam)
+ show_gather_eval_params(planstate, es);
+
if (es->analyze)
{
int nworkers;
@@ -2331,6 +2347,34 @@ show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es)
}
/*
+ * Show initplan params evaluated at gather node.
+ */
+static void
+show_gather_eval_params(PlanState *planstate, ExplainState *es)
+{
+ ParamExecData *prm;
+ int paramid = -1;
+ Bitmapset *bms_params = planstate->plan->allParam;
+ List *params = NIL;
+ EState *estate = planstate->state;
+
+ while ((paramid = bms_next_member(bms_params, paramid)) >= 0)
+ {
+ char param[32];
+
+ prm = &(estate->es_param_exec_vals[paramid]);
+ if (!prm->isinitplan)
+ continue;
+
+ snprintf(param, sizeof(param), "$%d", paramid);
+ params = lappend(params, pstrdup(param));
+ }
+
+ if (params)
+ ExplainPropertyList("Params Evaluated", params, es);
+}
+
+/*
* Fetch the name of an index in an EXPLAIN
*
* We allow plugins to get control here so that plans involving hypothetical
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index 784dbaf..c77a84d 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -28,6 +28,7 @@
#include "executor/nodeCustom.h"
#include "executor/nodeForeignscan.h"
#include "executor/nodeSeqscan.h"
+#include "executor/nodeSubplan.h"
#include "executor/tqueue.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/planmain.h"
@@ -49,6 +50,8 @@
#define PARALLEL_KEY_TUPLE_QUEUE UINT64CONST(0xE000000000000004)
#define PARALLEL_KEY_INSTRUMENTATION UINT64CONST(0xE000000000000005)
#define PARALLEL_KEY_DSA UINT64CONST(0xE000000000000006)
+#define PARALLEL_KEY_INITPLAN_PARAMS UINT64CONST(0xE000000000000007)
+#define PARALLEL_KEY_INITPLAN_IDS UINT64CONST(0xE000000000000008)
#define PARALLEL_TUPLE_QUEUE_SIZE 65536
@@ -77,6 +80,14 @@ struct SharedExecutorInstrumentation
int plan_node_id[FLEXIBLE_ARRAY_MEMBER];
/* array of num_plan_nodes * num_workers Instrumentation objects follows */
};
+
+/* Context object for SharedExecutorInstrumentation. */
+struct SharedExecutorInstrumentationContext
+{
+ SharedExecutorInstrumentation *instrumentation;
+ Bitmapset *init_plan_node_ids;
+};
+
#define GetInstrumentationArray(sei) \
(AssertVariableIsOfTypeMacro(sei, SharedExecutorInstrumentation *), \
(Instrumentation *) (((char *) sei) + sei->instrument_offset))
@@ -85,6 +96,7 @@ struct SharedExecutorInstrumentation
typedef struct ExecParallelEstimateContext
{
ParallelContext *pcxt;
+ Bitmapset *init_plan_node_ids;
int nnodes;
} ExecParallelEstimateContext;
@@ -93,9 +105,24 @@ typedef struct ExecParallelInitializeDSMContext
{
ParallelContext *pcxt;
SharedExecutorInstrumentation *instrumentation;
+ Bitmapset *init_plan_node_ids;
int nnodes;
} ExecParallelInitializeDSMContext;
+/* Context object for ExecEvalInitPlans. */
+typedef struct ExecParallelEvalInitPlan
+{
+ Bitmapset *params;
+ Bitmapset *init_plan_node_ids;
+} ExecParallelEvalInitPlan;
+
+/* Context object for ExecParallelInitializeWorker. */
+typedef struct ParallelWorkerContext
+{
+ Bitmapset *init_plan_node_ids;
+ shm_toc *toc;
+} ParallelWorkerContext;
+
/* Helper functions that run in the parallel leader. */
static char *ExecSerializePlan(Plan *plan, EState *estate);
static bool ExecParallelEstimate(PlanState *node,
@@ -105,7 +132,7 @@ static bool ExecParallelInitializeDSM(PlanState *node,
static shm_mq_handle **ExecParallelSetupTupleQueues(ParallelContext *pcxt,
bool reinitialize);
static bool ExecParallelRetrieveInstrumentation(PlanState *planstate,
- SharedExecutorInstrumentation *instrumentation);
+ SharedExecutorInstrumentationContext *sei_context);
/* Helper functions that run in the parallel worker. */
static void ParallelQueryMain(dsm_segment *seg, shm_toc *toc);
@@ -185,6 +212,11 @@ ExecParallelEstimate(PlanState *planstate, ExecParallelEstimateContext *e)
if (planstate == NULL)
return false;
+ /* Skip nodes that are already evaluated. */
+ if (!bms_is_empty(e->init_plan_node_ids) &&
+ bms_is_member(planstate->plan->plan_node_id, e->init_plan_node_ids))
+ return false;
+
/* Count this node. */
e->nnodes++;
@@ -224,6 +256,11 @@ ExecParallelInitializeDSM(PlanState *planstate,
if (planstate == NULL)
return false;
+ /* Skip nodes that are already evaluated. */
+ if (!bms_is_empty(d->init_plan_node_ids) &&
+ bms_is_member(planstate->plan->plan_node_id, d->init_plan_node_ids))
+ return false;
+
/* If instrumentation is enabled, initialize slot for this node. */
if (d->instrumentation != NULL)
d->instrumentation->plan_node_id[d->nnodes] =
@@ -266,6 +303,36 @@ ExecParallelInitializeDSM(PlanState *planstate,
}
/*
+ * Execute the iniPlans, if not done already. This is different from the way
+ * initPlans are evaluated (lazy evaluation) at other places as instead of
+ * sharing the initPlan to all the workers and let them execute, we pass the
+ * values which can be directly used by worker backends. We do remember the
+ * initPlans that are already processed at the upper level of the tree. This
+ * helps in tracking them uniquely across the plan tree sent for execution to
+ * workers.
+ */
+static bool
+ExecEvalInitPlans(PlanState *planstate,
+ ExecParallelEvalInitPlan *eip)
+{
+ if (planstate->plan->allParam)
+ {
+ EState *estate = planstate->state;
+
+ /*
+ * Evaluate the setParam of initPlan's which are included only in
+ * plans' allParams.
+ */
+ ExecEvalParamExecParams(estate,
+ planstate->plan->allParam,
+ &(eip->params),
+ &(eip->init_plan_node_ids));
+ }
+
+ return planstate_tree_walker(planstate, ExecEvalInitPlans, eip);
+}
+
+/*
* It sets up the response queues for backend workers to return tuples
* to the main backend and start the workers.
*/
@@ -340,13 +407,18 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers)
ParallelContext *pcxt;
ExecParallelEstimateContext e;
ExecParallelInitializeDSMContext d;
+ ExecParallelEvalInitPlan eip;
char *pstmt_data;
char *pstmt_space;
char *param_space;
+ char *initplan_param_space;
+ char *initplan_nodes_space;
BufferUsage *bufusage_space;
SharedExecutorInstrumentation *instrumentation = NULL;
int pstmt_len;
int param_len;
+ int initplan_param_len;
+ int initplan_nodes_len;
int instrumentation_len = 0;
int instrument_offset = 0;
Size dsa_minsize = dsa_minimum_size();
@@ -355,6 +427,9 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers)
pei = palloc0(sizeof(ParallelExecutorInfo));
pei->finished = false;
pei->planstate = planstate;
+ eip.params = NULL;
+ eip.init_plan_node_ids = NULL;
+ ExecEvalInitPlans(planstate, &eip);
/* Fix up and serialize plan to be sent to workers. */
pstmt_data = ExecSerializePlan(planstate->plan, estate);
@@ -379,6 +454,16 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers)
shm_toc_estimate_chunk(&pcxt->estimator, param_len);
shm_toc_estimate_keys(&pcxt->estimator, 1);
+ /* Estimate space for initplan params. */
+ initplan_param_len = EstimateInitPlanParamsSpace(estate->es_param_exec_vals, eip.params);
+ shm_toc_estimate_chunk(&pcxt->estimator, initplan_param_len);
+ shm_toc_estimate_keys(&pcxt->estimator, 1);
+
+ /* Estimate space for evaluated initplan plan node ids. */
+ initplan_nodes_len = EstimateInitPlanNodeIdsSpace(eip.init_plan_node_ids);
+ shm_toc_estimate_chunk(&pcxt->estimator, initplan_nodes_len);
+ shm_toc_estimate_keys(&pcxt->estimator, 1);
+
/*
* Estimate space for BufferUsage.
*
@@ -401,6 +486,7 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers)
*/
e.pcxt = pcxt;
e.nnodes = 0;
+ e.init_plan_node_ids = eip.init_plan_node_ids;
ExecParallelEstimate(planstate, &e);
/* Estimate space for instrumentation, if required. */
@@ -443,6 +529,16 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers)
shm_toc_insert(pcxt->toc, PARALLEL_KEY_PARAMS, param_space);
SerializeParamList(estate->es_param_list_info, ¶m_space);
+ /* Store serialized initplan params. */
+ initplan_param_space = shm_toc_allocate(pcxt->toc, initplan_param_len);
+ shm_toc_insert(pcxt->toc, PARALLEL_KEY_INITPLAN_PARAMS, initplan_param_space);
+ SerializeInitPlanParams(estate->es_param_exec_vals, eip.params, &initplan_param_space);
+
+ /* Store serialized evaluated initplan plan node ids. */
+ initplan_nodes_space = shm_toc_allocate(pcxt->toc, initplan_nodes_len);
+ shm_toc_insert(pcxt->toc, PARALLEL_KEY_INITPLAN_IDS, initplan_nodes_space);
+ SerializeInitPlanNodeIds(eip.init_plan_node_ids, &initplan_nodes_space);
+
/* Allocate space for each worker's BufferUsage; no need to initialize. */
bufusage_space = shm_toc_allocate(pcxt->toc,
mul_size(sizeof(BufferUsage), pcxt->nworkers));
@@ -472,7 +568,9 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers)
InstrInit(&instrument[i], estate->es_instrument);
shm_toc_insert(pcxt->toc, PARALLEL_KEY_INSTRUMENTATION,
instrumentation);
- pei->instrumentation = instrumentation;
+ pei->sei_context = palloc0(sizeof(SharedExecutorInstrumentationContext));
+ pei->sei_context->instrumentation = instrumentation;
+ pei->sei_context->init_plan_node_ids = eip.init_plan_node_ids;
}
/*
@@ -504,6 +602,7 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers)
*/
d.pcxt = pcxt;
d.instrumentation = instrumentation;
+ d.init_plan_node_ids = eip.init_plan_node_ids;
d.nnodes = 0;
ExecParallelInitializeDSM(planstate, &d);
@@ -524,15 +623,24 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers)
*/
static bool
ExecParallelRetrieveInstrumentation(PlanState *planstate,
- SharedExecutorInstrumentation *instrumentation)
+ SharedExecutorInstrumentationContext *sei_context)
{
Instrumentation *instrument;
int i;
int n;
int ibytes;
int plan_node_id = planstate->plan->plan_node_id;
+ SharedExecutorInstrumentation *instrumentation;
MemoryContext oldcontext;
+ instrumentation = sei_context->instrumentation;
+
+ /* Skip nodes that are already evaluated. */
+ if (!bms_is_empty(sei_context->init_plan_node_ids) &&
+ bms_is_member(planstate->plan->plan_node_id, sei_context->init_plan_node_ids))
+ return false;
+
+
/* Find the instrumentation for this node. */
for (i = 0; i < instrumentation->num_plan_nodes; ++i)
if (instrumentation->plan_node_id[i] == plan_node_id)
@@ -563,7 +671,7 @@ ExecParallelRetrieveInstrumentation(PlanState *planstate,
memcpy(&planstate->worker_instrument->instrument, instrument, ibytes);
return planstate_tree_walker(planstate, ExecParallelRetrieveInstrumentation,
- instrumentation);
+ sei_context);
}
/*
@@ -586,9 +694,9 @@ ExecParallelFinish(ParallelExecutorInfo *pei)
InstrAccumParallelQuery(&pei->buffer_usage[i]);
/* Finally, accumulate instrumentation, if any. */
- if (pei->instrumentation)
+ if (pei->sei_context)
ExecParallelRetrieveInstrumentation(pei->planstate,
- pei->instrumentation);
+ pei->sei_context);
pei->finished = true;
}
@@ -633,6 +741,35 @@ ExecParallelGetReceiver(dsm_segment *seg, shm_toc *toc)
}
/*
+ * Copy the ParamExecData params corresponding to initplans from dynamic
+ * shared memory. This has to be done once the params are allocated by
+ * executor; that is after ExecutorStart().
+ */
+static void
+ExecParallelInitializeInitPlanParams(shm_toc *toc, ParamExecData *params)
+{
+ char *paramspace;
+
+ /* Reconstruct initplan params. */
+ paramspace = shm_toc_lookup(toc, PARALLEL_KEY_INITPLAN_PARAMS);
+ RestoreInitPlanParams(¶mspace, params);
+}
+
+/*
+ * Copy the evaluated plan node ids corresponding to initplans from dynamic
+ * shared memory.
+ */
+static void
+ExecParallelInitializeInitPlanNodes(shm_toc *toc, Bitmapset **plan_node_ids)
+{
+ char *plan_nodes_space;
+
+ /* Reconstruct evaluated initplan plan node ids. */
+ plan_nodes_space = shm_toc_lookup(toc, PARALLEL_KEY_INITPLAN_IDS);
+ *plan_node_ids = RestoreInitPlanNodeIds(&plan_nodes_space);
+}
+
+/*
* Create a QueryDesc for the PlannedStmt we are to execute, and return it.
*/
static QueryDesc *
@@ -672,11 +809,19 @@ ExecParallelGetQueryDesc(shm_toc *toc, DestReceiver *receiver,
*/
static bool
ExecParallelReportInstrumentation(PlanState *planstate,
- SharedExecutorInstrumentation *instrumentation)
+ SharedExecutorInstrumentationContext *sei_context)
{
int i;
int plan_node_id = planstate->plan->plan_node_id;
Instrumentation *instrument;
+ SharedExecutorInstrumentation *instrumentation;
+
+ instrumentation = sei_context->instrumentation;
+
+ /* Skip nodes that are already evaluated. */
+ if (!bms_is_empty(sei_context->init_plan_node_ids) &&
+ bms_is_member(planstate->plan->plan_node_id, sei_context->init_plan_node_ids))
+ return false;
InstrEndLoop(planstate->instrument);
@@ -703,7 +848,7 @@ ExecParallelReportInstrumentation(PlanState *planstate,
InstrAggNode(&instrument[ParallelWorkerNumber], planstate->instrument);
return planstate_tree_walker(planstate, ExecParallelReportInstrumentation,
- instrumentation);
+ sei_context);
}
/*
@@ -712,33 +857,38 @@ ExecParallelReportInstrumentation(PlanState *planstate,
* is allocated and initialized by executor; that is, after ExecutorStart().
*/
static bool
-ExecParallelInitializeWorker(PlanState *planstate, shm_toc *toc)
+ExecParallelInitializeWorker(PlanState *planstate, ParallelWorkerContext *pw_context)
{
if (planstate == NULL)
return false;
+ /* Skip nodes that are already evaluated. */
+ if (!bms_is_empty(pw_context->init_plan_node_ids) &&
+ bms_is_member(planstate->plan->plan_node_id, pw_context->init_plan_node_ids))
+ return false;
+
/* Call initializers for parallel-aware plan nodes. */
if (planstate->plan->parallel_aware)
{
switch (nodeTag(planstate))
{
case T_SeqScanState:
- ExecSeqScanInitializeWorker((SeqScanState *) planstate, toc);
+ ExecSeqScanInitializeWorker((SeqScanState *) planstate, pw_context->toc);
break;
case T_ForeignScanState:
ExecForeignScanInitializeWorker((ForeignScanState *) planstate,
- toc);
+ pw_context->toc);
break;
case T_CustomScanState:
ExecCustomScanInitializeWorker((CustomScanState *) planstate,
- toc);
+ pw_context->toc);
break;
default:
break;
}
}
- return planstate_tree_walker(planstate, ExecParallelInitializeWorker, toc);
+ return planstate_tree_walker(planstate, ExecParallelInitializeWorker, pw_context);
}
/*
@@ -764,15 +914,23 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
DestReceiver *receiver;
QueryDesc *queryDesc;
SharedExecutorInstrumentation *instrumentation;
+ SharedExecutorInstrumentationContext *sei_context = NULL;
int instrument_options = 0;
void *area_space;
dsa_area *area;
+ ParallelWorkerContext *pw_context;
+ Bitmapset *init_plan_node_ids;
/* Set up DestReceiver, SharedExecutorInstrumentation, and QueryDesc. */
receiver = ExecParallelGetReceiver(seg, toc);
instrumentation = shm_toc_lookup(toc, PARALLEL_KEY_INSTRUMENTATION);
if (instrumentation != NULL)
+ {
+ sei_context = palloc0(sizeof(SharedExecutorInstrumentationContext));
+ sei_context->instrumentation = instrumentation;
instrument_options = instrumentation->instrument_options;
+ }
+
queryDesc = ExecParallelGetQueryDesc(toc, receiver, instrument_options);
/* Prepare to track buffer usage during query execution. */
@@ -787,7 +945,14 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
/* Special executor initialization steps for parallel workers */
queryDesc->planstate->state->es_query_dsa = area;
- ExecParallelInitializeWorker(queryDesc->planstate, toc);
+ ExecParallelInitializeInitPlanParams(toc, queryDesc->estate->es_param_exec_vals);
+ ExecParallelInitializeInitPlanNodes(toc, &init_plan_node_ids);
+
+ pw_context = palloc0(sizeof(ParallelWorkerContext));
+ pw_context->toc = toc;
+ pw_context->init_plan_node_ids = init_plan_node_ids;
+
+ ExecParallelInitializeWorker(queryDesc->planstate, pw_context);
/* Run the plan */
ExecutorRun(queryDesc, ForwardScanDirection, 0L);
@@ -801,8 +966,11 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
/* Report instrumentation data if any instrumentation options are set. */
if (instrumentation != NULL)
+ {
+ sei_context->init_plan_node_ids = init_plan_node_ids;
ExecParallelReportInstrumentation(queryDesc->planstate,
- instrumentation);
+ sei_context);
+ }
/* Must do this after capturing instrumentation. */
ExecutorEnd(queryDesc);
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 4566219..df5cfdd 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -1092,6 +1092,56 @@ ExecEvalParamExec(ExprState *exprstate, ExprContext *econtext,
return prm->value;
}
+/*
+ * ExecEvalParamExecParams
+ *
+ * Execute the subplan stored in PARAM_EXEC initplans params, if not executed
+ * till now.
+ *
+ * eval_params is the list of params that are already processed.
+ * init_plan_node_ids is the list of plan node ids that are processed.
+ */
+void
+ExecEvalParamExecParams(EState *estate, Bitmapset *params,
+ Bitmapset **eval_params,
+ Bitmapset **init_plan_node_ids)
+{
+ ParamExecData *prm;
+ int paramid;
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ if (bms_is_member(paramid, *eval_params))
+ continue;
+
+ /*
+ * PARAM_EXEC params (internal executor parameters) are stored in the
+ * ecxt_param_exec_vals array, and can be accessed by array index.
+ */
+ prm = &(estate->es_param_exec_vals[paramid]);
+
+ if (!prm->isinitplan)
+ continue;
+
+ if (prm->execPlan != NULL)
+ {
+ SubPlanState *node = (SubPlanState *) prm->execPlan;
+
+ /* Include plan node id which is about to get evaluated. */
+ *init_plan_node_ids = bms_add_member(*init_plan_node_ids,
+ node->planstate->plan->plan_node_id);
+
+ /* Parameter not evaluated yet, so go do it */
+ ExecSetParamPlan(prm->execPlan, GetPerTupleExprContext(estate));
+ /* ExecSetParamPlan should have processed this param... */
+ Assert(prm->execPlan == NULL);
+ }
+
+ *eval_params = bms_add_member(*eval_params, paramid);
+ }
+}
+
/* ----------------------------------------------------------------
* ExecEvalParamExtern
*
diff --git a/src/backend/executor/nodeNestloop.c b/src/backend/executor/nodeNestloop.c
index cac7ba1..11aeaaf 100644
--- a/src/backend/executor/nodeNestloop.c
+++ b/src/backend/executor/nodeNestloop.c
@@ -126,6 +126,7 @@ ExecNestLoop(NestLoopState *node)
{
NestLoopParam *nlp = (NestLoopParam *) lfirst(lc);
int paramno = nlp->paramno;
+ TupleDesc tdesc = outerTupleSlot->tts_tupleDescriptor;
ParamExecData *prm;
prm = &(econtext->ecxt_param_exec_vals[paramno]);
@@ -136,6 +137,7 @@ ExecNestLoop(NestLoopState *node)
prm->value = slot_getattr(outerTupleSlot,
nlp->paramval->varattno,
&(prm->isnull));
+ prm->ptype = tdesc->attrs[nlp->paramval->varattno - 1]->atttypid;
/* Flag parameter value as changed */
innerPlan->chgParam = bms_add_member(innerPlan->chgParam,
paramno);
diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c
index 8f419a1..5cbaa24 100644
--- a/src/backend/executor/nodeSubplan.c
+++ b/src/backend/executor/nodeSubplan.c
@@ -30,11 +30,15 @@
#include <math.h>
#include "access/htup_details.h"
+#include "catalog/pg_type.h"
#include "executor/executor.h"
#include "executor/nodeSubplan.h"
#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
+#include "storage/shmem.h"
#include "utils/array.h"
+#include "utils/datum.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
@@ -249,6 +253,7 @@ ExecScanSubPlan(SubPlanState *node,
ParamExecData *prm = &(estate->es_param_exec_vals[paramid]);
prm->execPlan = node;
+ prm->isinitplan = subplan->isInitPlan;
}
*isNull = true;
return (Datum) 0;
@@ -276,11 +281,13 @@ ExecScanSubPlan(SubPlanState *node,
forboth(l, subplan->parParam, pvar, node->args)
{
int paramid = lfirst_int(l);
+ ExprState *exprstate = (ExprState *) lfirst(pvar);
ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]);
- prm->value = ExecEvalExprSwitchContext((ExprState *) lfirst(pvar),
+ prm->value = ExecEvalExprSwitchContext(exprstate,
econtext,
&(prm->isnull));
+ prm->ptype = exprType((Node *) exprstate->expr);
planstate->chgParam = bms_add_member(planstate->chgParam, paramid);
}
@@ -393,6 +400,7 @@ ExecScanSubPlan(SubPlanState *node,
prmdata = &(econtext->ecxt_param_exec_vals[paramid]);
Assert(prmdata->execPlan == NULL);
prmdata->value = slot_getattr(slot, col, &(prmdata->isnull));
+ prmdata->ptype = tdesc->attrs[col - 1]->atttypid;
col++;
}
@@ -550,6 +558,7 @@ buildSubPlanHash(SubPlanState *node, ExprContext *econtext)
int col = 1;
ListCell *plst;
bool isnew;
+ TupleDesc tdesc = slot->tts_tupleDescriptor;
/*
* Load up the Params representing the raw sub-select outputs, then
@@ -564,6 +573,7 @@ buildSubPlanHash(SubPlanState *node, ExprContext *econtext)
Assert(prmdata->execPlan == NULL);
prmdata->value = slot_getattr(slot, col,
&(prmdata->isnull));
+ prmdata->ptype = tdesc->attrs[col - 1]->atttypid;
col++;
}
slot = ExecProject(node->projRight);
@@ -748,6 +758,7 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent)
ParamExecData *prm = &(estate->es_param_exec_vals[paramid]);
prm->execPlan = sstate;
+ prm->isinitplan = subplan->isInitPlan;
}
}
@@ -943,6 +954,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
ListCell *l;
bool found = false;
ArrayBuildStateAny *astate = NULL;
+ Oid ptype;
if (subLinkType == ANY_SUBLINK ||
subLinkType == ALL_SUBLINK)
@@ -950,6 +962,8 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
if (subLinkType == CTE_SUBLINK)
elog(ERROR, "CTE subplans should not be executed via ExecSetParamPlan");
+ ptype = exprType((Node *) node->xprstate.expr);
+
/* Initialize ArrayBuildStateAny in caller's context, if needed */
if (subLinkType == ARRAY_SUBLINK)
astate = initArrayResultAny(subplan->firstColType,
@@ -972,11 +986,13 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
forboth(l, subplan->parParam, pvar, node->args)
{
int paramid = lfirst_int(l);
+ ExprState *exprstate = (ExprState *) lfirst(pvar);
ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]);
- prm->value = ExecEvalExprSwitchContext((ExprState *) lfirst(pvar),
+ prm->value = ExecEvalExprSwitchContext(exprstate,
econtext,
&(prm->isnull));
+ prm->ptype = exprType((Node *) exprstate->expr);
planstate->chgParam = bms_add_member(planstate->chgParam, paramid);
}
@@ -999,6 +1015,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = BoolGetDatum(true);
+ prm->ptype = ptype;
prm->isnull = false;
found = true;
break;
@@ -1050,6 +1067,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = heap_getattr(node->curTuple, i, tdesc,
&(prm->isnull));
+ prm->ptype = tdesc->attrs[i - 1]->atttypid;
i++;
}
}
@@ -1072,6 +1090,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
true);
prm->execPlan = NULL;
prm->value = node->curArray;
+ prm->ptype = ptype;
prm->isnull = false;
}
else if (!found)
@@ -1084,6 +1103,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = BoolGetDatum(false);
+ prm->ptype = ptype;
prm->isnull = false;
}
else
@@ -1096,6 +1116,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = (Datum) 0;
+ prm->ptype = VOIDOID;
prm->isnull = true;
}
}
@@ -1141,7 +1162,10 @@ ExecReScanSetParamPlan(SubPlanState *node, PlanState *parent)
ParamExecData *prm = &(estate->es_param_exec_vals[paramid]);
if (subplan->subLinkType != CTE_SUBLINK)
+ {
prm->execPlan = node;
+ prm->isinitplan = subplan->isInitPlan;
+ }
parent->chgParam = bms_add_member(parent->chgParam, paramid);
}
@@ -1220,3 +1244,206 @@ ExecAlternativeSubPlan(AlternativeSubPlanState *node,
return ExecSubPlan(activesp, econtext, isNull);
}
+
+/*
+ * Estimate the amount of space required to serialize the InitPlan params.
+ */
+Size
+EstimateInitPlanParamsSpace(ParamExecData *paramExecVals, Bitmapset *params)
+{
+ int paramid;
+ Size sz = sizeof(int);
+ ParamExecData *prm;
+
+ if (params == NULL)
+ return sz;
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ Oid typeOid;
+ int16 typLen;
+ bool typByVal;
+
+ prm = &(paramExecVals[paramid]);
+ typeOid = prm->ptype;
+
+ sz = add_size(sz, sizeof(int)); /* space for paramid */
+ sz = add_size(sz, sizeof(Oid)); /* space for type OID */
+
+ /* space for datum/isnull */
+ if (OidIsValid(typeOid))
+ get_typlenbyval(typeOid, &typLen, &typByVal);
+ else
+ {
+ /* If no type OID, assume by-value, like copyParamList does. */
+ typLen = sizeof(Datum);
+ typByVal = true;
+ }
+ sz = add_size(sz,
+ datumEstimateSpace(prm->value, prm->isnull, typByVal, typLen));
+ }
+ return sz;
+}
+
+/*
+ * Serialize ParamExecData params corresponding to initplans.
+ *
+ * We write the number of parameters first, as a 4-byte integer, and then
+ * write details for each parameter in turn. The details for each parameter
+ * consist of a 4-byte paramid (location of param in execution time internal
+ * parameter array), 4-byte type OID, and then the datum as serialized by
+ * datumSerialize().
+ *
+ * The above format is quite similar to the format used to serialize
+ * paramListInfo structure, so if we change either format, then consider to
+ * change at both the places.
+ */
+void
+SerializeInitPlanParams(ParamExecData *paramExecVals, Bitmapset *params,
+ char **start_address)
+{
+ int nparams;
+ int paramid;
+ ParamExecData *prm;
+
+ if (params == NULL)
+ nparams = 0;
+ else
+ nparams = bms_num_members(params);
+ memcpy(*start_address, &nparams, sizeof(int));
+ *start_address += sizeof(int);
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ Oid typeOid;
+ int16 typLen;
+ bool typByVal;
+
+ prm = &(paramExecVals[paramid]);
+ typeOid = prm->ptype;
+
+ /* Write paramid. */
+ memcpy(*start_address, ¶mid, sizeof(int));
+ *start_address += sizeof(int);
+
+ /* Write OID. */
+ memcpy(*start_address, &typeOid, sizeof(Oid));
+ *start_address += sizeof(Oid);
+
+ /* space for datum/isnull */
+ if (OidIsValid(typeOid))
+ get_typlenbyval(typeOid, &typLen, &typByVal);
+ else
+ {
+ /* If no type OID, assume by-value, like copyParamList does. */
+ typLen = sizeof(Datum);
+ typByVal = true;
+ }
+ datumSerialize(prm->value, prm->isnull, typByVal, typLen,
+ start_address);
+ }
+}
+
+/*
+ * Restore ParamExecData params corresponding to initplans.
+ */
+void
+RestoreInitPlanParams(char **start_address, ParamExecData *params)
+{
+ int nparams;
+ int i;
+ int paramid;
+
+ memcpy(&nparams, *start_address, sizeof(int));
+ *start_address += sizeof(int);
+
+ for (i = 0; i < nparams; i++)
+ {
+ ParamExecData *prm;
+
+ /* Read paramid */
+ memcpy(¶mid, *start_address, sizeof(int));
+ *start_address += sizeof(int);
+ prm = ¶ms[paramid];
+
+ /* Read type OID. */
+ memcpy(&prm->ptype, *start_address, sizeof(Oid));
+ *start_address += sizeof(Oid);
+
+ /* Read datum/isnull. */
+ prm->value = datumRestore(start_address, &prm->isnull);
+ prm->execPlan = NULL;
+ }
+}
+
+/*
+ * Estimate the amount of space required to serialize the evaluated
+ * plan node ids corresponding to InitPlans.
+ */
+Size
+EstimateInitPlanNodeIdsSpace(Bitmapset *plan_node_ids)
+{
+ Size sz = sizeof(int);
+
+ if (!plan_node_ids)
+ return sz;
+
+ sz += bms_num_members(plan_node_ids) * sizeof(int);
+ return sz;
+}
+
+/*
+ * Serialize evaluated plan node ids corresponding to initplans.
+ *
+ * We write the number of nodes first, as a 4-byte integer, and then
+ * write each plan node id in turn.
+ */
+void
+SerializeInitPlanNodeIds(Bitmapset *plan_node_ids, char **start_address)
+{
+ int nnodes;
+ int plan_id;
+
+ if (!plan_node_ids)
+ nnodes = 0;
+ else
+ nnodes = bms_num_members(plan_node_ids);
+ memcpy(*start_address, &nnodes, sizeof(int));
+ *start_address += sizeof(int);
+
+ plan_id = -1;
+ while ((plan_id = bms_next_member(plan_node_ids, plan_id)) >= 0)
+ {
+ /* Write plan id. */
+ memcpy(*start_address, &plan_id, sizeof(int));
+ *start_address += sizeof(int);
+ }
+}
+
+/*
+ * Restore evaluated plan node ids corresponding to initplans.
+ */
+Bitmapset *
+RestoreInitPlanNodeIds(char **start_address)
+{
+ int nnodes;
+ int plan_id;
+ int i;
+
+ Bitmapset *plan_node_ids = NULL;
+
+ memcpy(&nnodes, *start_address, sizeof(int));
+ *start_address += sizeof(int);
+
+ for (i = 0; i < nnodes; i++)
+ {
+ /* Read plan id */
+ memcpy(&plan_id, *start_address, sizeof(int));
+ *start_address += sizeof(int);
+ plan_node_ids = bms_add_member(plan_node_ids, plan_id);
+ }
+
+ return plan_node_ids;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 5d57138..dfc828f 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1498,6 +1498,7 @@ _copySubPlan(const SubPlan *from)
COPY_SCALAR_FIELD(useHashTable);
COPY_SCALAR_FIELD(unknownEqFalse);
COPY_SCALAR_FIELD(parallel_safe);
+ COPY_SCALAR_FIELD(isInitPlan);
COPY_NODE_FIELD(setParam);
COPY_NODE_FIELD(parParam);
COPY_NODE_FIELD(args);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 46f7c5b..ce5d748 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -426,6 +426,7 @@ _equalSubPlan(const SubPlan *a, const SubPlan *b)
COMPARE_SCALAR_FIELD(useHashTable);
COMPARE_SCALAR_FIELD(unknownEqFalse);
COMPARE_SCALAR_FIELD(parallel_safe);
+ COMPARE_SCALAR_FIELD(isInitPlan);
COMPARE_NODE_FIELD(setParam);
COMPARE_NODE_FIELD(parParam);
COMPARE_NODE_FIELD(args);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 69e1919..fc56188 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1229,6 +1229,7 @@ _outSubPlan(StringInfo str, const SubPlan *node)
WRITE_BOOL_FIELD(useHashTable);
WRITE_BOOL_FIELD(unknownEqFalse);
WRITE_BOOL_FIELD(parallel_safe);
+ WRITE_BOOL_FIELD(isInitPlan);
WRITE_NODE_FIELD(setParam);
WRITE_NODE_FIELD(parParam);
WRITE_NODE_FIELD(args);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 2184f9e..e415d42 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2236,6 +2236,7 @@ _readSubPlan(void)
READ_BOOL_FIELD(useHashTable);
READ_BOOL_FIELD(unknownEqFalse);
READ_BOOL_FIELD(parallel_safe);
+ READ_BOOL_FIELD(isInitPlan);
READ_NODE_FIELD(setParam);
READ_NODE_FIELD(parParam);
READ_NODE_FIELD(args);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index caa3a4e..2a0d630 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -329,6 +329,14 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
{
Gather *gather = makeNode(Gather);
+ /*
+ * If there are any initPlans attached to the formerly-top plan node,
+ * move them up to the Gather node; same as we do for Material node
+ * above.
+ */
+ gather->plan.initPlan = top_plan->initPlan;
+ top_plan->initPlan = NIL;
+
gather->plan.targetlist = top_plan->targetlist;
gather->plan.qual = NIL;
gather->plan.lefttree = top_plan;
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 41743e4..238b58d 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -58,9 +58,10 @@ static Node *build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot,
List *plan_params,
SubLinkType subLinkType, int subLinkId,
Node *testexpr, bool adjust_testexpr,
- bool unknownEqFalse, bool parallel_safe);
+ bool unknownEqFalse, bool parallel_safe,
+ bool contain_correl_param);
static List *generate_subquery_params(PlannerInfo *root, List *tlist,
- List **paramIds);
+ List **paramIds, bool parallel_safe);
static List *generate_subquery_vars(PlannerInfo *root, List *tlist,
Index varno);
static Node *convert_testexpr(PlannerInfo *root,
@@ -412,7 +413,7 @@ replace_outer_grouping(PlannerInfo *root, GroupingFunc *grp)
*/
static Param *
generate_new_param(PlannerInfo *root, Oid paramtype, int32 paramtypmod,
- Oid paramcollation)
+ Oid paramcollation, bool parallel_safe)
{
Param *retval;
@@ -423,7 +424,7 @@ generate_new_param(PlannerInfo *root, Oid paramtype, int32 paramtypmod,
retval->paramtypmod = paramtypmod;
retval->paramcollid = paramcollation;
retval->location = -1;
- retval->parallel_safe = false;
+ retval->parallel_safe = parallel_safe;
retval->is_correlated = false;
return retval;
@@ -578,7 +579,8 @@ make_subplan(PlannerInfo *root, Query *orig_subquery,
result = build_subplan(root, plan, subroot, plan_params,
subLinkType, subLinkId,
testexpr, true, isTopQual,
- best_path->parallel_safe);
+ best_path->parallel_safe,
+ best_path->contain_correl_param);
/*
* If it's a correlated EXISTS with an unimportant targetlist, we might be
@@ -632,7 +634,8 @@ make_subplan(PlannerInfo *root, Query *orig_subquery,
ANY_SUBLINK, 0,
newtestexpr,
false, true,
- best_path->parallel_safe);
+ best_path->parallel_safe,
+ best_path->contain_correl_param);
/* Check we got what we expected */
Assert(IsA(hashplan, SubPlan));
Assert(hashplan->parParam == NIL);
@@ -662,12 +665,14 @@ build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot,
List *plan_params,
SubLinkType subLinkType, int subLinkId,
Node *testexpr, bool adjust_testexpr,
- bool unknownEqFalse, bool parallel_safe)
+ bool unknownEqFalse, bool parallel_safe,
+ bool contain_correl_param)
{
Node *result;
SubPlan *splan;
bool isInitPlan;
ListCell *lc;
+ bool initplan_parallel_safe = true;
/*
* Initialize the SubPlan node. Note plan_id, plan_name, and cost fields
@@ -721,13 +726,22 @@ build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot,
* null constant: the resjunk targetlist item containing the SubLink does
* not need to return anything useful, since the referencing Params are
* elsewhere.
- */
+ *
+ * initPlans which doesn't contain reference to correlated params are
+ * parallel safe because we always execute them in master backend before
+ * the processing of gather node. The plans of ROWCOMPARE or MULTIEXPR
+ * types are considred parallel_safe only when the corresponding subplan
+ * is parallel safe. The reason is that it is not clear whether plan of
+ * those types can be considered as parallel safe.
+ */
+ if (contain_correl_param)
+ initplan_parallel_safe = false;
if (splan->parParam == NIL && subLinkType == EXISTS_SUBLINK)
{
Param *prm;
Assert(testexpr == NULL);
- prm = generate_new_param(root, BOOLOID, -1, InvalidOid);
+ prm = generate_new_param(root, BOOLOID, -1, InvalidOid, initplan_parallel_safe);
splan->setParam = list_make1_int(prm->paramid);
isInitPlan = true;
result = (Node *) prm;
@@ -742,7 +756,8 @@ build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot,
prm = generate_new_param(root,
exprType((Node *) te->expr),
exprTypmod((Node *) te->expr),
- exprCollation((Node *) te->expr));
+ exprCollation((Node *) te->expr),
+ initplan_parallel_safe);
splan->setParam = list_make1_int(prm->paramid);
isInitPlan = true;
result = (Node *) prm;
@@ -762,7 +777,8 @@ build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot,
prm = generate_new_param(root,
arraytype,
exprTypmod((Node *) te->expr),
- exprCollation((Node *) te->expr));
+ exprCollation((Node *) te->expr),
+ initplan_parallel_safe);
splan->setParam = list_make1_int(prm->paramid);
isInitPlan = true;
result = (Node *) prm;
@@ -775,7 +791,8 @@ build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot,
Assert(testexpr != NULL);
params = generate_subquery_params(root,
plan->targetlist,
- &splan->paramIds);
+ &splan->paramIds,
+ parallel_safe);
result = convert_testexpr(root,
testexpr,
params);
@@ -798,7 +815,8 @@ build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot,
Assert(testexpr == NULL);
params = generate_subquery_params(root,
plan->targetlist,
- &splan->setParam);
+ &splan->setParam,
+ parallel_safe);
/*
* Save the list of replacement Params in the n'th cell of
@@ -835,7 +853,8 @@ build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot,
params = generate_subquery_params(root,
plan->targetlist,
- &splan->paramIds);
+ &splan->paramIds,
+ parallel_safe);
splan->testexpr = convert_testexpr(root,
testexpr,
params);
@@ -882,7 +901,12 @@ build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot,
splan->plan_id = list_length(root->glob->subplans);
if (isInitPlan)
+ {
root->init_plans = lappend(root->init_plans, splan);
+ splan->isInitPlan = true;
+ }
+ else
+ splan->isInitPlan = false;
/*
* A parameterless subplan (not initplan) should be prepared to handle
@@ -926,7 +950,8 @@ build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot,
* We also return an integer list of the paramids of the Params.
*/
static List *
-generate_subquery_params(PlannerInfo *root, List *tlist, List **paramIds)
+generate_subquery_params(PlannerInfo *root, List *tlist, List **paramIds,
+ bool parallel_safe)
{
List *result;
List *ids;
@@ -944,7 +969,8 @@ generate_subquery_params(PlannerInfo *root, List *tlist, List **paramIds)
param = generate_new_param(root,
exprType((Node *) tent->expr),
exprTypmod((Node *) tent->expr),
- exprCollation((Node *) tent->expr));
+ exprCollation((Node *) tent->expr),
+ parallel_safe);
result = lappend(result, param);
ids = lappend_int(ids, param->paramid);
}
@@ -1277,6 +1303,8 @@ SS_process_ctes(PlannerInfo *root)
root->glob->subroots = lappend(root->glob->subroots, subroot);
splan->plan_id = list_length(root->glob->subplans);
+ splan->isInitPlan = true;
+
root->init_plans = lappend(root->init_plans, splan);
root->cte_plan_ids = lappend_int(root->cte_plan_ids, splan->plan_id);
@@ -1873,10 +1901,17 @@ convert_EXISTS_to_ANY(PlannerInfo *root, Query *subselect,
Param *param;
cc = lnext(cc);
+
+ /*
+ * it is not clear whether generating parallel safe param has any
+ * benefit, so not incurring the cost to identify the parallel-safety
+ * of an argument seems advisable.
+ */
param = generate_new_param(root,
exprType(rightarg),
exprTypmod(rightarg),
- exprCollation(rightarg));
+ exprCollation(rightarg),
+ false);
tlist = lappend(tlist,
makeTargetEntry((Expr *) rightarg,
resno++,
@@ -2163,13 +2198,11 @@ SS_identify_outer_params(PlannerInfo *root)
}
/*
- * SS_charge_for_initplans - account for initplans in Path costs & parallelism
+ * SS_charge_for_initplans - account for initplans in Path costs
*
* If any initPlans have been created in the current query level, they will
* get attached to the Plan tree created from whichever Path we select from
- * the given rel. Increment all that rel's Paths' costs to account for them,
- * and make sure the paths get marked as parallel-unsafe, since we can't
- * currently transmit initPlans to parallel workers.
+ * the given rel. Increment all that rel's Paths' costs to account for them.
*
* This is separate from SS_attach_initplans because we might conditionally
* create more initPlans during create_plan(), depending on which Path we
@@ -2201,7 +2234,7 @@ SS_charge_for_initplans(PlannerInfo *root, RelOptInfo *final_rel)
}
/*
- * Now adjust the costs and parallel_safe flags.
+ * Now adjust the costs.
*/
foreach(lc, final_rel->pathlist)
{
@@ -2209,7 +2242,6 @@ SS_charge_for_initplans(PlannerInfo *root, RelOptInfo *final_rel)
path->startup_cost += initplan_cost;
path->total_cost += initplan_cost;
- path->parallel_safe = false;
}
/* We needn't do set_cheapest() here, caller will do it */
@@ -2905,7 +2937,7 @@ SS_make_initplan_output_param(PlannerInfo *root,
Oid resulttype, int32 resulttypmod,
Oid resultcollation)
{
- return generate_new_param(root, resulttype, resulttypmod, resultcollation);
+ return generate_new_param(root, resulttype, resulttypmod, resultcollation, false);
}
/*
@@ -2942,6 +2974,8 @@ SS_make_initplan_from_plan(PlannerInfo *root,
&node->firstColCollation);
node->setParam = list_make1_int(prm->paramid);
+ node->isInitPlan = true;
+
root->init_plans = lappend(root->init_plans, node);
/*
diff --git a/src/include/executor/execParallel.h b/src/include/executor/execParallel.h
index 8bc4270..9cc44bd 100644
--- a/src/include/executor/execParallel.h
+++ b/src/include/executor/execParallel.h
@@ -20,13 +20,14 @@
#include "utils/dsa.h"
typedef struct SharedExecutorInstrumentation SharedExecutorInstrumentation;
+typedef struct SharedExecutorInstrumentationContext SharedExecutorInstrumentationContext;
typedef struct ParallelExecutorInfo
{
PlanState *planstate;
ParallelContext *pcxt;
BufferUsage *buffer_usage;
- SharedExecutorInstrumentation *instrumentation;
+ SharedExecutorInstrumentationContext *sei_context;
shm_mq_handle **tqueue;
dsa_area *area;
bool finished;
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 02dbe7b..5acd5e4 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -243,6 +243,9 @@ extern bool ExecShutdownNode(PlanState *node);
/*
* prototypes from functions in execQual.c
*/
+extern void ExecEvalParamExecParams(EState *estate, Bitmapset *params,
+ Bitmapset **eval_params,
+ Bitmapset **init_plan_node_ids);
extern Datum GetAttributeByNum(HeapTupleHeader tuple, AttrNumber attrno,
bool *isNull);
extern Datum GetAttributeByName(HeapTupleHeader tuple, const char *attname,
diff --git a/src/include/executor/nodeSubplan.h b/src/include/executor/nodeSubplan.h
index 0f821dc..823acef 100644
--- a/src/include/executor/nodeSubplan.h
+++ b/src/include/executor/nodeSubplan.h
@@ -24,4 +24,16 @@ extern void ExecReScanSetParamPlan(SubPlanState *node, PlanState *parent);
extern void ExecSetParamPlan(SubPlanState *node, ExprContext *econtext);
+extern Size EstimateInitPlanParamsSpace(ParamExecData *paramExecVals, Bitmapset *params);
+
+extern void SerializeInitPlanParams(ParamExecData *paramExecVals, Bitmapset *params, char **start_address);
+
+extern void RestoreInitPlanParams(char **start_address, ParamExecData *params);
+
+extern Size EstimateInitPlanNodeIdsSpace(Bitmapset *plan_node_ids);
+
+extern void SerializeInitPlanNodeIds(Bitmapset *plan_node_ids, char **start_address);
+
+extern Bitmapset *RestoreInitPlanNodeIds(char **start_address);
+
#endif /* NODESUBPLAN_H */
diff --git a/src/include/nodes/params.h b/src/include/nodes/params.h
index e19ac24..68a0435 100644
--- a/src/include/nodes/params.h
+++ b/src/include/nodes/params.h
@@ -98,7 +98,19 @@ typedef struct ParamExecData
{
void *execPlan; /* should be "SubPlanState *" */
Datum value;
+
+ /*
+ * parameter's datatype, or 0. This is required so that datum value can
+ * be read and used for other purposes like passing it to worker backend
+ * via shared memory. This is required only for initPlan's evaluation,
+ * however for consistency we set this for Subplan as well. We left it
+ * for other cases like CTE or RecursiveUnion cases where this structure
+ * is not used for evaluation of subplans.
+ */
+ Oid ptype;
bool isnull;
+ /* indicates if the param points to initplan */
+ bool isinitplan;
} ParamExecData;
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 16860e9..d5923e2 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -680,6 +680,7 @@ typedef struct SubPlan
* spec result is UNKNOWN; this allows much
* simpler handling of null values */
bool parallel_safe; /* OK to use as part of parallel plan? */
+ bool isInitPlan; /* TRUE if it's an InitPlan */
/* 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
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index c4235ae..f2fb4ca 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -544,6 +544,7 @@ ExecForeignInsert_function
ExecForeignUpdate_function
ExecParallelEstimateContext
ExecParallelInitializeDSMContext
+ExecParallelEvalInitPlan
ExecPhraseData
ExecRowMark
ExecScanAccessMtd
@@ -1458,6 +1459,7 @@ ParallelExecutorInfo
ParallelHeapScanDesc
ParallelSlot
ParallelState
+ParallelWorkerContext
ParallelWorkerInfo
Param
ParamExecData
@@ -1914,6 +1916,7 @@ SetupWorkerPtr
SharedDependencyObjectType
SharedDependencyType
SharedExecutorInstrumentation
+SharedExecutorInstrumentationContext
SharedInvalCatalogMsg
SharedInvalCatcacheMsg
SharedInvalRelcacheMsg
On Fri, Feb 10, 2017 at 4:34 PM, Amit Kapila <amit.kapila16@gmail.com> wrote:
I could see two possibilities to determine whether the plan (for which
we are going to generate an initplan) contains a reference to a
correlated var param node. One is to write a plan or path walker to
determine any such reference and the second is to keep the information
about the correlated param in path node. I think the drawback of the
first approach is that traversing path tree during generation of
initplan can be costly, so for now I have kept the information in path
node to prohibit generating parallel initplans which contain a
reference to correlated vars. I think we can go with first approach of
using path walker if people feel that is better than maintaining a
reference in path. Attached patch
prohibit_parallel_correl_params_v1.patch implements the second
approach of keeping the correlated var param reference in path node
and pq_pushdown_initplan_v2.patch uses that to generate parallel
initplans.
Two weeks back when Robert was in Bangalore, we (myself, Kuntal and
Robert) had a discussion on this patch. He mentioned that the idea
of pulling up initplans (uncorrelated initplans) at Gather node (and
then execute them and share the values to each worker) used in this
patch doesn't sound appealing and has a chance of bugs in some corner
cases. We discussed an idea where the first worker to access the
initplan will evaluate it and then share the value with other
participating processes, but with that, we won't be able to use
parallelism in the execution of Initplan due to the restriction of
multiple levels of Gather node. Another idea we discussed is that we
can evaluate the Initplans at Gather node if it is used as an external
param (plan->extParam) at or below the Gather node.
Based on that idea, I have modified the patch such that it will
compute the set of initplans Params that are required below gather
node and store them as bitmap of initplan params at gather node.
During set_plan_references, we can find the intersection of external
parameters that are required at Gather or nodes below it with the
initplans that are passed from same or above query level. Once the set
of initplan params are established, we evaluate those (if they are not
already evaluated) before execution of gather node and then pass the
computed value to each of the workers. To identify whether a
particular param is parallel safe or not, we check if the paramid of
the param exists in initplans at same or above query level. We don't
allow to generate gather path if there are initplans at some query
level below the current query level as those plans could be
parallel-unsafe or undirect correlated plans.
This restricts some of the cases for parallelism like when initplans
are below gather node, but the patch looks better. We can open up
those cases if required in a separate patch.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
pq_pushdown_initplan_v3.patchapplication/octet-stream; name=pq_pushdown_initplan_v3.patchDownload
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index c9b55ea..8c16370 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -106,6 +106,7 @@ static void show_tidbitmap_info(BitmapHeapScanState *planstate,
static void show_instrumentation_count(const char *qlabel, int which,
PlanState *planstate, ExplainState *es);
static void show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es);
+static void show_eval_params(Bitmapset *bms_params, ExplainState *es);
static const char *explain_get_index_name(Oid indexId);
static void show_buffer_usage(ExplainState *es, const BufferUsage *usage);
static void ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir,
@@ -631,7 +632,17 @@ ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc)
*/
ps = queryDesc->planstate;
if (IsA(ps, GatherState) &&((Gather *) ps->plan)->invisible)
+ {
+ List *initPlanState = NULL;
+ PlanState *save_ps;
+
+ /* initplans are always attached to top node (cf standard_planner) */
+ save_ps = ps;
+ initPlanState = ps->initPlan;
ps = outerPlanState(ps);
+ ps->initPlan = initPlanState;
+ save_ps->initPlan = NIL;
+ }
ExplainNode(ps, NIL, NULL, NULL, es);
}
@@ -1402,6 +1413,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
planstate, es);
ExplainPropertyInteger("Workers Planned",
gather->num_workers, es);
+
+ /* Show params evaluated at gather node */
+ if (gather->initParam)
+ show_eval_params(gather->initParam, es);
+
if (es->analyze)
{
int nworkers;
@@ -1424,6 +1440,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
planstate, es);
ExplainPropertyInteger("Workers Planned",
gm->num_workers, es);
+
+ /* Show params evaluated at gather-merge node */
+ if (gm->initParam)
+ show_eval_params(gm->initParam, es);
+
if (es->analyze)
{
int nworkers;
@@ -2385,6 +2406,28 @@ show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es)
}
/*
+ * Show initplan params evaluated at gather or gather merge node.
+ */
+static void
+show_eval_params(Bitmapset *bms_params, ExplainState *es)
+{
+ int paramid = -1;
+ List *params = NIL;
+
+ Assert(bms_params);
+
+ while ((paramid = bms_next_member(bms_params, paramid)) >= 0)
+ {
+ char param[32];
+
+ snprintf(param, sizeof(param), "$%d", paramid);
+ params = lappend(params, pstrdup(param));
+ }
+
+ ExplainPropertyList("Params Evaluated", params, es);
+}
+
+/*
* Fetch the name of an index in an EXPLAIN
*
* We allow plugins to get control here so that plans involving hypothetical
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index a1289e5..09bb721 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -29,6 +29,7 @@
#include "executor/nodeCustom.h"
#include "executor/nodeForeignscan.h"
#include "executor/nodeSeqscan.h"
+#include "executor/nodeSubplan.h"
#include "executor/nodeIndexscan.h"
#include "executor/nodeIndexonlyscan.h"
#include "executor/tqueue.h"
@@ -54,6 +55,7 @@
#define PARALLEL_KEY_INSTRUMENTATION UINT64CONST(0xE000000000000005)
#define PARALLEL_KEY_DSA UINT64CONST(0xE000000000000006)
#define PARALLEL_KEY_QUERY_TEXT UINT64CONST(0xE000000000000007)
+#define PARALLEL_KEY_INITPLAN_PARAMS UINT64CONST(0xE000000000000008)
#define PARALLEL_TUPLE_QUEUE_SIZE 65536
@@ -364,7 +366,8 @@ ExecParallelReinitialize(ParallelExecutorInfo *pei)
* execution and return results to the main backend.
*/
ParallelExecutorInfo *
-ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers)
+ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
+ Bitmapset *initParam)
{
ParallelExecutorInfo *pei;
ParallelContext *pcxt;
@@ -373,10 +376,12 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers)
char *pstmt_data;
char *pstmt_space;
char *param_space;
+ char *initplan_param_space;
BufferUsage *bufusage_space;
SharedExecutorInstrumentation *instrumentation = NULL;
int pstmt_len;
int param_len;
+ int initplan_param_len;
int instrumentation_len = 0;
int instrument_offset = 0;
Size dsa_minsize = dsa_minimum_size();
@@ -388,6 +393,8 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers)
pei->finished = false;
pei->planstate = planstate;
+ ExecEvalParamExecParams(initParam, estate);
+
/* Fix up and serialize plan to be sent to workers. */
pstmt_data = ExecSerializePlan(planstate->plan, estate);
@@ -416,6 +423,11 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers)
shm_toc_estimate_chunk(&pcxt->estimator, param_len);
shm_toc_estimate_keys(&pcxt->estimator, 1);
+ /* Estimate space for initplan params. */
+ initplan_param_len = EstimateInitPlanParamsSpace(estate->es_param_exec_vals, initParam);
+ shm_toc_estimate_chunk(&pcxt->estimator, initplan_param_len);
+ shm_toc_estimate_keys(&pcxt->estimator, 1);
+
/*
* Estimate space for BufferUsage.
*
@@ -485,6 +497,11 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers)
shm_toc_insert(pcxt->toc, PARALLEL_KEY_PARAMS, param_space);
SerializeParamList(estate->es_param_list_info, ¶m_space);
+ /* Store serialized initplan params. */
+ initplan_param_space = shm_toc_allocate(pcxt->toc, initplan_param_len);
+ shm_toc_insert(pcxt->toc, PARALLEL_KEY_INITPLAN_PARAMS, initplan_param_space);
+ SerializeInitPlanParams(estate->es_param_exec_vals, initParam, &initplan_param_space);
+
/* Allocate space for each worker's BufferUsage; no need to initialize. */
bufusage_space = shm_toc_allocate(pcxt->toc,
mul_size(sizeof(BufferUsage), pcxt->nworkers));
@@ -675,6 +692,21 @@ ExecParallelGetReceiver(dsm_segment *seg, shm_toc *toc)
}
/*
+ * Copy the ParamExecData params corresponding to initplans from dynamic
+ * shared memory. This has to be done once the params are allocated by
+ * executor; that is after ExecutorStart().
+ */
+static void
+ExecParallelInitializeInitPlanParams(shm_toc *toc, ParamExecData *params)
+{
+ char *paramspace;
+
+ /* Reconstruct initplan params. */
+ paramspace = shm_toc_lookup(toc, PARALLEL_KEY_INITPLAN_PARAMS);
+ RestoreInitPlanParams(¶mspace, params);
+}
+
+/*
* Create a QueryDesc for the PlannedStmt we are to execute, and return it.
*/
static QueryDesc *
@@ -849,6 +881,7 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
/* Special executor initialization steps for parallel workers */
queryDesc->planstate->state->es_query_dsa = area;
+ ExecParallelInitializeInitPlanParams(toc, queryDesc->estate->es_param_exec_vals);
ExecParallelInitializeWorker(queryDesc->planstate, toc);
/* Run the plan */
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 90bef6f..72a1dcb 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -1094,6 +1094,34 @@ ExecEvalParamExec(ExprState *exprstate, ExprContext *econtext,
}
/* ----------------------------------------------------------------
+ * ExecEvalParamExecParams
+ *
+ * Execute the subplan stored in PARAM_EXEC initplans params, if not executed
+ * till now.
+ * ----------------------------------------------------------------
+ */
+void
+ExecEvalParamExecParams(Bitmapset *params, EState *estate)
+{
+ ParamExecData *prm;
+ int paramid;
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ prm = &(estate->es_param_exec_vals[paramid]);
+
+ if (prm->execPlan != NULL)
+ {
+ /* Parameter not evaluated yet, so go do it */
+ ExecSetParamPlan(prm->execPlan, GetPerTupleExprContext(estate));
+ /* ExecSetParamPlan should have processed this param... */
+ Assert(prm->execPlan == NULL);
+ }
+ }
+}
+
+/* ----------------------------------------------------------------
* ExecEvalParamExtern
*
* Returns the value of a PARAM_EXTERN parameter.
diff --git a/src/backend/executor/nodeGather.c b/src/backend/executor/nodeGather.c
index 32c97d3..31d4bf5 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -155,7 +155,8 @@ ExecGather(GatherState *node)
if (!node->pei)
node->pei = ExecInitParallelPlan(node->ps.lefttree,
estate,
- gather->num_workers);
+ gather->num_workers,
+ gather->initParam);
/*
* Register backend workers. We might not get as many as we
diff --git a/src/backend/executor/nodeGatherMerge.c b/src/backend/executor/nodeGatherMerge.c
index 72f30ab..6df2226 100644
--- a/src/backend/executor/nodeGatherMerge.c
+++ b/src/backend/executor/nodeGatherMerge.c
@@ -189,7 +189,8 @@ ExecGatherMerge(GatherMergeState *node)
if (!node->pei)
node->pei = ExecInitParallelPlan(node->ps.lefttree,
estate,
- gm->num_workers);
+ gm->num_workers,
+ gm->initParam);
/* Try to launch workers. */
pcxt = node->pei->pcxt;
diff --git a/src/backend/executor/nodeNestloop.c b/src/backend/executor/nodeNestloop.c
index cac7ba1..11aeaaf 100644
--- a/src/backend/executor/nodeNestloop.c
+++ b/src/backend/executor/nodeNestloop.c
@@ -126,6 +126,7 @@ ExecNestLoop(NestLoopState *node)
{
NestLoopParam *nlp = (NestLoopParam *) lfirst(lc);
int paramno = nlp->paramno;
+ TupleDesc tdesc = outerTupleSlot->tts_tupleDescriptor;
ParamExecData *prm;
prm = &(econtext->ecxt_param_exec_vals[paramno]);
@@ -136,6 +137,7 @@ ExecNestLoop(NestLoopState *node)
prm->value = slot_getattr(outerTupleSlot,
nlp->paramval->varattno,
&(prm->isnull));
+ prm->ptype = tdesc->attrs[nlp->paramval->varattno - 1]->atttypid;
/* Flag parameter value as changed */
innerPlan->chgParam = bms_add_member(innerPlan->chgParam,
paramno);
diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c
index 8f419a1..b75fae6 100644
--- a/src/backend/executor/nodeSubplan.c
+++ b/src/backend/executor/nodeSubplan.c
@@ -30,11 +30,15 @@
#include <math.h>
#include "access/htup_details.h"
+#include "catalog/pg_type.h"
#include "executor/executor.h"
#include "executor/nodeSubplan.h"
#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
+#include "storage/shmem.h"
#include "utils/array.h"
+#include "utils/datum.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
@@ -276,11 +280,13 @@ ExecScanSubPlan(SubPlanState *node,
forboth(l, subplan->parParam, pvar, node->args)
{
int paramid = lfirst_int(l);
+ ExprState *exprstate = (ExprState *) lfirst(pvar);
ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]);
- prm->value = ExecEvalExprSwitchContext((ExprState *) lfirst(pvar),
+ prm->value = ExecEvalExprSwitchContext(exprstate,
econtext,
&(prm->isnull));
+ prm->ptype = exprType((Node *) exprstate->expr);
planstate->chgParam = bms_add_member(planstate->chgParam, paramid);
}
@@ -393,6 +399,7 @@ ExecScanSubPlan(SubPlanState *node,
prmdata = &(econtext->ecxt_param_exec_vals[paramid]);
Assert(prmdata->execPlan == NULL);
prmdata->value = slot_getattr(slot, col, &(prmdata->isnull));
+ prmdata->ptype = tdesc->attrs[col - 1]->atttypid;
col++;
}
@@ -559,11 +566,13 @@ buildSubPlanHash(SubPlanState *node, ExprContext *econtext)
{
int paramid = lfirst_int(plst);
ParamExecData *prmdata;
+ TupleDesc tdesc = slot->tts_tupleDescriptor;
prmdata = &(innerecontext->ecxt_param_exec_vals[paramid]);
Assert(prmdata->execPlan == NULL);
prmdata->value = slot_getattr(slot, col,
&(prmdata->isnull));
+ prmdata->ptype = tdesc->attrs[col - 1]->atttypid;
col++;
}
slot = ExecProject(node->projRight);
@@ -943,6 +952,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
ListCell *l;
bool found = false;
ArrayBuildStateAny *astate = NULL;
+ Oid ptype;
if (subLinkType == ANY_SUBLINK ||
subLinkType == ALL_SUBLINK)
@@ -950,6 +960,8 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
if (subLinkType == CTE_SUBLINK)
elog(ERROR, "CTE subplans should not be executed via ExecSetParamPlan");
+ ptype = exprType((Node *) node->xprstate.expr);
+
/* Initialize ArrayBuildStateAny in caller's context, if needed */
if (subLinkType == ARRAY_SUBLINK)
astate = initArrayResultAny(subplan->firstColType,
@@ -972,11 +984,13 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
forboth(l, subplan->parParam, pvar, node->args)
{
int paramid = lfirst_int(l);
+ ExprState *exprstate = (ExprState *) lfirst(pvar);
ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]);
- prm->value = ExecEvalExprSwitchContext((ExprState *) lfirst(pvar),
+ prm->value = ExecEvalExprSwitchContext(exprstate,
econtext,
&(prm->isnull));
+ prm->ptype = exprType((Node *) exprstate->expr);
planstate->chgParam = bms_add_member(planstate->chgParam, paramid);
}
@@ -999,6 +1013,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = BoolGetDatum(true);
+ prm->ptype = ptype;
prm->isnull = false;
found = true;
break;
@@ -1050,6 +1065,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = heap_getattr(node->curTuple, i, tdesc,
&(prm->isnull));
+ prm->ptype = tdesc->attrs[i - 1]->atttypid;
i++;
}
}
@@ -1072,6 +1088,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
true);
prm->execPlan = NULL;
prm->value = node->curArray;
+ prm->ptype = ptype;
prm->isnull = false;
}
else if (!found)
@@ -1084,6 +1101,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = BoolGetDatum(false);
+ prm->ptype = ptype;
prm->isnull = false;
}
else
@@ -1096,6 +1114,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = (Datum) 0;
+ prm->ptype = VOIDOID;
prm->isnull = true;
}
}
@@ -1220,3 +1239,136 @@ ExecAlternativeSubPlan(AlternativeSubPlanState *node,
return ExecSubPlan(activesp, econtext, isNull);
}
+
+/*
+ * Estimate the amount of space required to serialize the InitPlan params.
+ */
+Size
+EstimateInitPlanParamsSpace(ParamExecData *paramExecVals, Bitmapset *params)
+{
+ int paramid;
+ Size sz = sizeof(int);
+ ParamExecData *prm;
+
+ if (params == NULL)
+ return sz;
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ Oid typeOid;
+ int16 typLen;
+ bool typByVal;
+
+ prm = &(paramExecVals[paramid]);
+ typeOid = prm->ptype;
+
+ sz = add_size(sz, sizeof(int)); /* space for paramid */
+ sz = add_size(sz, sizeof(Oid)); /* space for type OID */
+
+ /* space for datum/isnull */
+ if (OidIsValid(typeOid))
+ get_typlenbyval(typeOid, &typLen, &typByVal);
+ else
+ {
+ /* If no type OID, assume by-value, like copyParamList does. */
+ typLen = sizeof(Datum);
+ typByVal = true;
+ }
+ sz = add_size(sz,
+ datumEstimateSpace(prm->value, prm->isnull, typByVal, typLen));
+ }
+ return sz;
+}
+
+/*
+ * Serialize ParamExecData params corresponding to initplans.
+ *
+ * We write the number of parameters first, as a 4-byte integer, and then
+ * write details for each parameter in turn. The details for each parameter
+ * consist of a 4-byte paramid (location of param in execution time internal
+ * parameter array), 4-byte type OID, and then the datum as serialized by
+ * datumSerialize().
+ *
+ * The above format is quite similar to the format used to serialize
+ * paramListInfo structure, so if we change either format, then consider to
+ * change at both the places.
+ */
+void
+SerializeInitPlanParams(ParamExecData *paramExecVals, Bitmapset *params,
+ char **start_address)
+{
+ int nparams;
+ int paramid;
+ ParamExecData *prm;
+
+ if (params == NULL)
+ nparams = 0;
+ else
+ nparams = bms_num_members(params);
+ memcpy(*start_address, &nparams, sizeof(int));
+ *start_address += sizeof(int);
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ Oid typeOid;
+ int16 typLen;
+ bool typByVal;
+
+ prm = &(paramExecVals[paramid]);
+ typeOid = prm->ptype;
+
+ /* Write paramid. */
+ memcpy(*start_address, ¶mid, sizeof(int));
+ *start_address += sizeof(int);
+
+ /* Write OID. */
+ memcpy(*start_address, &typeOid, sizeof(Oid));
+ *start_address += sizeof(Oid);
+
+ /* space for datum/isnull */
+ if (OidIsValid(typeOid))
+ get_typlenbyval(typeOid, &typLen, &typByVal);
+ else
+ {
+ /* If no type OID, assume by-value, like copyParamList does. */
+ typLen = sizeof(Datum);
+ typByVal = true;
+ }
+ datumSerialize(prm->value, prm->isnull, typByVal, typLen,
+ start_address);
+ }
+}
+
+/*
+ * Restore ParamExecData params corresponding to initplans.
+ */
+void
+RestoreInitPlanParams(char **start_address, ParamExecData *params)
+{
+ int nparams;
+ int i;
+ int paramid;
+
+ memcpy(&nparams, *start_address, sizeof(int));
+ *start_address += sizeof(int);
+
+ for (i = 0; i < nparams; i++)
+ {
+ ParamExecData *prm;
+
+ /* Read paramid */
+ memcpy(¶mid, *start_address, sizeof(int));
+ *start_address += sizeof(int);
+ prm = ¶ms[paramid];
+
+ /* Read type OID. */
+ memcpy(&prm->ptype, *start_address, sizeof(Oid));
+ *start_address += sizeof(Oid);
+
+ /* Read datum/isnull. */
+ prm->value = datumRestore(start_address, &prm->isnull);
+ prm->execPlan = NULL;
+ }
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index bfc2ac1..6a0ca65 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -356,6 +356,7 @@ _copyGather(const Gather *from)
COPY_SCALAR_FIELD(num_workers);
COPY_SCALAR_FIELD(single_copy);
COPY_SCALAR_FIELD(invisible);
+ COPY_BITMAPSET_FIELD(initParam);
return newnode;
}
@@ -382,6 +383,7 @@ _copyGatherMerge(const GatherMerge *from)
COPY_POINTER_FIELD(sortOperators, from->numCols * sizeof(Oid));
COPY_POINTER_FIELD(collations, from->numCols * sizeof(Oid));
COPY_POINTER_FIELD(nullsFirst, from->numCols * sizeof(bool));
+ COPY_BITMAPSET_FIELD(initParam);
return newnode;
}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 7418fbe..7437a65 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -455,6 +455,7 @@ _outGather(StringInfo str, const Gather *node)
WRITE_INT_FIELD(num_workers);
WRITE_BOOL_FIELD(single_copy);
WRITE_BOOL_FIELD(invisible);
+ WRITE_BITMAPSET_FIELD(initParam);
}
static void
@@ -484,6 +485,8 @@ _outGatherMerge(StringInfo str, const GatherMerge *node)
appendStringInfoString(str, " :nullsFirst");
for (i = 0; i < node->numCols; i++)
appendStringInfo(str, " %s", booltostr(node->nullsFirst[i]));
+
+ WRITE_BITMAPSET_FIELD(initParam);
}
static void
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index d3bbc02..152bb1b 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2133,6 +2133,7 @@ _readGather(void)
READ_INT_FIELD(num_workers);
READ_BOOL_FIELD(single_copy);
READ_BOOL_FIELD(invisible);
+ READ_BITMAPSET_FIELD(initParam);
READ_DONE();
}
@@ -2153,6 +2154,7 @@ _readGatherMerge(void)
READ_OID_ARRAY(sortOperators, local_node->numCols);
READ_OID_ARRAY(collations, local_node->numCols);
READ_BOOL_ARRAY(nullsFirst, local_node->numCols);
+ READ_BITMAPSET_FIELD(initParam);
READ_DONE();
}
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index b263359..fb240b8 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -38,6 +38,7 @@
#include "optimizer/planner.h"
#include "optimizer/prep.h"
#include "optimizer/restrictinfo.h"
+#include "optimizer/subselect.h"
#include "optimizer/tlist.h"
#include "optimizer/var.h"
#include "parser/parse_clause.h"
@@ -2103,6 +2104,14 @@ generate_gather_paths(PlannerInfo *root, RelOptInfo *rel)
return;
/*
+ * We don't want to generate gather or gather merge node if there are
+ * initplans at some query level below the current query level as those
+ * plans could be parallel-unsafe or undirect correlated plans.
+ */
+ if (is_initplan_below_current_query_level(root))
+ return;
+
+ /*
* The output of Gather is always unsorted, so there's only one partial
* path of interest: the cheapest one. That will be the one at the front
* of partial_pathlist because of the way add_partial_path works.
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index d002e6d..3fe1e3b 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -6161,6 +6161,7 @@ make_gather(List *qptlist,
node->num_workers = nworkers;
node->single_copy = single_copy;
node->invisible = false;
+ node->initParam = NULL;
return node;
}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 02286d9..8767e69 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -331,6 +331,14 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
{
Gather *gather = makeNode(Gather);
+ /*
+ * If there are any initPlans attached to the formerly-top plan node,
+ * move them up to the Gather node; same as we do for Material node
+ * in materialize_finished_plan.
+ */
+ gather->plan.initPlan = top_plan->initPlan;
+ top_plan->initPlan = NIL;
+
gather->plan.targetlist = top_plan->targetlist;
gather->plan.qual = NIL;
gather->plan.lefttree = top_plan;
@@ -3441,6 +3449,14 @@ create_grouping_paths(PlannerInfo *root,
/* Insufficient support for partial mode. */
try_parallel_aggregation = false;
}
+ else if (is_initplan_below_current_query_level(root))
+ {
+ /*
+ * Don't parallelize the plan if there is an initplan below current
+ * query level. See generate_gather_paths() for detailed reason.
+ */
+ try_parallel_aggregation = false;
+ }
else
{
/* Everything looks good. */
@@ -4357,9 +4373,13 @@ create_ordered_paths(PlannerInfo *root,
* above will have handled those as well. However, there's one more
* possibility: it may make sense to sort the cheapest partial path
* according to the required output order and then use Gather Merge.
+ *
+ * Don't parallelize the plan if there is an initplan below current
+ * query level. See generate_gather_paths() for detailed reason.
*/
if (ordered_rel->consider_parallel && root->sort_pathkeys != NIL &&
- input_rel->partial_pathlist != NIL)
+ input_rel->partial_pathlist != NIL &&
+ !is_initplan_below_current_query_level(root))
{
Path *cheapest_partial_path;
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 5f3027e..4ee8da8 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -104,6 +104,7 @@ static Node *fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context);
static bool fix_scan_expr_walker(Node *node, fix_scan_expr_context *context);
static void set_join_references(PlannerInfo *root, Join *join, int rtoffset);
static void set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset);
+static void set_param_references(PlannerInfo *root, Plan *plan);
static Node *convert_combining_aggrefs(Node *node, void *context);
static void set_dummy_tlist_references(Plan *plan, int rtoffset);
static indexed_tlist *build_tlist_index(List *tlist);
@@ -617,7 +618,10 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
case T_Gather:
case T_GatherMerge:
- set_upper_references(root, plan, rtoffset);
+ {
+ set_upper_references(root, plan, rtoffset);
+ set_param_references(root, plan);
+ }
break;
case T_Hash:
@@ -1740,6 +1744,47 @@ set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset)
}
/*
+ * set_param_references
+ * Initialize the initParam list in gather or gather merge node such that
+ * it contains reference of all the params that needs to be evaluated
+ * before execution of the node. It contains the initplan params that are
+ * being passed to the plan nodes below it.
+ */
+static void
+set_param_references(PlannerInfo *root, Plan *plan)
+{
+ Assert(IsA(plan, Gather) || IsA(plan, GatherMerge));
+
+ if (plan->lefttree->extParam)
+ {
+ PlannerInfo *proot;
+ Bitmapset *initSetParam = NULL;
+ ListCell *l;
+
+ for (proot = root; proot != NULL; proot = proot->parent_root)
+ {
+ foreach(l, proot->init_plans)
+ {
+ SubPlan *initsubplan = (SubPlan *) lfirst(l);
+ ListCell *l2;
+
+ foreach(l2, initsubplan->setParam)
+ {
+ initSetParam = bms_add_member(initSetParam, lfirst_int(l2));
+ }
+ }
+ }
+
+ if (IsA(plan, Gather))
+ ((Gather *) plan)->initParam = bms_intersect(plan->lefttree->extParam, initSetParam);
+ else if (IsA(plan, GatherMerge))
+ ((GatherMerge *) plan)->initParam = bms_intersect(plan->lefttree->extParam, initSetParam);
+ else
+ elog(ERROR, "unrecognized node type: %d", nodeTag(plan));
+ }
+}
+
+/*
* Recursively scan an expression tree and convert Aggrefs to the proper
* intermediate form for combining aggregates. This means (1) replacing each
* one's argument list with a single argument that is the original Aggref
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 6fa6540..a4527ff 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -2136,13 +2136,11 @@ SS_identify_outer_params(PlannerInfo *root)
}
/*
- * SS_charge_for_initplans - account for initplans in Path costs & parallelism
+ * SS_charge_for_initplans - account for initplans in Path costs
*
* If any initPlans have been created in the current query level, they will
* get attached to the Plan tree created from whichever Path we select from
- * the given rel. Increment all that rel's Paths' costs to account for them,
- * and make sure the paths get marked as parallel-unsafe, since we can't
- * currently transmit initPlans to parallel workers.
+ * the given rel. Increment all that rel's Paths' costs to account for them.
*
* This is separate from SS_attach_initplans because we might conditionally
* create more initPlans during create_plan(), depending on which Path we
@@ -2174,7 +2172,7 @@ SS_charge_for_initplans(PlannerInfo *root, RelOptInfo *final_rel)
}
/*
- * Now adjust the costs and parallel_safe flags.
+ * Now adjust the costs.
*/
foreach(lc, final_rel->pathlist)
{
@@ -2182,7 +2180,6 @@ SS_charge_for_initplans(PlannerInfo *root, RelOptInfo *final_rel)
path->startup_cost += initplan_cost;
path->total_cost += initplan_cost;
- path->parallel_safe = false;
}
/* We needn't do set_cheapest() here, caller will do it */
@@ -2932,3 +2929,29 @@ SS_make_initplan_from_plan(PlannerInfo *root,
/* Set costs of SubPlan using info from the plan tree */
cost_subplan(subroot, node, plan);
}
+
+/*
+ * is_initplan_below_current_query_level - is there any initplan present below
+ * current query level.
+ */
+bool
+is_initplan_below_current_query_level(PlannerInfo *root)
+{
+ ListCell *lc;
+
+ /*
+ * If the subplan corresponding to the subroot is an initPlan, it'll
+ * be attached to its parent root. Hence, we check the query level
+ * of its parent root and if any init_plans are attached there.
+ */
+ foreach(lc, root->glob->subroots)
+ {
+ PlannerInfo *subroot = (PlannerInfo *) lfirst(lc);
+ PlannerInfo *proot = subroot->parent_root;
+
+ if (proot->query_level > root->query_level && proot->init_plans)
+ return true;
+ }
+
+ return false;
+}
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index b19380e..10cb255 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -91,6 +91,7 @@ typedef struct
typedef struct
{
+ PlannerInfo *root;
char max_hazard; /* worst proparallel hazard found so far */
char max_interesting; /* worst proparallel hazard of interest */
} max_parallel_hazard_context;
@@ -1054,6 +1055,7 @@ max_parallel_hazard(Query *parse)
{
max_parallel_hazard_context context;
+ context.root = NULL;
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_UNSAFE;
(void) max_parallel_hazard_walker((Node *) parse, &context);
@@ -1082,6 +1084,7 @@ is_parallel_safe(PlannerInfo *root, Node *node)
root->glob->nParamExec == 0)
return true;
/* Else use max_parallel_hazard's search logic, but stop on RESTRICTED */
+ context.root = root;
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_RESTRICTED;
return !max_parallel_hazard_walker(node, &context);
@@ -1176,13 +1179,40 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
return !((SubPlan *) node)->parallel_safe;
/*
- * We can't pass Params to workers at the moment either, so they are also
- * parallel-restricted.
+ * As of now, we can only pass initplan Params that refer to the same or
+ * parent query level. For the detailed reason, see generate_gather_paths
*/
else if (IsA(node, Param))
{
- if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
- return true;
+ int paramid;
+ PlannerInfo *root;
+
+ root = context->root;
+ paramid = ((Param *) node)->paramid;
+
+ if (root)
+ {
+ PlannerInfo *proot;
+ ListCell *l;
+
+ for (proot = root; proot != NULL; proot = proot->parent_root)
+ {
+ foreach(l, proot->init_plans)
+ {
+ SubPlan *initsubplan = (SubPlan *) lfirst(l);
+ ListCell *l2;
+
+ foreach(l2, initsubplan->setParam)
+ {
+ int initparam = lfirst_int(l2);
+ if (paramid == initparam)
+ return false;
+ }
+ }
+ }
+ }
+
+ return true;
}
/*
diff --git a/src/include/executor/execParallel.h b/src/include/executor/execParallel.h
index 8bc4270..5c42160 100644
--- a/src/include/executor/execParallel.h
+++ b/src/include/executor/execParallel.h
@@ -33,7 +33,7 @@ typedef struct ParallelExecutorInfo
} ParallelExecutorInfo;
extern ParallelExecutorInfo *ExecInitParallelPlan(PlanState *planstate,
- EState *estate, int nworkers);
+ EState *estate, int nworkers, Bitmapset *initParam);
extern void ExecParallelFinish(ParallelExecutorInfo *pei);
extern void ExecParallelCleanup(ParallelExecutorInfo *pei);
extern void ExecParallelReinitialize(ParallelExecutorInfo *pei);
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 02dbe7b..1c7e0b9 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -243,6 +243,7 @@ extern bool ExecShutdownNode(PlanState *node);
/*
* prototypes from functions in execQual.c
*/
+extern void ExecEvalParamExecParams(Bitmapset *params, EState *estate);
extern Datum GetAttributeByNum(HeapTupleHeader tuple, AttrNumber attrno,
bool *isNull);
extern Datum GetAttributeByName(HeapTupleHeader tuple, const char *attname,
diff --git a/src/include/executor/nodeSubplan.h b/src/include/executor/nodeSubplan.h
index 0f821dc..a59c671 100644
--- a/src/include/executor/nodeSubplan.h
+++ b/src/include/executor/nodeSubplan.h
@@ -24,4 +24,10 @@ extern void ExecReScanSetParamPlan(SubPlanState *node, PlanState *parent);
extern void ExecSetParamPlan(SubPlanState *node, ExprContext *econtext);
+extern Size EstimateInitPlanParamsSpace(ParamExecData *paramExecVals, Bitmapset *params);
+
+extern void SerializeInitPlanParams(ParamExecData *paramExecVals, Bitmapset *params, char **start_address);
+
+extern void RestoreInitPlanParams(char **start_address, ParamExecData *params);
+
#endif /* NODESUBPLAN_H */
diff --git a/src/include/nodes/params.h b/src/include/nodes/params.h
index e19ac24..b0732d2 100644
--- a/src/include/nodes/params.h
+++ b/src/include/nodes/params.h
@@ -98,6 +98,16 @@ typedef struct ParamExecData
{
void *execPlan; /* should be "SubPlanState *" */
Datum value;
+
+ /*
+ * parameter's datatype, or 0. This is required so that datum value can
+ * be read and used for other purposes like passing it to worker backend
+ * via shared memory. This is required only for initPlan's evaluation,
+ * however for consistency we set this for Subplan as well. We left it
+ * for other cases like CTE or RecursiveUnion cases where this structure
+ * is not used for evaluation of subplans.
+ */
+ Oid ptype;
bool isnull;
} ParamExecData;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index b880dc1..c56f4b0 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -795,6 +795,8 @@ typedef struct Gather
int num_workers;
bool single_copy;
bool invisible; /* suppress EXPLAIN display (for testing)? */
+ Bitmapset *initParam; /* param id's of initplans which are referred at gather or
+ * one of it's child node */
} Gather;
/* ------------
@@ -811,6 +813,8 @@ typedef struct GatherMerge
Oid *sortOperators; /* OIDs of operators to sort them by */
Oid *collations; /* OIDs of collations */
bool *nullsFirst; /* NULLS FIRST/LAST directions */
+ Bitmapset *initParam; /* param id's of initplans which are referred at gather
+ * merge or one of it's child node */
} GatherMerge;
/* ----------------
diff --git a/src/include/optimizer/subselect.h b/src/include/optimizer/subselect.h
index 56dc237..62ff0a9 100644
--- a/src/include/optimizer/subselect.h
+++ b/src/include/optimizer/subselect.h
@@ -35,6 +35,7 @@ extern Param *SS_make_initplan_output_param(PlannerInfo *root,
extern void SS_make_initplan_from_plan(PlannerInfo *root,
PlannerInfo *subroot, Plan *plan,
Param *prm);
+extern bool is_initplan_below_current_query_level(PlannerInfo *root);
extern Param *assign_nestloop_param_var(PlannerInfo *root, Var *var);
extern Param *assign_nestloop_param_placeholdervar(PlannerInfo *root,
PlaceHolderVar *phv);
On Tue, Mar 14, 2017 at 3:20 PM, Amit Kapila <amit.kapila16@gmail.com> wrote:
Based on that idea, I have modified the patch such that it will
compute the set of initplans Params that are required below gather
node and store them as bitmap of initplan params at gather node.
During set_plan_references, we can find the intersection of external
parameters that are required at Gather or nodes below it with the
initplans that are passed from same or above query level. Once the set
of initplan params are established, we evaluate those (if they are not
already evaluated) before execution of gather node and then pass the
computed value to each of the workers. To identify whether a
particular param is parallel safe or not, we check if the paramid of
the param exists in initplans at same or above query level. We don't
allow to generate gather path if there are initplans at some query
level below the current query level as those plans could be
parallel-unsafe or undirect correlated plans.
I would like to mention different test scenarios with InitPlans that
we've considered while developing and testing of the patch.
An InitPlan is a subselect that doesn't take any reference from its
immediate outer query level and it returns a param value. For example,
consider the following query:
QUERY PLAN
------------------------------
Seq Scan on t1
Filter: (k = $0)
allParams: $0
InitPlan 1 (returns $0)
-> Aggregate
-> Seq Scan on t3
In this case, the InitPlan is evaluated once when the filter is
checked for the first time. For subsequent checks, we need not
evaluate the initplan again since we already have the value. In our
approach, we parallelize the sequential scan by inserting a Gather
node on top of parallel sequential scan node. At the Gather node, we
evaluate the InitPlan before spawning the workers and pass this value
to the worker using dynamic shared memory. This yields the following
plan:
QUERY PLAN
---------------------------------------------------
Gather
Workers Planned: 2
Params Evaluated: $0
InitPlan 1 (returns $0)
-> Aggregate
-> Seq Scan on t3
-> Parallel Seq Scan on t1
Filter: (k = $0)
As Amit mentioned up in the thread, at a Gather node, we evaluate only
those InitPlans that are attached to this query level or any higher
one and are used under the Gather node. extParam at a node includes
the InitPlan params that should be passed from an outer node. I've
attached a patch to show extParams and allParams for each node. Here
is the output with that patch:
QUERY PLAN
---------------------------------------------------
Gather
Workers Planned: 2
Params Evaluated: $0
allParams: $0
InitPlan 1 (returns $0)
-> Finalize Aggregate
-> Gather
Workers Planned: 2
-> Partial Aggregate
-> Parallel Seq Scan on t3
-> Parallel Seq Scan on t1
Filter: (k = $0)
allParams: $0
extParams: $0
In this case, $0 is included in extParam of parallel sequential scan
and the InitPlan corresponding to this param is attached to the same
query level that contains the Gather node. Hence, we evaluate $0 at
Gather and pass it to workers.
But, for generating a plan like this requires marking an InitPlan
param as parallel_safe. We can't mark all params as parallel_safe
because of correlated subselects. Hence, in
max_parallel_hazard_walker, the only params marked safe are InitPlan
params from current or outer query level. An InitPlan param from inner
query level isn't marked safe since we can't evaluate this param at
any Gather node above the current node(where the param is used). As
mentioned by Amit, we also don't allow generation of gather path if
there are InitPlans at some query level below the current query level
as those plans could be parallel-unsafe or undirect correlated plans.
I've attached a script file and its output containing several
scenarios relevant to InitPlans. I've also attached the patch for
displaying extParam and allParam at each node. This patch can be
applied on top of pq_pushdown_initplan_v3.patch. Please find the
attachments.
This restricts some of the cases for parallelism like when initplans
are below gather node, but the patch looks better. We can open up
those cases if required in a separate patch.
+1. Unfortunately, this patch doesn't enable parallelism for all
possible cases with InitPlans. Our objective is to keep things simple
and clean. Still, TPC-H q22 runs 2.5~3 times faster with this patch.
--
Thanks & Regards,
Kuntal Ghosh
EnterpriseDB: http://www.enterprisedb.com
Attachments:
0002-Show-extParams-and-allParams-in-explain.patchbinary/octet-stream; name=0002-Show-extParams-and-allParams-in-explain.patchDownload
From b8e5d108188866c3a762a477e2ef175455542ee9 Mon Sep 17 00:00:00 2001
From: Kuntal Ghosh <kuntal.ghosh@enterprisedb.com>
Date: Fri, 10 Mar 2017 13:58:30 +0530
Subject: [PATCH] Show extParams and allParams in explain
---
src/backend/commands/explain.c | 46 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 46 insertions(+)
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 49e2543..c7186cf 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -106,6 +106,7 @@ static void show_tidbitmap_info(BitmapHeapScanState *planstate,
static void show_instrumentation_count(const char *qlabel, int which,
PlanState *planstate, ExplainState *es);
static void show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es);
+static void show_params(PlanState *plastate, ExplainState *es);
static void show_eval_params(Bitmapset *bms_params, ExplainState *es);
static const char *explain_get_index_name(Oid indexId);
static void show_buffer_usage(ExplainState *es, const BufferUsage *usage);
@@ -1605,6 +1606,10 @@ ExplainNode(PlanState *planstate, List *ancestors,
break;
}
+ /* Show params evaluated at a node */
+ show_params(planstate, es);
+
+
/* Show buffer usage */
if (es->buffers && planstate->instrument)
show_buffer_usage(es, &planstate->instrument->bufusage);
@@ -1771,6 +1776,47 @@ ExplainNode(PlanState *planstate, List *ancestors,
}
/*
+ * Show params at node.
+ */
+static void
+show_params(PlanState *planstate, ExplainState *es)
+{
+ if (planstate->plan->allParam)
+ {
+ int paramid = -1;
+ List *params = NIL;
+ Bitmapset *bms_params = planstate->plan->allParam;
+ while ((paramid = bms_next_member(bms_params, paramid)) >= 0)
+ {
+ char param[32];
+
+ snprintf(param, sizeof(param), "$%d", paramid);
+ params = lappend(params, pstrdup(param));
+ }
+
+ if (params)
+ ExplainPropertyList("allParams", params, es);
+ }
+
+ if (planstate->plan->extParam)
+ {
+ int paramid = -1;
+ List *params = NIL;
+ Bitmapset *bms_params = planstate->plan->extParam;
+ while ((paramid = bms_next_member(bms_params, paramid)) >= 0)
+ {
+ char param[32];
+
+ snprintf(param, sizeof(param), "$%d", paramid);
+ params = lappend(params, pstrdup(param));
+ }
+
+ if (params)
+ ExplainPropertyList("extParams", params, es);
+ }
+}
+
+/*
* Show the targetlist of a plan node
*/
static void
--
1.8.3.1
On Thu, Mar 16, 2017 at 2:34 AM, Kuntal Ghosh
<kuntalghosh.2007@gmail.com> wrote:
On Tue, Mar 14, 2017 at 3:20 PM, Amit Kapila <amit.kapila16@gmail.com> wrote:
Based on that idea, I have modified the patch such that it will
compute the set of initplans Params that are required below gather
node and store them as bitmap of initplan params at gather node.
During set_plan_references, we can find the intersection of external
parameters that are required at Gather or nodes below it with the
initplans that are passed from same or above query level. Once the set
of initplan params are established, we evaluate those (if they are not
already evaluated) before execution of gather node and then pass the
computed value to each of the workers. To identify whether a
particular param is parallel safe or not, we check if the paramid of
the param exists in initplans at same or above query level. We don't
allow to generate gather path if there are initplans at some query
level below the current query level as those plans could be
parallel-unsafe or undirect correlated plans.I would like to mention different test scenarios with InitPlans that
we've considered while developing and testing of the patch.
Thanks a lot Kuntal for sharing different test scenarios.
Unfortunately, this patch doesn't received any review till now, so
there is no chance of making it in to PostgreSQL-10. I have moved
this to next CF.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Mar 28, 2017 at 7:25 AM, Amit Kapila <amit.kapila16@gmail.com> wrote:
On Thu, Mar 16, 2017 at 2:34 AM, Kuntal Ghosh
<kuntalghosh.2007@gmail.com> wrote:On Tue, Mar 14, 2017 at 3:20 PM, Amit Kapila <amit.kapila16@gmail.com> wrote:
Based on that idea, I have modified the patch such that it will
compute the set of initplans Params that are required below gather
node and store them as bitmap of initplan params at gather node.
During set_plan_references, we can find the intersection of external
parameters that are required at Gather or nodes below it with the
initplans that are passed from same or above query level. Once the set
of initplan params are established, we evaluate those (if they are not
already evaluated) before execution of gather node and then pass the
computed value to each of the workers. To identify whether a
particular param is parallel safe or not, we check if the paramid of
the param exists in initplans at same or above query level. We don't
allow to generate gather path if there are initplans at some query
level below the current query level as those plans could be
parallel-unsafe or undirect correlated plans.I would like to mention different test scenarios with InitPlans that
we've considered while developing and testing of the patch.Thanks a lot Kuntal for sharing different test scenarios.
Unfortunately, this patch doesn't received any review till now, so
there is no chance of making it in to PostgreSQL-10. I have moved
this to next CF.
Attached is a rebased version of the patch with below changes:
a. SubplanState now directly stores Subplan rather than ExprState, so
patch needs some adjustment in that regard.
b. While rejecting the paths (based on if there are initplans at level
below the current query level) for parallelism, the rejected paths
were not marked as parallel unsafe. Due to this in
force_parallel_mode=regress, we were able to add gather node above
parallel unsafe paths. The modified patch ensures to mark such paths
as parallel unsafe.
c. Added regression test.
d. Improve comments in the code.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
pq_pushdown_initplan_v4.patchapplication/octet-stream; name=pq_pushdown_initplan_v4.patchDownload
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 7648201..83a26ec 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -107,6 +107,7 @@ static void show_tidbitmap_info(BitmapHeapScanState *planstate,
static void show_instrumentation_count(const char *qlabel, int which,
PlanState *planstate, ExplainState *es);
static void show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es);
+static void show_eval_params(Bitmapset *bms_params, ExplainState *es);
static const char *explain_get_index_name(Oid indexId);
static void show_buffer_usage(ExplainState *es, const BufferUsage *usage);
static void ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir,
@@ -637,7 +638,17 @@ ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc)
*/
ps = queryDesc->planstate;
if (IsA(ps, GatherState) &&((Gather *) ps->plan)->invisible)
+ {
+ List *initPlanState = NULL;
+ PlanState *save_ps;
+
+ /* initplans are always attached to the top node (cf standard_planner) */
+ save_ps = ps;
+ initPlanState = ps->initPlan;
ps = outerPlanState(ps);
+ ps->initPlan = initPlanState;
+ save_ps->initPlan = NIL;
+ }
ExplainNode(ps, NIL, NULL, NULL, es);
}
@@ -1434,6 +1445,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
planstate, es);
ExplainPropertyInteger("Workers Planned",
gather->num_workers, es);
+
+ /* Show params evaluated at gather node */
+ if (gather->initParam)
+ show_eval_params(gather->initParam, es);
+
if (es->analyze)
{
int nworkers;
@@ -1456,6 +1472,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
planstate, es);
ExplainPropertyInteger("Workers Planned",
gm->num_workers, es);
+
+ /* Show params evaluated at gather-merge node */
+ if (gm->initParam)
+ show_eval_params(gm->initParam, es);
+
if (es->analyze)
{
int nworkers;
@@ -2430,6 +2451,28 @@ show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es)
}
/*
+ * Show initplan params evaluated at gather or gather merge node.
+ */
+static void
+show_eval_params(Bitmapset *bms_params, ExplainState *es)
+{
+ int paramid = -1;
+ List *params = NIL;
+
+ Assert(bms_params);
+
+ while ((paramid = bms_next_member(bms_params, paramid)) >= 0)
+ {
+ char param[32];
+
+ snprintf(param, sizeof(param), "$%d", paramid);
+ params = lappend(params, pstrdup(param));
+ }
+
+ ExplainPropertyList("Params Evaluated", params, es);
+}
+
+/*
* Fetch the name of an index in an EXPLAIN
*
* We allow plugins to get control here so that plans involving hypothetical
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index f2a52f62..ed1449d 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -1861,6 +1861,33 @@ ExecEvalParamExec(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
}
/*
+ * ExecEvalParamExecParams
+ *
+ * Execute the subplan stored in PARAM_EXEC initplans params, if not executed
+ * till now.
+ */
+void
+ExecEvalParamExecParams(Bitmapset *params, EState *estate)
+{
+ ParamExecData *prm;
+ int paramid;
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ prm = &(estate->es_param_exec_vals[paramid]);
+
+ if (prm->execPlan != NULL)
+ {
+ /* Parameter not evaluated yet, so go do it */
+ ExecSetParamPlan(prm->execPlan, GetPerTupleExprContext(estate));
+ /* ExecSetParamPlan should have processed this param... */
+ Assert(prm->execPlan == NULL);
+ }
+ }
+}
+
+/*
* Evaluate a PARAM_EXTERN parameter.
*
* PARAM_EXTERN parameters must be sought in ecxt_param_list_info.
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index ce47f1d..d127031 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -23,12 +23,14 @@
#include "postgres.h"
+#include "executor/execExpr.h"
#include "executor/execParallel.h"
#include "executor/executor.h"
#include "executor/nodeBitmapHeapscan.h"
#include "executor/nodeCustom.h"
#include "executor/nodeForeignscan.h"
#include "executor/nodeSeqscan.h"
+#include "executor/nodeSubplan.h"
#include "executor/nodeIndexscan.h"
#include "executor/nodeIndexonlyscan.h"
#include "executor/tqueue.h"
@@ -54,6 +56,7 @@
#define PARALLEL_KEY_INSTRUMENTATION UINT64CONST(0xE000000000000005)
#define PARALLEL_KEY_DSA UINT64CONST(0xE000000000000006)
#define PARALLEL_KEY_QUERY_TEXT UINT64CONST(0xE000000000000007)
+#define PARALLEL_KEY_INITPLAN_PARAMS UINT64CONST(0xE000000000000008)
#define PARALLEL_TUPLE_QUEUE_SIZE 65536
@@ -381,7 +384,8 @@ ExecParallelReinitialize(ParallelExecutorInfo *pei)
* execution and return results to the main backend.
*/
ParallelExecutorInfo *
-ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers)
+ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
+ Bitmapset *initParam)
{
ParallelExecutorInfo *pei;
ParallelContext *pcxt;
@@ -390,10 +394,12 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers)
char *pstmt_data;
char *pstmt_space;
char *param_space;
+ char *initplan_param_space;
BufferUsage *bufusage_space;
SharedExecutorInstrumentation *instrumentation = NULL;
int pstmt_len;
int param_len;
+ int initplan_param_len;
int instrumentation_len = 0;
int instrument_offset = 0;
Size dsa_minsize = dsa_minimum_size();
@@ -405,6 +411,8 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers)
pei->finished = false;
pei->planstate = planstate;
+ ExecEvalParamExecParams(initParam, estate);
+
/* Fix up and serialize plan to be sent to workers. */
pstmt_data = ExecSerializePlan(planstate->plan, estate);
@@ -433,6 +441,11 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers)
shm_toc_estimate_chunk(&pcxt->estimator, param_len);
shm_toc_estimate_keys(&pcxt->estimator, 1);
+ /* Estimate space for initplan params. */
+ initplan_param_len = EstimateInitPlanParamsSpace(estate->es_param_exec_vals, initParam);
+ shm_toc_estimate_chunk(&pcxt->estimator, initplan_param_len);
+ shm_toc_estimate_keys(&pcxt->estimator, 1);
+
/*
* Estimate space for BufferUsage.
*
@@ -502,6 +515,11 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers)
shm_toc_insert(pcxt->toc, PARALLEL_KEY_PARAMS, param_space);
SerializeParamList(estate->es_param_list_info, ¶m_space);
+ /* Store serialized initplan params. */
+ initplan_param_space = shm_toc_allocate(pcxt->toc, initplan_param_len);
+ shm_toc_insert(pcxt->toc, PARALLEL_KEY_INITPLAN_PARAMS, initplan_param_space);
+ SerializeInitPlanParams(estate->es_param_exec_vals, initParam, &initplan_param_space);
+
/* Allocate space for each worker's BufferUsage; no need to initialize. */
bufusage_space = shm_toc_allocate(pcxt->toc,
mul_size(sizeof(BufferUsage), pcxt->nworkers));
@@ -692,6 +710,21 @@ ExecParallelGetReceiver(dsm_segment *seg, shm_toc *toc)
}
/*
+ * Copy the ParamExecData params corresponding to initplans from dynamic
+ * shared memory. This has to be done once the params are allocated by
+ * executor; that is after ExecutorStart().
+ */
+static void
+ExecParallelInitializeInitPlanParams(shm_toc *toc, ParamExecData *params)
+{
+ char *paramspace;
+
+ /* Reconstruct initplan params. */
+ paramspace = shm_toc_lookup(toc, PARALLEL_KEY_INITPLAN_PARAMS, false);
+ RestoreInitPlanParams(¶mspace, params);
+}
+
+/*
* Create a QueryDesc for the PlannedStmt we are to execute, and return it.
*/
static QueryDesc *
@@ -866,6 +899,7 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
/* Special executor initialization steps for parallel workers */
queryDesc->planstate->state->es_query_dsa = area;
+ ExecParallelInitializeInitPlanParams(toc, queryDesc->estate->es_param_exec_vals);
ExecParallelInitializeWorker(queryDesc->planstate, toc);
/* Run the plan */
diff --git a/src/backend/executor/nodeGather.c b/src/backend/executor/nodeGather.c
index f83cd58..3027c83 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -151,7 +151,8 @@ ExecGather(GatherState *node)
if (!node->pei)
node->pei = ExecInitParallelPlan(node->ps.lefttree,
estate,
- gather->num_workers);
+ gather->num_workers,
+ gather->initParam);
/*
* Register backend workers. We might not get as many as we
diff --git a/src/backend/executor/nodeGatherMerge.c b/src/backend/executor/nodeGatherMerge.c
index 80ee1fc..f8dd31f 100644
--- a/src/backend/executor/nodeGatherMerge.c
+++ b/src/backend/executor/nodeGatherMerge.c
@@ -185,7 +185,8 @@ ExecGatherMerge(GatherMergeState *node)
if (!node->pei)
node->pei = ExecInitParallelPlan(node->ps.lefttree,
estate,
- gm->num_workers);
+ gm->num_workers,
+ gm->initParam);
/* Try to launch workers. */
pcxt = node->pei->pcxt;
diff --git a/src/backend/executor/nodeNestloop.c b/src/backend/executor/nodeNestloop.c
index 0065fe6..23a9102 100644
--- a/src/backend/executor/nodeNestloop.c
+++ b/src/backend/executor/nodeNestloop.c
@@ -126,6 +126,7 @@ ExecNestLoop(NestLoopState *node)
{
NestLoopParam *nlp = (NestLoopParam *) lfirst(lc);
int paramno = nlp->paramno;
+ TupleDesc tdesc = outerTupleSlot->tts_tupleDescriptor;
ParamExecData *prm;
prm = &(econtext->ecxt_param_exec_vals[paramno]);
@@ -136,6 +137,7 @@ ExecNestLoop(NestLoopState *node)
prm->value = slot_getattr(outerTupleSlot,
nlp->paramval->varattno,
&(prm->isnull));
+ prm->ptype = tdesc->attrs[nlp->paramval->varattno - 1]->atttypid;
/* Flag parameter value as changed */
innerPlan->chgParam = bms_add_member(innerPlan->chgParam,
paramno);
diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c
index e8fa4c8..49c1661 100644
--- a/src/backend/executor/nodeSubplan.c
+++ b/src/backend/executor/nodeSubplan.c
@@ -30,11 +30,15 @@
#include <math.h>
#include "access/htup_details.h"
+#include "catalog/pg_type.h"
#include "executor/executor.h"
#include "executor/nodeSubplan.h"
#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
+#include "storage/shmem.h"
#include "utils/array.h"
+#include "utils/datum.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
@@ -270,11 +274,13 @@ ExecScanSubPlan(SubPlanState *node,
forboth(l, subplan->parParam, pvar, node->args)
{
int paramid = lfirst_int(l);
+ ExprState *exprstate = (ExprState *) lfirst(pvar);
ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]);
- prm->value = ExecEvalExprSwitchContext((ExprState *) lfirst(pvar),
+ prm->value = ExecEvalExprSwitchContext(exprstate,
econtext,
&(prm->isnull));
+ prm->ptype = exprType((Node *) exprstate->expr);
planstate->chgParam = bms_add_member(planstate->chgParam, paramid);
}
@@ -387,6 +393,7 @@ ExecScanSubPlan(SubPlanState *node,
prmdata = &(econtext->ecxt_param_exec_vals[paramid]);
Assert(prmdata->execPlan == NULL);
prmdata->value = slot_getattr(slot, col, &(prmdata->isnull));
+ prmdata->ptype = tdesc->attrs[col - 1]->atttypid;
col++;
}
@@ -553,11 +560,13 @@ buildSubPlanHash(SubPlanState *node, ExprContext *econtext)
{
int paramid = lfirst_int(plst);
ParamExecData *prmdata;
+ TupleDesc tdesc = slot->tts_tupleDescriptor;
prmdata = &(innerecontext->ecxt_param_exec_vals[paramid]);
Assert(prmdata->execPlan == NULL);
prmdata->value = slot_getattr(slot, col,
&(prmdata->isnull));
+ prmdata->ptype = tdesc->attrs[col - 1]->atttypid;
col++;
}
slot = ExecProject(node->projRight);
@@ -919,6 +928,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
ListCell *l;
bool found = false;
ArrayBuildStateAny *astate = NULL;
+ Oid ptype;
if (subLinkType == ANY_SUBLINK ||
subLinkType == ALL_SUBLINK)
@@ -926,6 +936,8 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
if (subLinkType == CTE_SUBLINK)
elog(ERROR, "CTE subplans should not be executed via ExecSetParamPlan");
+ ptype = exprType((Node *) node->subplan);
+
/* Initialize ArrayBuildStateAny in caller's context, if needed */
if (subLinkType == ARRAY_SUBLINK)
astate = initArrayResultAny(subplan->firstColType,
@@ -948,11 +960,13 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
forboth(l, subplan->parParam, pvar, node->args)
{
int paramid = lfirst_int(l);
+ ExprState *exprstate = (ExprState *) lfirst(pvar);
ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]);
- prm->value = ExecEvalExprSwitchContext((ExprState *) lfirst(pvar),
+ prm->value = ExecEvalExprSwitchContext(exprstate,
econtext,
&(prm->isnull));
+ prm->ptype = exprType((Node *) exprstate->expr);
planstate->chgParam = bms_add_member(planstate->chgParam, paramid);
}
@@ -975,6 +989,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = BoolGetDatum(true);
+ prm->ptype = ptype;
prm->isnull = false;
found = true;
break;
@@ -1026,6 +1041,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = heap_getattr(node->curTuple, i, tdesc,
&(prm->isnull));
+ prm->ptype = tdesc->attrs[i - 1]->atttypid;
i++;
}
}
@@ -1048,6 +1064,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
true);
prm->execPlan = NULL;
prm->value = node->curArray;
+ prm->ptype = ptype;
prm->isnull = false;
}
else if (!found)
@@ -1060,6 +1077,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = BoolGetDatum(false);
+ prm->ptype = ptype;
prm->isnull = false;
}
else
@@ -1072,6 +1090,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = (Datum) 0;
+ prm->ptype = VOIDOID;
prm->isnull = true;
}
}
@@ -1202,3 +1221,136 @@ ExecAlternativeSubPlan(AlternativeSubPlanState *node,
return ExecSubPlan(activesp, econtext, isNull);
}
+
+/*
+ * Estimate the amount of space required to serialize the InitPlan params.
+ */
+Size
+EstimateInitPlanParamsSpace(ParamExecData *paramExecVals, Bitmapset *params)
+{
+ int paramid;
+ Size sz = sizeof(int);
+ ParamExecData *prm;
+
+ if (params == NULL)
+ return sz;
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ Oid typeOid;
+ int16 typLen;
+ bool typByVal;
+
+ prm = &(paramExecVals[paramid]);
+ typeOid = prm->ptype;
+
+ sz = add_size(sz, sizeof(int)); /* space for paramid */
+ sz = add_size(sz, sizeof(Oid)); /* space for type OID */
+
+ /* space for datum/isnull */
+ if (OidIsValid(typeOid))
+ get_typlenbyval(typeOid, &typLen, &typByVal);
+ else
+ {
+ /* If no type OID, assume by-value, like copyParamList does. */
+ typLen = sizeof(Datum);
+ typByVal = true;
+ }
+ sz = add_size(sz,
+ datumEstimateSpace(prm->value, prm->isnull, typByVal, typLen));
+ }
+ return sz;
+}
+
+/*
+ * Serialize ParamExecData params corresponding to initplans.
+ *
+ * We write the number of parameters first, as a 4-byte integer, and then
+ * write details for each parameter in turn. The details for each parameter
+ * consist of a 4-byte paramid (location of param in execution time internal
+ * parameter array), 4-byte type OID, and then the datum as serialized by
+ * datumSerialize().
+ *
+ * The above format is quite similar to the format used to serialize
+ * paramListInfo structure, so if we change either format, then consider to
+ * change at both the places.
+ */
+void
+SerializeInitPlanParams(ParamExecData *paramExecVals, Bitmapset *params,
+ char **start_address)
+{
+ int nparams;
+ int paramid;
+ ParamExecData *prm;
+
+ if (params == NULL)
+ nparams = 0;
+ else
+ nparams = bms_num_members(params);
+ memcpy(*start_address, &nparams, sizeof(int));
+ *start_address += sizeof(int);
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ Oid typeOid;
+ int16 typLen;
+ bool typByVal;
+
+ prm = &(paramExecVals[paramid]);
+ typeOid = prm->ptype;
+
+ /* Write paramid. */
+ memcpy(*start_address, ¶mid, sizeof(int));
+ *start_address += sizeof(int);
+
+ /* Write OID. */
+ memcpy(*start_address, &typeOid, sizeof(Oid));
+ *start_address += sizeof(Oid);
+
+ /* space for datum/isnull */
+ if (OidIsValid(typeOid))
+ get_typlenbyval(typeOid, &typLen, &typByVal);
+ else
+ {
+ /* If no type OID, assume by-value, like copyParamList does. */
+ typLen = sizeof(Datum);
+ typByVal = true;
+ }
+ datumSerialize(prm->value, prm->isnull, typByVal, typLen,
+ start_address);
+ }
+}
+
+/*
+ * Restore ParamExecData params corresponding to initplans.
+ */
+void
+RestoreInitPlanParams(char **start_address, ParamExecData *params)
+{
+ int nparams;
+ int i;
+ int paramid;
+
+ memcpy(&nparams, *start_address, sizeof(int));
+ *start_address += sizeof(int);
+
+ for (i = 0; i < nparams; i++)
+ {
+ ParamExecData *prm;
+
+ /* Read paramid */
+ memcpy(¶mid, *start_address, sizeof(int));
+ *start_address += sizeof(int);
+ prm = ¶ms[paramid];
+
+ /* Read type OID. */
+ memcpy(&prm->ptype, *start_address, sizeof(Oid));
+ *start_address += sizeof(Oid);
+
+ /* Read datum/isnull. */
+ prm->value = datumRestore(start_address, &prm->isnull);
+ prm->execPlan = NULL;
+ }
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 67ac814..29594d3 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -363,6 +363,7 @@ _copyGather(const Gather *from)
COPY_SCALAR_FIELD(num_workers);
COPY_SCALAR_FIELD(single_copy);
COPY_SCALAR_FIELD(invisible);
+ COPY_BITMAPSET_FIELD(initParam);
return newnode;
}
@@ -389,6 +390,7 @@ _copyGatherMerge(const GatherMerge *from)
COPY_POINTER_FIELD(sortOperators, from->numCols * sizeof(Oid));
COPY_POINTER_FIELD(collations, from->numCols * sizeof(Oid));
COPY_POINTER_FIELD(nullsFirst, from->numCols * sizeof(bool));
+ COPY_BITMAPSET_FIELD(initParam);
return newnode;
}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 21e39a0..c90200c 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -481,6 +481,7 @@ _outGather(StringInfo str, const Gather *node)
WRITE_INT_FIELD(num_workers);
WRITE_BOOL_FIELD(single_copy);
WRITE_BOOL_FIELD(invisible);
+ WRITE_BITMAPSET_FIELD(initParam);
}
static void
@@ -510,6 +511,8 @@ _outGatherMerge(StringInfo str, const GatherMerge *node)
appendStringInfoString(str, " :nullsFirst");
for (i = 0; i < node->numCols; i++)
appendStringInfo(str, " %s", booltostr(node->nullsFirst[i]));
+
+ WRITE_BITMAPSET_FIELD(initParam);
}
static void
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 8ab09d7..82c944a 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2165,6 +2165,7 @@ _readGather(void)
READ_INT_FIELD(num_workers);
READ_BOOL_FIELD(single_copy);
READ_BOOL_FIELD(invisible);
+ READ_BITMAPSET_FIELD(initParam);
READ_DONE();
}
@@ -2185,6 +2186,7 @@ _readGatherMerge(void)
READ_OID_ARRAY(sortOperators, local_node->numCols);
READ_OID_ARRAY(collations, local_node->numCols);
READ_BOOL_ARRAY(nullsFirst, local_node->numCols);
+ READ_BITMAPSET_FIELD(initParam);
READ_DONE();
}
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index f087ddb..d1c6e1f 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -38,6 +38,7 @@
#include "optimizer/planner.h"
#include "optimizer/prep.h"
#include "optimizer/restrictinfo.h"
+#include "optimizer/subselect.h"
#include "optimizer/tlist.h"
#include "optimizer/var.h"
#include "parser/parse_clause.h"
@@ -2202,6 +2203,31 @@ generate_gather_paths(PlannerInfo *root, RelOptInfo *rel)
Path *simple_gather_path;
ListCell *lc;
+ /*
+ * We don't want to generate gather or gather merge node if there are
+ * initplans at some query level below the current query level as those
+ * plans could be parallel-unsafe or undirect correlated plans. Ensure to
+ * mark all the partial and non-partial paths for a relation at this level
+ * to be parallel-unsafe.
+ */
+ if (is_initplan_below_current_query_level(root))
+ {
+ foreach(lc, rel->partial_pathlist)
+ {
+ Path *subpath = (Path *) lfirst(lc);
+
+ subpath->parallel_safe = false;
+ }
+
+ foreach(lc, rel->pathlist)
+ {
+ Path *subpath = (Path *) lfirst(lc);
+
+ subpath->parallel_safe = false;
+ }
+ return;
+ }
+
/* If there are no partial paths, there's nothing to do here. */
if (rel->partial_pathlist == NIL)
return;
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index e589d92..60bb41b 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -6250,6 +6250,7 @@ make_gather(List *qptlist,
node->num_workers = nworkers;
node->single_copy = single_copy;
node->invisible = false;
+ node->initParam = NULL;
return node;
}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 2988c11..3f7986a 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -358,6 +358,14 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
{
Gather *gather = makeNode(Gather);
+ /*
+ * If there are any initPlans attached to the formerly-top plan node,
+ * move them up to the Gather node; same as we do for Material node in
+ * materialize_finished_plan.
+ */
+ gather->plan.initPlan = top_plan->initPlan;
+ top_plan->initPlan = NIL;
+
gather->plan.targetlist = top_plan->targetlist;
gather->plan.qual = NIL;
gather->plan.lefttree = top_plan;
@@ -3524,6 +3532,29 @@ create_grouping_paths(PlannerInfo *root,
ListCell *lc;
+ /*
+ * Don't parallelize the plan if there is an initplan below current query
+ * level. See generate_gather_paths() for detailed reason.
+ */
+ if (is_initplan_below_current_query_level(root))
+ {
+ foreach(lc, input_rel->partial_pathlist)
+ {
+ Path *subpath = (Path *) lfirst(lc);
+
+ subpath->parallel_safe = false;
+ }
+
+ foreach(lc, input_rel->pathlist)
+ {
+ Path *subpath = (Path *) lfirst(lc);
+
+ subpath->parallel_safe = false;
+ }
+
+ input_rel->consider_parallel = false;
+ }
+
/* For now, do all work in the (GROUP_AGG, NULL) upperrel */
grouped_rel = fetch_upper_rel(root, UPPERREL_GROUP_AGG, NULL);
@@ -4886,6 +4917,29 @@ create_ordered_paths(PlannerInfo *root,
RelOptInfo *ordered_rel;
ListCell *lc;
+ /*
+ * Don't parallelize the plan if there is an initplan below current query
+ * level. See generate_gather_paths() for detailed reason.
+ */
+ if (is_initplan_below_current_query_level(root))
+ {
+ foreach(lc, input_rel->partial_pathlist)
+ {
+ Path *subpath = (Path *) lfirst(lc);
+
+ subpath->parallel_safe = false;
+ }
+
+ foreach(lc, input_rel->pathlist)
+ {
+ Path *subpath = (Path *) lfirst(lc);
+
+ subpath->parallel_safe = false;
+ }
+
+ input_rel->consider_parallel = false;
+ }
+
/* For now, do all work in the (ORDERED, NULL) upperrel */
ordered_rel = fetch_upper_rel(root, UPPERREL_ORDERED, NULL);
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index b0c9e94..2704a48 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -104,6 +104,7 @@ static Node *fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context);
static bool fix_scan_expr_walker(Node *node, fix_scan_expr_context *context);
static void set_join_references(PlannerInfo *root, Join *join, int rtoffset);
static void set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset);
+static void set_param_references(PlannerInfo *root, Plan *plan);
static Node *convert_combining_aggrefs(Node *node, void *context);
static void set_dummy_tlist_references(Plan *plan, int rtoffset);
static indexed_tlist *build_tlist_index(List *tlist);
@@ -628,7 +629,10 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
case T_Gather:
case T_GatherMerge:
- set_upper_references(root, plan, rtoffset);
+ {
+ set_upper_references(root, plan, rtoffset);
+ set_param_references(root, plan);
+ }
break;
case T_Hash:
@@ -1784,6 +1788,47 @@ set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset)
}
/*
+ * set_param_references
+ * Initialize the initParam list in gather or gather merge node such that
+ * it contains reference of all the params that needs to be evaluated
+ * before execution of the node. It contains the initplan params that are
+ * being passed to the plan nodes below it.
+ */
+static void
+set_param_references(PlannerInfo *root, Plan *plan)
+{
+ Assert(IsA(plan, Gather) || IsA(plan, GatherMerge));
+
+ if (plan->lefttree->extParam)
+ {
+ PlannerInfo *proot;
+ Bitmapset *initSetParam = NULL;
+ ListCell *l;
+
+ for (proot = root; proot != NULL; proot = proot->parent_root)
+ {
+ foreach(l, proot->init_plans)
+ {
+ SubPlan *initsubplan = (SubPlan *) lfirst(l);
+ ListCell *l2;
+
+ foreach(l2, initsubplan->setParam)
+ {
+ initSetParam = bms_add_member(initSetParam, lfirst_int(l2));
+ }
+ }
+ }
+
+ if (IsA(plan, Gather))
+ ((Gather *) plan)->initParam = bms_intersect(plan->lefttree->extParam, initSetParam);
+ else if (IsA(plan, GatherMerge))
+ ((GatherMerge *) plan)->initParam = bms_intersect(plan->lefttree->extParam, initSetParam);
+ else
+ elog(ERROR, "unrecognized node type: %d", nodeTag(plan));
+ }
+}
+
+/*
* Recursively scan an expression tree and convert Aggrefs to the proper
* intermediate form for combining aggregates. This means (1) replacing each
* one's argument list with a single argument that is the original Aggref
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index ffbd3ee..0e0a375 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -2135,13 +2135,11 @@ SS_identify_outer_params(PlannerInfo *root)
}
/*
- * SS_charge_for_initplans - account for initplans in Path costs & parallelism
+ * SS_charge_for_initplans - account for initplans in Path costs
*
* If any initPlans have been created in the current query level, they will
* get attached to the Plan tree created from whichever Path we select from
- * the given rel. Increment all that rel's Paths' costs to account for them,
- * and make sure the paths get marked as parallel-unsafe, since we can't
- * currently transmit initPlans to parallel workers.
+ * the given rel. Increment all that rel's Paths' costs to account for them.
*
* This is separate from SS_attach_initplans because we might conditionally
* create more initPlans during create_plan(), depending on which Path we
@@ -2173,7 +2171,7 @@ SS_charge_for_initplans(PlannerInfo *root, RelOptInfo *final_rel)
}
/*
- * Now adjust the costs and parallel_safe flags.
+ * Now adjust the costs.
*/
foreach(lc, final_rel->pathlist)
{
@@ -2181,7 +2179,6 @@ SS_charge_for_initplans(PlannerInfo *root, RelOptInfo *final_rel)
path->startup_cost += initplan_cost;
path->total_cost += initplan_cost;
- path->parallel_safe = false;
}
/* We needn't do set_cheapest() here, caller will do it */
@@ -2935,3 +2932,29 @@ SS_make_initplan_from_plan(PlannerInfo *root,
/* Set costs of SubPlan using info from the plan tree */
cost_subplan(subroot, node, plan);
}
+
+/*
+ * is_initplan_below_current_query_level - is there any initplan present below
+ * current query level.
+ */
+bool
+is_initplan_below_current_query_level(PlannerInfo *root)
+{
+ ListCell *lc;
+
+ /*
+ * If the subplan corresponding to the subroot is an initPlan, it'll be
+ * attached to its parent root. Hence, we check the query level of its
+ * parent root and if any init_plans are attached there.
+ */
+ foreach(lc, root->glob->subroots)
+ {
+ PlannerInfo *subroot = (PlannerInfo *) lfirst(lc);
+ PlannerInfo *proot = subroot->parent_root;
+
+ if (proot->query_level > root->query_level && proot->init_plans)
+ return true;
+ }
+
+ return false;
+}
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 8b4425d..f1f63ce 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -91,6 +91,7 @@ typedef struct
typedef struct
{
+ PlannerInfo *root;
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 */
@@ -1069,6 +1070,7 @@ max_parallel_hazard(Query *parse)
{
max_parallel_hazard_context context;
+ context.root = NULL;
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_UNSAFE;
context.safe_param_ids = NIL;
@@ -1098,6 +1100,7 @@ is_parallel_safe(PlannerInfo *root, Node *node)
root->glob->nParamExec == 0)
return true;
/* Else use max_parallel_hazard's search logic, but stop on RESTRICTED */
+ context.root = root;
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_RESTRICTED;
context.safe_param_ids = NIL;
@@ -1222,21 +1225,47 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
}
/*
- * We can't pass Params to workers at the moment either, so they are also
- * parallel-restricted, unless they are PARAM_EXEC Params listed in
+ * As of now, we can only pass Params that refer to the same or parent
+ * query level (see generate_gather_paths) or they are listed in
* safe_param_ids, meaning they could be generated within the worker.
*/
else if (IsA(node, Param))
{
+ int paramid;
+ PlannerInfo *root;
Param *param = (Param *) node;
- if (param->paramkind != PARAM_EXEC ||
- !list_member_int(context->safe_param_ids, param->paramid))
+ if (list_member_int(context->safe_param_ids, param->paramid))
+ return false;
+
+ root = context->root;
+ paramid = ((Param *) node)->paramid;
+
+ if (root)
{
- if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
- return true;
+ PlannerInfo *proot;
+ ListCell *l;
+
+ for (proot = root; proot != NULL; proot = proot->parent_root)
+ {
+ foreach(l, proot->init_plans)
+ {
+ SubPlan *initsubplan = (SubPlan *) lfirst(l);
+ ListCell *l2;
+
+ foreach(l2, initsubplan->setParam)
+ {
+ int initparam = lfirst_int(l2);
+
+ if (paramid == initparam)
+ return false;
+ }
+ }
+ }
}
- return false; /* nothing to recurse to */
+
+ if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
+ return true;
}
/*
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 8ee0496..c44ad88 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -611,6 +611,7 @@ extern ExprEvalOp ExecEvalStepOp(ExprState *state, ExprEvalStep *op);
*/
extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
+extern void ExecEvalParamExecParams(Bitmapset *params, EState *estate);
extern void ExecEvalParamExtern(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
extern void ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op);
diff --git a/src/include/executor/execParallel.h b/src/include/executor/execParallel.h
index bd0a87f..72eda9e 100644
--- a/src/include/executor/execParallel.h
+++ b/src/include/executor/execParallel.h
@@ -33,7 +33,7 @@ typedef struct ParallelExecutorInfo
} ParallelExecutorInfo;
extern ParallelExecutorInfo *ExecInitParallelPlan(PlanState *planstate,
- EState *estate, int nworkers);
+ EState *estate, int nworkers, Bitmapset *initParam);
extern void ExecParallelFinish(ParallelExecutorInfo *pei);
extern void ExecParallelCleanup(ParallelExecutorInfo *pei);
extern void ExecParallelReinitialize(ParallelExecutorInfo *pei);
diff --git a/src/include/executor/nodeSubplan.h b/src/include/executor/nodeSubplan.h
index 5dbaeeb..40b1a34 100644
--- a/src/include/executor/nodeSubplan.h
+++ b/src/include/executor/nodeSubplan.h
@@ -28,4 +28,10 @@ extern void ExecReScanSetParamPlan(SubPlanState *node, PlanState *parent);
extern void ExecSetParamPlan(SubPlanState *node, ExprContext *econtext);
+extern Size EstimateInitPlanParamsSpace(ParamExecData *paramExecVals, Bitmapset *params);
+
+extern void SerializeInitPlanParams(ParamExecData *paramExecVals, Bitmapset *params, char **start_address);
+
+extern void RestoreInitPlanParams(char **start_address, ParamExecData *params);
+
#endif /* NODESUBPLAN_H */
diff --git a/src/include/nodes/params.h b/src/include/nodes/params.h
index 55219da..e96a1ea 100644
--- a/src/include/nodes/params.h
+++ b/src/include/nodes/params.h
@@ -98,6 +98,16 @@ typedef struct ParamExecData
{
void *execPlan; /* should be "SubPlanState *" */
Datum value;
+
+ /*
+ * parameter's datatype, or 0. This is required so that datum value can
+ * be read and used for other purposes like passing it to worker backend
+ * via shared memory. This is required only for initPlan's evaluation,
+ * however for consistency we set this for Subplan as well. We left it
+ * for other cases like CTE or RecursiveUnion cases where this structure
+ * is not used for evaluation of subplans.
+ */
+ Oid ptype;
bool isnull;
} ParamExecData;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index f1a1b24..16761ba 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -833,6 +833,8 @@ typedef struct Gather
int num_workers;
bool single_copy;
bool invisible; /* suppress EXPLAIN display (for testing)? */
+ Bitmapset *initParam; /* param id's of initplans which are referred
+ * at gather or one of it's child node */
} Gather;
/* ------------
@@ -849,6 +851,8 @@ typedef struct GatherMerge
Oid *sortOperators; /* OIDs of operators to sort them by */
Oid *collations; /* OIDs of collations */
bool *nullsFirst; /* NULLS FIRST/LAST directions */
+ Bitmapset *initParam; /* param id's of initplans which are referred
+ * at gather merge or one of it's child node */
} GatherMerge;
/* ----------------
diff --git a/src/include/optimizer/subselect.h b/src/include/optimizer/subselect.h
index ecd2011..bed6d2b 100644
--- a/src/include/optimizer/subselect.h
+++ b/src/include/optimizer/subselect.h
@@ -35,6 +35,7 @@ extern Param *SS_make_initplan_output_param(PlannerInfo *root,
extern void SS_make_initplan_from_plan(PlannerInfo *root,
PlannerInfo *subroot, Plan *plan,
Param *prm);
+extern bool is_initplan_below_current_query_level(PlannerInfo *root);
extern Param *assign_nestloop_param_var(PlannerInfo *root, Var *var);
extern Param *assign_nestloop_param_placeholdervar(PlannerInfo *root,
PlaceHolderVar *phv);
diff --git a/src/test/regress/expected/select_parallel.out b/src/test/regress/expected/select_parallel.out
index 3e35e96..56133fe 100644
--- a/src/test/regress/expected/select_parallel.out
+++ b/src/test/regress/expected/select_parallel.out
@@ -139,6 +139,39 @@ explain (costs off)
(4 rows)
alter table tenk2 reset (parallel_workers);
+--test parallel plan for a query containing initplan.
+set enable_indexscan = off;
+set enable_indexonlyscan = off;
+set enable_bitmapscan = off;
+alter table tenk2 set (parallel_workers = 2);
+explain (costs off)
+ select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+ QUERY PLAN
+------------------------------------------------------
+ Aggregate
+ InitPlan 1 (returns $1)
+ -> Finalize Aggregate
+ -> Gather
+ Workers Planned: 2
+ -> Partial Aggregate
+ -> Parallel Seq Scan on tenk2
+ -> Gather
+ Workers Planned: 4
+ Params Evaluated: $1
+ -> Parallel Seq Scan on tenk1
+ Filter: (unique1 = $1)
+(12 rows)
+
+select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+ count
+-------
+ 1
+(1 row)
+
+reset enable_indexscan;
+reset enable_indexonlyscan;
+reset enable_bitmapscan;
+alter table tenk2 reset (parallel_workers);
-- test parallel index scans.
set enable_seqscan to off;
set enable_bitmapscan to off;
diff --git a/src/test/regress/sql/select_parallel.sql b/src/test/regress/sql/select_parallel.sql
index d2d262c..57a4944 100644
--- a/src/test/regress/sql/select_parallel.sql
+++ b/src/test/regress/sql/select_parallel.sql
@@ -52,6 +52,21 @@ explain (costs off)
(select ten from tenk2);
alter table tenk2 reset (parallel_workers);
+--test parallel plan for a query containing initplan.
+set enable_indexscan = off;
+set enable_indexonlyscan = off;
+set enable_bitmapscan = off;
+alter table tenk2 set (parallel_workers = 2);
+
+explain (costs off)
+ select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+
+reset enable_indexscan;
+reset enable_indexonlyscan;
+reset enable_bitmapscan;
+alter table tenk2 reset (parallel_workers);
+
-- test parallel index scans.
set enable_seqscan to off;
set enable_bitmapscan to off;
On Mon, Jul 17, 2017 at 10:53 PM, Amit Kapila <amit.kapila16@gmail.com>
wrote:
On Tue, Mar 28, 2017 at 7:25 AM, Amit Kapila <amit.kapila16@gmail.com>
wrote:On Thu, Mar 16, 2017 at 2:34 AM, Kuntal Ghosh
<kuntalghosh.2007@gmail.com> wrote:On Tue, Mar 14, 2017 at 3:20 PM, Amit Kapila <amit.kapila16@gmail.com>
wrote:
Based on that idea, I have modified the patch such that it will
compute the set of initplans Params that are required below gather
node and store them as bitmap of initplan params at gather node.
During set_plan_references, we can find the intersection of external
parameters that are required at Gather or nodes below it with the
initplans that are passed from same or above query level. Once the set
of initplan params are established, we evaluate those (if they are not
already evaluated) before execution of gather node and then pass the
computed value to each of the workers. To identify whether a
particular param is parallel safe or not, we check if the paramid of
the param exists in initplans at same or above query level. We don't
allow to generate gather path if there are initplans at some query
level below the current query level as those plans could be
parallel-unsafe or undirect correlated plans.I would like to mention different test scenarios with InitPlans that
we've considered while developing and testing of the patch.Thanks a lot Kuntal for sharing different test scenarios.
Unfortunately, this patch doesn't received any review till now, so
there is no chance of making it in to PostgreSQL-10. I have moved
this to next CF.Attached is a rebased version of the patch with below changes:
a. SubplanState now directly stores Subplan rather than ExprState, so
patch needs some adjustment in that regard.
b. While rejecting the paths (based on if there are initplans at level
below the current query level) for parallelism, the rejected paths
were not marked as parallel unsafe. Due to this in
force_parallel_mode=regress, we were able to add gather node above
parallel unsafe paths. The modified patch ensures to mark such paths
as parallel unsafe.
c. Added regression test.
d. Improve comments in the code.
I tested the latest patch and the parallel plan is getting choose for most
of
the init plans.
For the following query the parallel plan is not chosen. The query contains
an init plan that refer the outer node.
postgres=# explain analyze select * from t1 where t1.i in (select t2.i from
t2 where t1.k = (select max(k) from t3 where t3.i=t1.i));
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------
Seq Scan on t1 (cost=0.00..22426.28 rows=448 width=12) (actual
time=8.335..132.557 rows=2 loops=1)
Filter: (SubPlan 2)
Rows Removed by Filter: 894
SubPlan 2
-> Result (cost=16.27..31.26 rows=999 width=4) (actual
time=0.146..0.146 rows=0 loops=896)
One-Time Filter: (t1.k = $1)
InitPlan 1 (returns $1)
-> Aggregate (cost=16.25..16.27 rows=1 width=4) (actual
time=0.145..0.145 rows=1 loops=896)
-> Seq Scan on t3 (cost=0.00..16.25 rows=2 width=4)
(actual time=0.131..0.144 rows=0 loops=896)
Filter: (i = t1.i)
Rows Removed by Filter: 900
-> Seq Scan on t2 (cost=16.27..31.26 rows=999 width=4) (actual
time=0.012..0.013 rows=10 loops=2)
Planning time: 0.272 ms
Execution time: 132.623 ms
(14 rows)
If I change the query a little bit, the Result node doesn't appear and the
parallel plan
gets chosen.
postgres=# explain analyze select * from t1 where t1.i in (select t2.i from
t2 where t2.k = (select max(k) from t3 where t3.i=t1.i));
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------
Seq Scan on t1 (cost=0.00..19162.88 rows=448 width=12) (actual
time=3501.483..3501.483 rows=0 loops=1)
Filter: (SubPlan 2)
Rows Removed by Filter: 896
SubPlan 2
-> Gather (cost=16.27..26.47 rows=2 width=4) (actual
time=3.471..3.795 rows=0 loops=896)
Workers Planned: 2
Params Evaluated: $1
Workers Launched: 2
InitPlan 1 (returns $1)
-> Aggregate (cost=16.25..16.27 rows=1 width=4) (actual
time=0.161..0.161 rows=1 loops=896)
-> Seq Scan on t3 (cost=0.00..16.25 rows=2 width=4)
(actual time=0.144..0.156 rows=0 loops=896)
Filter: (i = t1.i)
Rows Removed by Filter: 900
-> Parallel Seq Scan on t2 (cost=0.00..10.20 rows=1 width=4)
(actual time=0.001..0.001 rows=0 loops=804608)
Filter: (k = $1)
Rows Removed by Filter: 1
Planning time: 0.480 ms
Execution time: 3502.016 ms
(18 rows)
I didn't check the code why the plan is not getting chosen.
Just shared it for your reference, whether it is a known already.
Regards,
Hari Babu
Fujitsu Australia
On Wed, Aug 9, 2017 at 10:24 AM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:
I tested the latest patch and the parallel plan is getting choose for most
of
the init plans.
Thanks for testing.
For the following query the parallel plan is not chosen. The query contains
an init plan that refer the outer node.postgres=# explain analyze select * from t1 where t1.i in (select t2.i from
t2 where t1.k = (select max(k) from t3 where t3.i=t1.i));
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------
Seq Scan on t1 (cost=0.00..22426.28 rows=448 width=12) (actual
time=8.335..132.557 rows=2 loops=1)
Filter: (SubPlan 2)
Rows Removed by Filter: 894
SubPlan 2
-> Result (cost=16.27..31.26 rows=999 width=4) (actual
time=0.146..0.146 rows=0 loops=896)
One-Time Filter: (t1.k = $1)
InitPlan 1 (returns $1)
-> Aggregate (cost=16.25..16.27 rows=1 width=4) (actual
time=0.145..0.145 rows=1 loops=896)
-> Seq Scan on t3 (cost=0.00..16.25 rows=2 width=4)
(actual time=0.131..0.144 rows=0 loops=896)
Filter: (i = t1.i)
Rows Removed by Filter: 900
-> Seq Scan on t2 (cost=16.27..31.26 rows=999 width=4) (actual
time=0.012..0.013 rows=10 loops=2)
Planning time: 0.272 ms
Execution time: 132.623 ms
(14 rows)
An observation is that the filter at Result node can't be pushed down
to the sequential scan on t2 because the filter is on t1. So, it has
to scan the complete t2 relation and send all the tuple to upper node,
a worst case for parallelism. Probably, this is the reason the
optimizer doesn't pick parallel plan for the above case.
Just for clarification, do you see any changes in the plan after
forcing parallelism(parallel_tuple_cost, parallel_setup_cost,
min_parallel_table_scan_size=0)?
--
Thanks & Regards,
Kuntal Ghosh
EnterpriseDB: http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Aug 9, 2017 at 10:24 AM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:
On Mon, Jul 17, 2017 at 10:53 PM, Amit Kapila <amit.kapila16@gmail.com>
wrote:On Tue, Mar 28, 2017 at 7:25 AM, Amit Kapila <amit.kapila16@gmail.com>
wrote:On Thu, Mar 16, 2017 at 2:34 AM, Kuntal Ghosh
<kuntalghosh.2007@gmail.com> wrote:On Tue, Mar 14, 2017 at 3:20 PM, Amit Kapila <amit.kapila16@gmail.com>
wrote:Based on that idea, I have modified the patch such that it will
compute the set of initplans Params that are required below gather
node and store them as bitmap of initplan params at gather node.
During set_plan_references, we can find the intersection of external
parameters that are required at Gather or nodes below it with the
initplans that are passed from same or above query level. Once the set
of initplan params are established, we evaluate those (if they are not
already evaluated) before execution of gather node and then pass the
computed value to each of the workers. To identify whether a
particular param is parallel safe or not, we check if the paramid of
the param exists in initplans at same or above query level. We don't
allow to generate gather path if there are initplans at some query
level below the current query level as those plans could be
parallel-unsafe or undirect correlated plans.I would like to mention different test scenarios with InitPlans that
we've considered while developing and testing of the patch.Thanks a lot Kuntal for sharing different test scenarios.
Unfortunately, this patch doesn't received any review till now, so
there is no chance of making it in to PostgreSQL-10. I have moved
this to next CF.Attached is a rebased version of the patch with below changes:
a. SubplanState now directly stores Subplan rather than ExprState, so
patch needs some adjustment in that regard.
b. While rejecting the paths (based on if there are initplans at level
below the current query level) for parallelism, the rejected paths
were not marked as parallel unsafe. Due to this in
force_parallel_mode=regress, we were able to add gather node above
parallel unsafe paths. The modified patch ensures to mark such paths
as parallel unsafe.
c. Added regression test.
d. Improve comments in the code.I tested the latest patch and the parallel plan is getting choose for most
of
the init plans.
Thanks for looking into this patch.
For the following query the parallel plan is not chosen. The query contains
an init plan that refer the outer node.
We don't want to generate the parallel plan for such cases. Whenever
initplan refers to any outer node (aka correlated plan), it won't
generate a parallel plan. Also, for t2, it doesn't choose a parallel
plan because one-time filter refers to the outer node (again
correlated plan case). Basically, till now we don't support parallel
plan for any case where the correlated plan is used. So, it is
perfectly valid that it doesn't use parallel plan here.
postgres=# explain analyze select * from t1 where t1.i in (select t2.i from
t2 where t1.k = (select max(k) from t3 where t3.i=t1.i));
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------
Seq Scan on t1 (cost=0.00..22426.28 rows=448 width=12) (actual
time=8.335..132.557 rows=2 loops=1)
Filter: (SubPlan 2)
Rows Removed by Filter: 894
SubPlan 2
-> Result (cost=16.27..31.26 rows=999 width=4) (actual
time=0.146..0.146 rows=0 loops=896)
One-Time Filter: (t1.k = $1)
InitPlan 1 (returns $1)
-> Aggregate (cost=16.25..16.27 rows=1 width=4) (actual
time=0.145..0.145 rows=1 loops=896)
-> Seq Scan on t3 (cost=0.00..16.25 rows=2 width=4)
(actual time=0.131..0.144 rows=0 loops=896)
Filter: (i = t1.i)
Rows Removed by Filter: 900
-> Seq Scan on t2 (cost=16.27..31.26 rows=999 width=4) (actual
time=0.012..0.013 rows=10 loops=2)
Planning time: 0.272 ms
Execution time: 132.623 ms
(14 rows)If I change the query a little bit, the Result node doesn't appear and the
parallel plan
gets chosen.
This is a valid case for choosing a parallel plan.
postgres=# explain analyze select * from t1 where t1.i in (select t2.i from
t2 where t2.k = (select max(k) from t3 where t3.i=t1.i));
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------
Seq Scan on t1 (cost=0.00..19162.88 rows=448 width=12) (actual
time=3501.483..3501.483 rows=0 loops=1)
Filter: (SubPlan 2)
Rows Removed by Filter: 896
SubPlan 2
-> Gather (cost=16.27..26.47 rows=2 width=4) (actual
time=3.471..3.795 rows=0 loops=896)
Workers Planned: 2
Params Evaluated: $1
Workers Launched: 2
InitPlan 1 (returns $1)
-> Aggregate (cost=16.25..16.27 rows=1 width=4) (actual
time=0.161..0.161 rows=1 loops=896)
-> Seq Scan on t3 (cost=0.00..16.25 rows=2 width=4)
(actual time=0.144..0.156 rows=0 loops=896)
Filter: (i = t1.i)
Rows Removed by Filter: 900
-> Parallel Seq Scan on t2 (cost=0.00..10.20 rows=1 width=4)
(actual time=0.001..0.001 rows=0 loops=804608)
Filter: (k = $1)
Rows Removed by Filter: 1
Planning time: 0.480 ms
Execution time: 3502.016 ms
(18 rows)
Here if you notice the parallel node t2 refers to the initplan which
can be parallelised after this patch. Basically, whenever the
initplan is attached at or above Gather node, we compute its value and
pass down to workers.
I didn't check the code why the plan is not getting chosen.
Just shared it for your reference, whether it is a known already.
Yeah, it is known the behavior of the patch.
By the way, the patch doesn't apply on HEAD, so attached rebased patch.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
pq_pushdown_initplan_v5.patchapplication/octet-stream; name=pq_pushdown_initplan_v5.patchDownload
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 7648201..83a26ec 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -107,6 +107,7 @@ static void show_tidbitmap_info(BitmapHeapScanState *planstate,
static void show_instrumentation_count(const char *qlabel, int which,
PlanState *planstate, ExplainState *es);
static void show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es);
+static void show_eval_params(Bitmapset *bms_params, ExplainState *es);
static const char *explain_get_index_name(Oid indexId);
static void show_buffer_usage(ExplainState *es, const BufferUsage *usage);
static void ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir,
@@ -637,7 +638,17 @@ ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc)
*/
ps = queryDesc->planstate;
if (IsA(ps, GatherState) &&((Gather *) ps->plan)->invisible)
+ {
+ List *initPlanState = NULL;
+ PlanState *save_ps;
+
+ /* initplans are always attached to the top node (cf standard_planner) */
+ save_ps = ps;
+ initPlanState = ps->initPlan;
ps = outerPlanState(ps);
+ ps->initPlan = initPlanState;
+ save_ps->initPlan = NIL;
+ }
ExplainNode(ps, NIL, NULL, NULL, es);
}
@@ -1434,6 +1445,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
planstate, es);
ExplainPropertyInteger("Workers Planned",
gather->num_workers, es);
+
+ /* Show params evaluated at gather node */
+ if (gather->initParam)
+ show_eval_params(gather->initParam, es);
+
if (es->analyze)
{
int nworkers;
@@ -1456,6 +1472,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
planstate, es);
ExplainPropertyInteger("Workers Planned",
gm->num_workers, es);
+
+ /* Show params evaluated at gather-merge node */
+ if (gm->initParam)
+ show_eval_params(gm->initParam, es);
+
if (es->analyze)
{
int nworkers;
@@ -2430,6 +2451,28 @@ show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es)
}
/*
+ * Show initplan params evaluated at gather or gather merge node.
+ */
+static void
+show_eval_params(Bitmapset *bms_params, ExplainState *es)
+{
+ int paramid = -1;
+ List *params = NIL;
+
+ Assert(bms_params);
+
+ while ((paramid = bms_next_member(bms_params, paramid)) >= 0)
+ {
+ char param[32];
+
+ snprintf(param, sizeof(param), "$%d", paramid);
+ params = lappend(params, pstrdup(param));
+ }
+
+ ExplainPropertyList("Params Evaluated", params, es);
+}
+
+/*
* Fetch the name of an index in an EXPLAIN
*
* We allow plugins to get control here so that plans involving hypothetical
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index f2a52f62..ed1449d 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -1861,6 +1861,33 @@ ExecEvalParamExec(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
}
/*
+ * ExecEvalParamExecParams
+ *
+ * Execute the subplan stored in PARAM_EXEC initplans params, if not executed
+ * till now.
+ */
+void
+ExecEvalParamExecParams(Bitmapset *params, EState *estate)
+{
+ ParamExecData *prm;
+ int paramid;
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ prm = &(estate->es_param_exec_vals[paramid]);
+
+ if (prm->execPlan != NULL)
+ {
+ /* Parameter not evaluated yet, so go do it */
+ ExecSetParamPlan(prm->execPlan, GetPerTupleExprContext(estate));
+ /* ExecSetParamPlan should have processed this param... */
+ Assert(prm->execPlan == NULL);
+ }
+ }
+}
+
+/*
* Evaluate a PARAM_EXTERN parameter.
*
* PARAM_EXTERN parameters must be sought in ecxt_param_list_info.
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index ce47f1d..d127031 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -23,12 +23,14 @@
#include "postgres.h"
+#include "executor/execExpr.h"
#include "executor/execParallel.h"
#include "executor/executor.h"
#include "executor/nodeBitmapHeapscan.h"
#include "executor/nodeCustom.h"
#include "executor/nodeForeignscan.h"
#include "executor/nodeSeqscan.h"
+#include "executor/nodeSubplan.h"
#include "executor/nodeIndexscan.h"
#include "executor/nodeIndexonlyscan.h"
#include "executor/tqueue.h"
@@ -54,6 +56,7 @@
#define PARALLEL_KEY_INSTRUMENTATION UINT64CONST(0xE000000000000005)
#define PARALLEL_KEY_DSA UINT64CONST(0xE000000000000006)
#define PARALLEL_KEY_QUERY_TEXT UINT64CONST(0xE000000000000007)
+#define PARALLEL_KEY_INITPLAN_PARAMS UINT64CONST(0xE000000000000008)
#define PARALLEL_TUPLE_QUEUE_SIZE 65536
@@ -381,7 +384,8 @@ ExecParallelReinitialize(ParallelExecutorInfo *pei)
* execution and return results to the main backend.
*/
ParallelExecutorInfo *
-ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers)
+ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
+ Bitmapset *initParam)
{
ParallelExecutorInfo *pei;
ParallelContext *pcxt;
@@ -390,10 +394,12 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers)
char *pstmt_data;
char *pstmt_space;
char *param_space;
+ char *initplan_param_space;
BufferUsage *bufusage_space;
SharedExecutorInstrumentation *instrumentation = NULL;
int pstmt_len;
int param_len;
+ int initplan_param_len;
int instrumentation_len = 0;
int instrument_offset = 0;
Size dsa_minsize = dsa_minimum_size();
@@ -405,6 +411,8 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers)
pei->finished = false;
pei->planstate = planstate;
+ ExecEvalParamExecParams(initParam, estate);
+
/* Fix up and serialize plan to be sent to workers. */
pstmt_data = ExecSerializePlan(planstate->plan, estate);
@@ -433,6 +441,11 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers)
shm_toc_estimate_chunk(&pcxt->estimator, param_len);
shm_toc_estimate_keys(&pcxt->estimator, 1);
+ /* Estimate space for initplan params. */
+ initplan_param_len = EstimateInitPlanParamsSpace(estate->es_param_exec_vals, initParam);
+ shm_toc_estimate_chunk(&pcxt->estimator, initplan_param_len);
+ shm_toc_estimate_keys(&pcxt->estimator, 1);
+
/*
* Estimate space for BufferUsage.
*
@@ -502,6 +515,11 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers)
shm_toc_insert(pcxt->toc, PARALLEL_KEY_PARAMS, param_space);
SerializeParamList(estate->es_param_list_info, ¶m_space);
+ /* Store serialized initplan params. */
+ initplan_param_space = shm_toc_allocate(pcxt->toc, initplan_param_len);
+ shm_toc_insert(pcxt->toc, PARALLEL_KEY_INITPLAN_PARAMS, initplan_param_space);
+ SerializeInitPlanParams(estate->es_param_exec_vals, initParam, &initplan_param_space);
+
/* Allocate space for each worker's BufferUsage; no need to initialize. */
bufusage_space = shm_toc_allocate(pcxt->toc,
mul_size(sizeof(BufferUsage), pcxt->nworkers));
@@ -692,6 +710,21 @@ ExecParallelGetReceiver(dsm_segment *seg, shm_toc *toc)
}
/*
+ * Copy the ParamExecData params corresponding to initplans from dynamic
+ * shared memory. This has to be done once the params are allocated by
+ * executor; that is after ExecutorStart().
+ */
+static void
+ExecParallelInitializeInitPlanParams(shm_toc *toc, ParamExecData *params)
+{
+ char *paramspace;
+
+ /* Reconstruct initplan params. */
+ paramspace = shm_toc_lookup(toc, PARALLEL_KEY_INITPLAN_PARAMS, false);
+ RestoreInitPlanParams(¶mspace, params);
+}
+
+/*
* Create a QueryDesc for the PlannedStmt we are to execute, and return it.
*/
static QueryDesc *
@@ -866,6 +899,7 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
/* Special executor initialization steps for parallel workers */
queryDesc->planstate->state->es_query_dsa = area;
+ ExecParallelInitializeInitPlanParams(toc, queryDesc->estate->es_param_exec_vals);
ExecParallelInitializeWorker(queryDesc->planstate, toc);
/* Run the plan */
diff --git a/src/backend/executor/nodeGather.c b/src/backend/executor/nodeGather.c
index e8d94ee..50152f4 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -156,7 +156,8 @@ ExecGather(PlanState *pstate)
if (!node->pei)
node->pei = ExecInitParallelPlan(node->ps.lefttree,
estate,
- gather->num_workers);
+ gather->num_workers,
+ gather->initParam);
/*
* Register backend workers. We might not get as many as we
diff --git a/src/backend/executor/nodeGatherMerge.c b/src/backend/executor/nodeGatherMerge.c
index 9a81e22..1e1db4f 100644
--- a/src/backend/executor/nodeGatherMerge.c
+++ b/src/backend/executor/nodeGatherMerge.c
@@ -190,7 +190,8 @@ ExecGatherMerge(PlanState *pstate)
if (!node->pei)
node->pei = ExecInitParallelPlan(node->ps.lefttree,
estate,
- gm->num_workers);
+ gm->num_workers,
+ gm->initParam);
/* Try to launch workers. */
pcxt = node->pei->pcxt;
diff --git a/src/backend/executor/nodeNestloop.c b/src/backend/executor/nodeNestloop.c
index 4447b7c..5c5dddb 100644
--- a/src/backend/executor/nodeNestloop.c
+++ b/src/backend/executor/nodeNestloop.c
@@ -130,6 +130,7 @@ ExecNestLoop(PlanState *pstate)
{
NestLoopParam *nlp = (NestLoopParam *) lfirst(lc);
int paramno = nlp->paramno;
+ TupleDesc tdesc = outerTupleSlot->tts_tupleDescriptor;
ParamExecData *prm;
prm = &(econtext->ecxt_param_exec_vals[paramno]);
@@ -140,6 +141,7 @@ ExecNestLoop(PlanState *pstate)
prm->value = slot_getattr(outerTupleSlot,
nlp->paramval->varattno,
&(prm->isnull));
+ prm->ptype = tdesc->attrs[nlp->paramval->varattno - 1]->atttypid;
/* Flag parameter value as changed */
innerPlan->chgParam = bms_add_member(innerPlan->chgParam,
paramno);
diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c
index fe10e80..9367175 100644
--- a/src/backend/executor/nodeSubplan.c
+++ b/src/backend/executor/nodeSubplan.c
@@ -30,12 +30,16 @@
#include <math.h>
#include "access/htup_details.h"
+#include "catalog/pg_type.h"
#include "executor/executor.h"
#include "executor/nodeSubplan.h"
#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
#include "miscadmin.h"
#include "optimizer/clauses.h"
+#include "storage/shmem.h"
#include "utils/array.h"
+#include "utils/datum.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
@@ -273,11 +277,13 @@ ExecScanSubPlan(SubPlanState *node,
forboth(l, subplan->parParam, pvar, node->args)
{
int paramid = lfirst_int(l);
+ ExprState *exprstate = (ExprState *) lfirst(pvar);
ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]);
- prm->value = ExecEvalExprSwitchContext((ExprState *) lfirst(pvar),
+ prm->value = ExecEvalExprSwitchContext(exprstate,
econtext,
&(prm->isnull));
+ prm->ptype = exprType((Node *) exprstate->expr);
planstate->chgParam = bms_add_member(planstate->chgParam, paramid);
}
@@ -390,6 +396,7 @@ ExecScanSubPlan(SubPlanState *node,
prmdata = &(econtext->ecxt_param_exec_vals[paramid]);
Assert(prmdata->execPlan == NULL);
prmdata->value = slot_getattr(slot, col, &(prmdata->isnull));
+ prmdata->ptype = tdesc->attrs[col - 1]->atttypid;
col++;
}
@@ -556,11 +563,13 @@ buildSubPlanHash(SubPlanState *node, ExprContext *econtext)
{
int paramid = lfirst_int(plst);
ParamExecData *prmdata;
+ TupleDesc tdesc = slot->tts_tupleDescriptor;
prmdata = &(innerecontext->ecxt_param_exec_vals[paramid]);
Assert(prmdata->execPlan == NULL);
prmdata->value = slot_getattr(slot, col,
&(prmdata->isnull));
+ prmdata->ptype = tdesc->attrs[col - 1]->atttypid;
col++;
}
slot = ExecProject(node->projRight);
@@ -924,6 +933,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
ListCell *l;
bool found = false;
ArrayBuildStateAny *astate = NULL;
+ Oid ptype;
if (subLinkType == ANY_SUBLINK ||
subLinkType == ALL_SUBLINK)
@@ -931,6 +941,8 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
if (subLinkType == CTE_SUBLINK)
elog(ERROR, "CTE subplans should not be executed via ExecSetParamPlan");
+ ptype = exprType((Node *) node->subplan);
+
/* Initialize ArrayBuildStateAny in caller's context, if needed */
if (subLinkType == ARRAY_SUBLINK)
astate = initArrayResultAny(subplan->firstColType,
@@ -953,11 +965,13 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
forboth(l, subplan->parParam, pvar, node->args)
{
int paramid = lfirst_int(l);
+ ExprState *exprstate = (ExprState *) lfirst(pvar);
ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]);
- prm->value = ExecEvalExprSwitchContext((ExprState *) lfirst(pvar),
+ prm->value = ExecEvalExprSwitchContext(exprstate,
econtext,
&(prm->isnull));
+ prm->ptype = exprType((Node *) exprstate->expr);
planstate->chgParam = bms_add_member(planstate->chgParam, paramid);
}
@@ -980,6 +994,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = BoolGetDatum(true);
+ prm->ptype = ptype;
prm->isnull = false;
found = true;
break;
@@ -1031,6 +1046,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = heap_getattr(node->curTuple, i, tdesc,
&(prm->isnull));
+ prm->ptype = tdesc->attrs[i - 1]->atttypid;
i++;
}
}
@@ -1053,6 +1069,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
true);
prm->execPlan = NULL;
prm->value = node->curArray;
+ prm->ptype = ptype;
prm->isnull = false;
}
else if (!found)
@@ -1065,6 +1082,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = BoolGetDatum(false);
+ prm->ptype = ptype;
prm->isnull = false;
}
else
@@ -1077,6 +1095,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = (Datum) 0;
+ prm->ptype = VOIDOID;
prm->isnull = true;
}
}
@@ -1207,3 +1226,136 @@ ExecAlternativeSubPlan(AlternativeSubPlanState *node,
return ExecSubPlan(activesp, econtext, isNull);
}
+
+/*
+ * Estimate the amount of space required to serialize the InitPlan params.
+ */
+Size
+EstimateInitPlanParamsSpace(ParamExecData *paramExecVals, Bitmapset *params)
+{
+ int paramid;
+ Size sz = sizeof(int);
+ ParamExecData *prm;
+
+ if (params == NULL)
+ return sz;
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ Oid typeOid;
+ int16 typLen;
+ bool typByVal;
+
+ prm = &(paramExecVals[paramid]);
+ typeOid = prm->ptype;
+
+ sz = add_size(sz, sizeof(int)); /* space for paramid */
+ sz = add_size(sz, sizeof(Oid)); /* space for type OID */
+
+ /* space for datum/isnull */
+ if (OidIsValid(typeOid))
+ get_typlenbyval(typeOid, &typLen, &typByVal);
+ else
+ {
+ /* If no type OID, assume by-value, like copyParamList does. */
+ typLen = sizeof(Datum);
+ typByVal = true;
+ }
+ sz = add_size(sz,
+ datumEstimateSpace(prm->value, prm->isnull, typByVal, typLen));
+ }
+ return sz;
+}
+
+/*
+ * Serialize ParamExecData params corresponding to initplans.
+ *
+ * We write the number of parameters first, as a 4-byte integer, and then
+ * write details for each parameter in turn. The details for each parameter
+ * consist of a 4-byte paramid (location of param in execution time internal
+ * parameter array), 4-byte type OID, and then the datum as serialized by
+ * datumSerialize().
+ *
+ * The above format is quite similar to the format used to serialize
+ * paramListInfo structure, so if we change either format, then consider to
+ * change at both the places.
+ */
+void
+SerializeInitPlanParams(ParamExecData *paramExecVals, Bitmapset *params,
+ char **start_address)
+{
+ int nparams;
+ int paramid;
+ ParamExecData *prm;
+
+ if (params == NULL)
+ nparams = 0;
+ else
+ nparams = bms_num_members(params);
+ memcpy(*start_address, &nparams, sizeof(int));
+ *start_address += sizeof(int);
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ Oid typeOid;
+ int16 typLen;
+ bool typByVal;
+
+ prm = &(paramExecVals[paramid]);
+ typeOid = prm->ptype;
+
+ /* Write paramid. */
+ memcpy(*start_address, ¶mid, sizeof(int));
+ *start_address += sizeof(int);
+
+ /* Write OID. */
+ memcpy(*start_address, &typeOid, sizeof(Oid));
+ *start_address += sizeof(Oid);
+
+ /* space for datum/isnull */
+ if (OidIsValid(typeOid))
+ get_typlenbyval(typeOid, &typLen, &typByVal);
+ else
+ {
+ /* If no type OID, assume by-value, like copyParamList does. */
+ typLen = sizeof(Datum);
+ typByVal = true;
+ }
+ datumSerialize(prm->value, prm->isnull, typByVal, typLen,
+ start_address);
+ }
+}
+
+/*
+ * Restore ParamExecData params corresponding to initplans.
+ */
+void
+RestoreInitPlanParams(char **start_address, ParamExecData *params)
+{
+ int nparams;
+ int i;
+ int paramid;
+
+ memcpy(&nparams, *start_address, sizeof(int));
+ *start_address += sizeof(int);
+
+ for (i = 0; i < nparams; i++)
+ {
+ ParamExecData *prm;
+
+ /* Read paramid */
+ memcpy(¶mid, *start_address, sizeof(int));
+ *start_address += sizeof(int);
+ prm = ¶ms[paramid];
+
+ /* Read type OID. */
+ memcpy(&prm->ptype, *start_address, sizeof(Oid));
+ *start_address += sizeof(Oid);
+
+ /* Read datum/isnull. */
+ prm->value = datumRestore(start_address, &prm->isnull);
+ prm->execPlan = NULL;
+ }
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 45a04b0..3e01857 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -363,6 +363,7 @@ _copyGather(const Gather *from)
COPY_SCALAR_FIELD(num_workers);
COPY_SCALAR_FIELD(single_copy);
COPY_SCALAR_FIELD(invisible);
+ COPY_BITMAPSET_FIELD(initParam);
return newnode;
}
@@ -389,6 +390,7 @@ _copyGatherMerge(const GatherMerge *from)
COPY_POINTER_FIELD(sortOperators, from->numCols * sizeof(Oid));
COPY_POINTER_FIELD(collations, from->numCols * sizeof(Oid));
COPY_POINTER_FIELD(nullsFirst, from->numCols * sizeof(bool));
+ COPY_BITMAPSET_FIELD(initParam);
return newnode;
}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 379d92a..95e9d37 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -481,6 +481,7 @@ _outGather(StringInfo str, const Gather *node)
WRITE_INT_FIELD(num_workers);
WRITE_BOOL_FIELD(single_copy);
WRITE_BOOL_FIELD(invisible);
+ WRITE_BITMAPSET_FIELD(initParam);
}
static void
@@ -510,6 +511,8 @@ _outGatherMerge(StringInfo str, const GatherMerge *node)
appendStringInfoString(str, " :nullsFirst");
for (i = 0; i < node->numCols; i++)
appendStringInfo(str, " %s", booltostr(node->nullsFirst[i]));
+
+ WRITE_BITMAPSET_FIELD(initParam);
}
static void
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 86c811d..c0ee903 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2165,6 +2165,7 @@ _readGather(void)
READ_INT_FIELD(num_workers);
READ_BOOL_FIELD(single_copy);
READ_BOOL_FIELD(invisible);
+ READ_BITMAPSET_FIELD(initParam);
READ_DONE();
}
@@ -2185,6 +2186,7 @@ _readGatherMerge(void)
READ_OID_ARRAY(sortOperators, local_node->numCols);
READ_OID_ARRAY(collations, local_node->numCols);
READ_BOOL_ARRAY(nullsFirst, local_node->numCols);
+ READ_BITMAPSET_FIELD(initParam);
READ_DONE();
}
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index f087ddb..d1c6e1f 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -38,6 +38,7 @@
#include "optimizer/planner.h"
#include "optimizer/prep.h"
#include "optimizer/restrictinfo.h"
+#include "optimizer/subselect.h"
#include "optimizer/tlist.h"
#include "optimizer/var.h"
#include "parser/parse_clause.h"
@@ -2202,6 +2203,31 @@ generate_gather_paths(PlannerInfo *root, RelOptInfo *rel)
Path *simple_gather_path;
ListCell *lc;
+ /*
+ * We don't want to generate gather or gather merge node if there are
+ * initplans at some query level below the current query level as those
+ * plans could be parallel-unsafe or undirect correlated plans. Ensure to
+ * mark all the partial and non-partial paths for a relation at this level
+ * to be parallel-unsafe.
+ */
+ if (is_initplan_below_current_query_level(root))
+ {
+ foreach(lc, rel->partial_pathlist)
+ {
+ Path *subpath = (Path *) lfirst(lc);
+
+ subpath->parallel_safe = false;
+ }
+
+ foreach(lc, rel->pathlist)
+ {
+ Path *subpath = (Path *) lfirst(lc);
+
+ subpath->parallel_safe = false;
+ }
+ return;
+ }
+
/* If there are no partial paths, there's nothing to do here. */
if (rel->partial_pathlist == NIL)
return;
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 5c934f2..adad0ef 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -6251,6 +6251,7 @@ make_gather(List *qptlist,
node->num_workers = nworkers;
node->single_copy = single_copy;
node->invisible = false;
+ node->initParam = NULL;
return node;
}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 2988c11..3f7986a 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -358,6 +358,14 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
{
Gather *gather = makeNode(Gather);
+ /*
+ * If there are any initPlans attached to the formerly-top plan node,
+ * move them up to the Gather node; same as we do for Material node in
+ * materialize_finished_plan.
+ */
+ gather->plan.initPlan = top_plan->initPlan;
+ top_plan->initPlan = NIL;
+
gather->plan.targetlist = top_plan->targetlist;
gather->plan.qual = NIL;
gather->plan.lefttree = top_plan;
@@ -3524,6 +3532,29 @@ create_grouping_paths(PlannerInfo *root,
ListCell *lc;
+ /*
+ * Don't parallelize the plan if there is an initplan below current query
+ * level. See generate_gather_paths() for detailed reason.
+ */
+ if (is_initplan_below_current_query_level(root))
+ {
+ foreach(lc, input_rel->partial_pathlist)
+ {
+ Path *subpath = (Path *) lfirst(lc);
+
+ subpath->parallel_safe = false;
+ }
+
+ foreach(lc, input_rel->pathlist)
+ {
+ Path *subpath = (Path *) lfirst(lc);
+
+ subpath->parallel_safe = false;
+ }
+
+ input_rel->consider_parallel = false;
+ }
+
/* For now, do all work in the (GROUP_AGG, NULL) upperrel */
grouped_rel = fetch_upper_rel(root, UPPERREL_GROUP_AGG, NULL);
@@ -4886,6 +4917,29 @@ create_ordered_paths(PlannerInfo *root,
RelOptInfo *ordered_rel;
ListCell *lc;
+ /*
+ * Don't parallelize the plan if there is an initplan below current query
+ * level. See generate_gather_paths() for detailed reason.
+ */
+ if (is_initplan_below_current_query_level(root))
+ {
+ foreach(lc, input_rel->partial_pathlist)
+ {
+ Path *subpath = (Path *) lfirst(lc);
+
+ subpath->parallel_safe = false;
+ }
+
+ foreach(lc, input_rel->pathlist)
+ {
+ Path *subpath = (Path *) lfirst(lc);
+
+ subpath->parallel_safe = false;
+ }
+
+ input_rel->consider_parallel = false;
+ }
+
/* For now, do all work in the (ORDERED, NULL) upperrel */
ordered_rel = fetch_upper_rel(root, UPPERREL_ORDERED, NULL);
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index b0c9e94..2704a48 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -104,6 +104,7 @@ static Node *fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context);
static bool fix_scan_expr_walker(Node *node, fix_scan_expr_context *context);
static void set_join_references(PlannerInfo *root, Join *join, int rtoffset);
static void set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset);
+static void set_param_references(PlannerInfo *root, Plan *plan);
static Node *convert_combining_aggrefs(Node *node, void *context);
static void set_dummy_tlist_references(Plan *plan, int rtoffset);
static indexed_tlist *build_tlist_index(List *tlist);
@@ -628,7 +629,10 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
case T_Gather:
case T_GatherMerge:
- set_upper_references(root, plan, rtoffset);
+ {
+ set_upper_references(root, plan, rtoffset);
+ set_param_references(root, plan);
+ }
break;
case T_Hash:
@@ -1784,6 +1788,47 @@ set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset)
}
/*
+ * set_param_references
+ * Initialize the initParam list in gather or gather merge node such that
+ * it contains reference of all the params that needs to be evaluated
+ * before execution of the node. It contains the initplan params that are
+ * being passed to the plan nodes below it.
+ */
+static void
+set_param_references(PlannerInfo *root, Plan *plan)
+{
+ Assert(IsA(plan, Gather) || IsA(plan, GatherMerge));
+
+ if (plan->lefttree->extParam)
+ {
+ PlannerInfo *proot;
+ Bitmapset *initSetParam = NULL;
+ ListCell *l;
+
+ for (proot = root; proot != NULL; proot = proot->parent_root)
+ {
+ foreach(l, proot->init_plans)
+ {
+ SubPlan *initsubplan = (SubPlan *) lfirst(l);
+ ListCell *l2;
+
+ foreach(l2, initsubplan->setParam)
+ {
+ initSetParam = bms_add_member(initSetParam, lfirst_int(l2));
+ }
+ }
+ }
+
+ if (IsA(plan, Gather))
+ ((Gather *) plan)->initParam = bms_intersect(plan->lefttree->extParam, initSetParam);
+ else if (IsA(plan, GatherMerge))
+ ((GatherMerge *) plan)->initParam = bms_intersect(plan->lefttree->extParam, initSetParam);
+ else
+ elog(ERROR, "unrecognized node type: %d", nodeTag(plan));
+ }
+}
+
+/*
* Recursively scan an expression tree and convert Aggrefs to the proper
* intermediate form for combining aggregates. This means (1) replacing each
* one's argument list with a single argument that is the original Aggref
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index ffbd3ee..0e0a375 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -2135,13 +2135,11 @@ SS_identify_outer_params(PlannerInfo *root)
}
/*
- * SS_charge_for_initplans - account for initplans in Path costs & parallelism
+ * SS_charge_for_initplans - account for initplans in Path costs
*
* If any initPlans have been created in the current query level, they will
* get attached to the Plan tree created from whichever Path we select from
- * the given rel. Increment all that rel's Paths' costs to account for them,
- * and make sure the paths get marked as parallel-unsafe, since we can't
- * currently transmit initPlans to parallel workers.
+ * the given rel. Increment all that rel's Paths' costs to account for them.
*
* This is separate from SS_attach_initplans because we might conditionally
* create more initPlans during create_plan(), depending on which Path we
@@ -2173,7 +2171,7 @@ SS_charge_for_initplans(PlannerInfo *root, RelOptInfo *final_rel)
}
/*
- * Now adjust the costs and parallel_safe flags.
+ * Now adjust the costs.
*/
foreach(lc, final_rel->pathlist)
{
@@ -2181,7 +2179,6 @@ SS_charge_for_initplans(PlannerInfo *root, RelOptInfo *final_rel)
path->startup_cost += initplan_cost;
path->total_cost += initplan_cost;
- path->parallel_safe = false;
}
/* We needn't do set_cheapest() here, caller will do it */
@@ -2935,3 +2932,29 @@ SS_make_initplan_from_plan(PlannerInfo *root,
/* Set costs of SubPlan using info from the plan tree */
cost_subplan(subroot, node, plan);
}
+
+/*
+ * is_initplan_below_current_query_level - is there any initplan present below
+ * current query level.
+ */
+bool
+is_initplan_below_current_query_level(PlannerInfo *root)
+{
+ ListCell *lc;
+
+ /*
+ * If the subplan corresponding to the subroot is an initPlan, it'll be
+ * attached to its parent root. Hence, we check the query level of its
+ * parent root and if any init_plans are attached there.
+ */
+ foreach(lc, root->glob->subroots)
+ {
+ PlannerInfo *subroot = (PlannerInfo *) lfirst(lc);
+ PlannerInfo *proot = subroot->parent_root;
+
+ if (proot->query_level > root->query_level && proot->init_plans)
+ return true;
+ }
+
+ return false;
+}
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 8b4425d..f1f63ce 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -91,6 +91,7 @@ typedef struct
typedef struct
{
+ PlannerInfo *root;
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 */
@@ -1069,6 +1070,7 @@ max_parallel_hazard(Query *parse)
{
max_parallel_hazard_context context;
+ context.root = NULL;
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_UNSAFE;
context.safe_param_ids = NIL;
@@ -1098,6 +1100,7 @@ is_parallel_safe(PlannerInfo *root, Node *node)
root->glob->nParamExec == 0)
return true;
/* Else use max_parallel_hazard's search logic, but stop on RESTRICTED */
+ context.root = root;
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_RESTRICTED;
context.safe_param_ids = NIL;
@@ -1222,21 +1225,47 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
}
/*
- * We can't pass Params to workers at the moment either, so they are also
- * parallel-restricted, unless they are PARAM_EXEC Params listed in
+ * As of now, we can only pass Params that refer to the same or parent
+ * query level (see generate_gather_paths) or they are listed in
* safe_param_ids, meaning they could be generated within the worker.
*/
else if (IsA(node, Param))
{
+ int paramid;
+ PlannerInfo *root;
Param *param = (Param *) node;
- if (param->paramkind != PARAM_EXEC ||
- !list_member_int(context->safe_param_ids, param->paramid))
+ if (list_member_int(context->safe_param_ids, param->paramid))
+ return false;
+
+ root = context->root;
+ paramid = ((Param *) node)->paramid;
+
+ if (root)
{
- if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
- return true;
+ PlannerInfo *proot;
+ ListCell *l;
+
+ for (proot = root; proot != NULL; proot = proot->parent_root)
+ {
+ foreach(l, proot->init_plans)
+ {
+ SubPlan *initsubplan = (SubPlan *) lfirst(l);
+ ListCell *l2;
+
+ foreach(l2, initsubplan->setParam)
+ {
+ int initparam = lfirst_int(l2);
+
+ if (paramid == initparam)
+ return false;
+ }
+ }
+ }
}
- return false; /* nothing to recurse to */
+
+ if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
+ return true;
}
/*
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 8ee0496..c44ad88 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -611,6 +611,7 @@ extern ExprEvalOp ExecEvalStepOp(ExprState *state, ExprEvalStep *op);
*/
extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
+extern void ExecEvalParamExecParams(Bitmapset *params, EState *estate);
extern void ExecEvalParamExtern(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
extern void ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op);
diff --git a/src/include/executor/execParallel.h b/src/include/executor/execParallel.h
index bd0a87f..72eda9e 100644
--- a/src/include/executor/execParallel.h
+++ b/src/include/executor/execParallel.h
@@ -33,7 +33,7 @@ typedef struct ParallelExecutorInfo
} ParallelExecutorInfo;
extern ParallelExecutorInfo *ExecInitParallelPlan(PlanState *planstate,
- EState *estate, int nworkers);
+ EState *estate, int nworkers, Bitmapset *initParam);
extern void ExecParallelFinish(ParallelExecutorInfo *pei);
extern void ExecParallelCleanup(ParallelExecutorInfo *pei);
extern void ExecParallelReinitialize(ParallelExecutorInfo *pei);
diff --git a/src/include/executor/nodeSubplan.h b/src/include/executor/nodeSubplan.h
index 5dbaeeb..40b1a34 100644
--- a/src/include/executor/nodeSubplan.h
+++ b/src/include/executor/nodeSubplan.h
@@ -28,4 +28,10 @@ extern void ExecReScanSetParamPlan(SubPlanState *node, PlanState *parent);
extern void ExecSetParamPlan(SubPlanState *node, ExprContext *econtext);
+extern Size EstimateInitPlanParamsSpace(ParamExecData *paramExecVals, Bitmapset *params);
+
+extern void SerializeInitPlanParams(ParamExecData *paramExecVals, Bitmapset *params, char **start_address);
+
+extern void RestoreInitPlanParams(char **start_address, ParamExecData *params);
+
#endif /* NODESUBPLAN_H */
diff --git a/src/include/nodes/params.h b/src/include/nodes/params.h
index 55219da..e96a1ea 100644
--- a/src/include/nodes/params.h
+++ b/src/include/nodes/params.h
@@ -98,6 +98,16 @@ typedef struct ParamExecData
{
void *execPlan; /* should be "SubPlanState *" */
Datum value;
+
+ /*
+ * parameter's datatype, or 0. This is required so that datum value can
+ * be read and used for other purposes like passing it to worker backend
+ * via shared memory. This is required only for initPlan's evaluation,
+ * however for consistency we set this for Subplan as well. We left it
+ * for other cases like CTE or RecursiveUnion cases where this structure
+ * is not used for evaluation of subplans.
+ */
+ Oid ptype;
bool isnull;
} ParamExecData;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index f1a1b24..16761ba 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -833,6 +833,8 @@ typedef struct Gather
int num_workers;
bool single_copy;
bool invisible; /* suppress EXPLAIN display (for testing)? */
+ Bitmapset *initParam; /* param id's of initplans which are referred
+ * at gather or one of it's child node */
} Gather;
/* ------------
@@ -849,6 +851,8 @@ typedef struct GatherMerge
Oid *sortOperators; /* OIDs of operators to sort them by */
Oid *collations; /* OIDs of collations */
bool *nullsFirst; /* NULLS FIRST/LAST directions */
+ Bitmapset *initParam; /* param id's of initplans which are referred
+ * at gather merge or one of it's child node */
} GatherMerge;
/* ----------------
diff --git a/src/include/optimizer/subselect.h b/src/include/optimizer/subselect.h
index ecd2011..bed6d2b 100644
--- a/src/include/optimizer/subselect.h
+++ b/src/include/optimizer/subselect.h
@@ -35,6 +35,7 @@ extern Param *SS_make_initplan_output_param(PlannerInfo *root,
extern void SS_make_initplan_from_plan(PlannerInfo *root,
PlannerInfo *subroot, Plan *plan,
Param *prm);
+extern bool is_initplan_below_current_query_level(PlannerInfo *root);
extern Param *assign_nestloop_param_var(PlannerInfo *root, Var *var);
extern Param *assign_nestloop_param_placeholdervar(PlannerInfo *root,
PlaceHolderVar *phv);
diff --git a/src/test/regress/expected/select_parallel.out b/src/test/regress/expected/select_parallel.out
index 3e35e96..56133fe 100644
--- a/src/test/regress/expected/select_parallel.out
+++ b/src/test/regress/expected/select_parallel.out
@@ -139,6 +139,39 @@ explain (costs off)
(4 rows)
alter table tenk2 reset (parallel_workers);
+--test parallel plan for a query containing initplan.
+set enable_indexscan = off;
+set enable_indexonlyscan = off;
+set enable_bitmapscan = off;
+alter table tenk2 set (parallel_workers = 2);
+explain (costs off)
+ select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+ QUERY PLAN
+------------------------------------------------------
+ Aggregate
+ InitPlan 1 (returns $1)
+ -> Finalize Aggregate
+ -> Gather
+ Workers Planned: 2
+ -> Partial Aggregate
+ -> Parallel Seq Scan on tenk2
+ -> Gather
+ Workers Planned: 4
+ Params Evaluated: $1
+ -> Parallel Seq Scan on tenk1
+ Filter: (unique1 = $1)
+(12 rows)
+
+select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+ count
+-------
+ 1
+(1 row)
+
+reset enable_indexscan;
+reset enable_indexonlyscan;
+reset enable_bitmapscan;
+alter table tenk2 reset (parallel_workers);
-- test parallel index scans.
set enable_seqscan to off;
set enable_bitmapscan to off;
diff --git a/src/test/regress/sql/select_parallel.sql b/src/test/regress/sql/select_parallel.sql
index d2d262c..57a4944 100644
--- a/src/test/regress/sql/select_parallel.sql
+++ b/src/test/regress/sql/select_parallel.sql
@@ -52,6 +52,21 @@ explain (costs off)
(select ten from tenk2);
alter table tenk2 reset (parallel_workers);
+--test parallel plan for a query containing initplan.
+set enable_indexscan = off;
+set enable_indexonlyscan = off;
+set enable_bitmapscan = off;
+alter table tenk2 set (parallel_workers = 2);
+
+explain (costs off)
+ select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+
+reset enable_indexscan;
+reset enable_indexonlyscan;
+reset enable_bitmapscan;
+alter table tenk2 reset (parallel_workers);
+
-- test parallel index scans.
set enable_seqscan to off;
set enable_bitmapscan to off;
On Wed, Aug 9, 2017 at 8:54 PM, Kuntal Ghosh <kuntalghosh.2007@gmail.com>
wrote:
On Wed, Aug 9, 2017 at 10:24 AM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:I tested the latest patch and the parallel plan is getting choose for
most
of
the init plans.Thanks for testing.
For the following query the parallel plan is not chosen. The query
contains
an init plan that refer the outer node.
postgres=# explain analyze select * from t1 where t1.i in (select t2.i
from
t2 where t1.k = (select max(k) from t3 where t3.i=t1.i));
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------
Seq Scan on t1 (cost=0.00..22426.28 rows=448 width=12) (actual
time=8.335..132.557 rows=2 loops=1)
Filter: (SubPlan 2)
Rows Removed by Filter: 894
SubPlan 2
-> Result (cost=16.27..31.26 rows=999 width=4) (actual
time=0.146..0.146 rows=0 loops=896)
One-Time Filter: (t1.k = $1)
InitPlan 1 (returns $1)
-> Aggregate (cost=16.25..16.27 rows=1 width=4) (actual
time=0.145..0.145 rows=1 loops=896)
-> Seq Scan on t3 (cost=0.00..16.25 rows=2 width=4)
(actual time=0.131..0.144 rows=0 loops=896)
Filter: (i = t1.i)
Rows Removed by Filter: 900
-> Seq Scan on t2 (cost=16.27..31.26 rows=999 width=4)(actual
time=0.012..0.013 rows=10 loops=2)
Planning time: 0.272 ms
Execution time: 132.623 ms
(14 rows)An observation is that the filter at Result node can't be pushed down
to the sequential scan on t2 because the filter is on t1. So, it has
to scan the complete t2 relation and send all the tuple to upper node,
a worst case for parallelism. Probably, this is the reason the
optimizer doesn't pick parallel plan for the above case.Just for clarification, do you see any changes in the plan after
forcing parallelism(parallel_tuple_cost, parallel_setup_cost,
min_parallel_table_scan_size=0)?
There is no plan change with parallel* GUC changes.
Regards,
Hari Babu
Fujitsu Australia
On Wed, Aug 9, 2017 at 9:26 PM, Amit Kapila <amit.kapila16@gmail.com> wrote:
On Wed, Aug 9, 2017 at 10:24 AM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:For the following query the parallel plan is not chosen. The query
contains
an init plan that refer the outer node.
We don't want to generate the parallel plan for such cases. Whenever
initplan refers to any outer node (aka correlated plan), it won't
generate a parallel plan. Also, for t2, it doesn't choose a parallel
plan because one-time filter refers to the outer node (again
correlated plan case). Basically, till now we don't support parallel
plan for any case where the correlated plan is used. So, it is
perfectly valid that it doesn't use parallel plan here.
Thanks for providing the details.
Here if you notice the parallel node t2 refers to the initplan which
can be parallelised after this patch. Basically, whenever the
initplan is attached at or above Gather node, we compute its value and
pass down to workers.
Thanks for the details. I checked the code also.
By the way, I tested the patch with by DML support for parallel patch to
check the returning of clause of insert, and all the returning clause init
plans
are parallel plans with this patch.
By the way, the patch doesn't apply on HEAD, so attached rebased patch.
Thanks for the updated patch. I have some comments and I am yet to finish
the review.
+ /*
+ * We don't want to generate gather or gather merge node if there are
+ * initplans at some query level below the current query level as those
+ * plans could be parallel-unsafe or undirect correlated plans. Ensure to
+ * mark all the partial and non-partial paths for a relation at this level
+ * to be parallel-unsafe.
+ */
+ if (is_initplan_below_current_query_level(root))
+ {
+ foreach(lc, rel->partial_pathlist)
+ {
+ Path *subpath = (Path *) lfirst(lc);
+
+ subpath->parallel_safe = false;
+ }
+
+ foreach(lc, rel->pathlist)
+ {
+ Path *subpath = (Path *) lfirst(lc);
+
+ subpath->parallel_safe = false;
+ }
+ return;
+ }
+
The above part of the code is almost same in mutiple places, is it possible
to change to function?
+ node->initParam = NULL;
This new parameter is set to NULL in make_gather function, the same
parameter
is added to GatherMerge structure also, but anyway this parameter is set to
NULL
makeNode macro, why only setting it to here, but not the other place.
Do we need to set it to default value such as NULL or false if it is
already the same value?
This is not related to the above parameter, for all existing parameters
also.
+ if (IsA(plan, Gather))
+ ((Gather *) plan)->initParam = bms_intersect(plan->lefttree->extParam,
initSetParam);
+ else if (IsA(plan, GatherMerge))
+ ((GatherMerge *) plan)->initParam =
bms_intersect(plan->lefttree->extParam, initSetParam);
+ else
+ elog(ERROR, "unrecognized node type: %d", nodeTag(plan));
The else case is not possible, because it is already validated for Gather
or GatherMerge.
Can we change it simple if and else?
Regards,
Hari Babu
Fujitsu Australia
On Wed, Aug 9, 2017 at 6:51 PM, Haribabu Kommi <kommi.haribabu@gmail.com> wrote:
On Wed, Aug 9, 2017 at 9:26 PM, Amit Kapila <amit.kapila16@gmail.com> wrote:
By the way, I tested the patch with by DML support for parallel patch to
check the returning of clause of insert, and all the returning clause init
plans
are parallel plans with this patch.
Good to know.
By the way, the patch doesn't apply on HEAD, so attached rebased patch.
Thanks for the updated patch. I have some comments and I am yet to finish
the review.+ /* + * We don't want to generate gather or gather merge node if there are + * initplans at some query level below the current query level as those + * plans could be parallel-unsafe or undirect correlated plans. Ensure to + * mark all the partial and non-partial paths for a relation at this level + * to be parallel-unsafe. + */ + if (is_initplan_below_current_query_level(root)) + { + foreach(lc, rel->partial_pathlist) + { + Path *subpath = (Path *) lfirst(lc); + + subpath->parallel_safe = false; + } + + foreach(lc, rel->pathlist) + { + Path *subpath = (Path *) lfirst(lc); + + subpath->parallel_safe = false; + } + return; + } +The above part of the code is almost same in mutiple places, is it possible
to change to function?
Sure, we can do that and I think that makes sense as well, so changed
accordingly in the attached patch.
+ node->initParam = NULL;
This new parameter is set to NULL in make_gather function, the same
parameter
is added to GatherMerge structure also, but anyway this parameter is set to
NULL
makeNode macro, why only setting it to here, but not the other place.Do we need to set it to default value such as NULL or false if it is already
the same value?
This is not related to the above parameter, for all existing parameters
also.
Strictly speaking, it is not required, but I have initialised at only
one of the place to make it consistent to near by code. We already do
similar stuff in some other functions like make_seqscan,
make_samplescan, ..
I don't see any value in trying to initialize it for GatherMerge as
well, so left it as it is.
+ if (IsA(plan, Gather)) + ((Gather *) plan)->initParam = bms_intersect(plan->lefttree->extParam, initSetParam); + else if (IsA(plan, GatherMerge)) + ((GatherMerge *) plan)->initParam = bms_intersect(plan->lefttree->extParam, initSetParam); + else + elog(ERROR, "unrecognized node type: %d", nodeTag(plan));The else case is not possible, because it is already validated for Gather or
GatherMerge.
Can we change it simple if and else?
As we already have an assert in this function to protect from any
other node type (nodes other than Gather and Gather Merge), it makes
sense to change the code to just if...else, so changed accordingly.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
pq_pushdown_initplan_v.6.patchapplication/octet-stream; name=pq_pushdown_initplan_v.6.patchDownload
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 7648201..83a26ec 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -107,6 +107,7 @@ static void show_tidbitmap_info(BitmapHeapScanState *planstate,
static void show_instrumentation_count(const char *qlabel, int which,
PlanState *planstate, ExplainState *es);
static void show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es);
+static void show_eval_params(Bitmapset *bms_params, ExplainState *es);
static const char *explain_get_index_name(Oid indexId);
static void show_buffer_usage(ExplainState *es, const BufferUsage *usage);
static void ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir,
@@ -637,7 +638,17 @@ ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc)
*/
ps = queryDesc->planstate;
if (IsA(ps, GatherState) &&((Gather *) ps->plan)->invisible)
+ {
+ List *initPlanState = NULL;
+ PlanState *save_ps;
+
+ /* initplans are always attached to the top node (cf standard_planner) */
+ save_ps = ps;
+ initPlanState = ps->initPlan;
ps = outerPlanState(ps);
+ ps->initPlan = initPlanState;
+ save_ps->initPlan = NIL;
+ }
ExplainNode(ps, NIL, NULL, NULL, es);
}
@@ -1434,6 +1445,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
planstate, es);
ExplainPropertyInteger("Workers Planned",
gather->num_workers, es);
+
+ /* Show params evaluated at gather node */
+ if (gather->initParam)
+ show_eval_params(gather->initParam, es);
+
if (es->analyze)
{
int nworkers;
@@ -1456,6 +1472,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
planstate, es);
ExplainPropertyInteger("Workers Planned",
gm->num_workers, es);
+
+ /* Show params evaluated at gather-merge node */
+ if (gm->initParam)
+ show_eval_params(gm->initParam, es);
+
if (es->analyze)
{
int nworkers;
@@ -2430,6 +2451,28 @@ show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es)
}
/*
+ * Show initplan params evaluated at gather or gather merge node.
+ */
+static void
+show_eval_params(Bitmapset *bms_params, ExplainState *es)
+{
+ int paramid = -1;
+ List *params = NIL;
+
+ Assert(bms_params);
+
+ while ((paramid = bms_next_member(bms_params, paramid)) >= 0)
+ {
+ char param[32];
+
+ snprintf(param, sizeof(param), "$%d", paramid);
+ params = lappend(params, pstrdup(param));
+ }
+
+ ExplainPropertyList("Params Evaluated", params, es);
+}
+
+/*
* Fetch the name of an index in an EXPLAIN
*
* We allow plugins to get control here so that plans involving hypothetical
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index f2a52f62..ed1449d 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -1861,6 +1861,33 @@ ExecEvalParamExec(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
}
/*
+ * ExecEvalParamExecParams
+ *
+ * Execute the subplan stored in PARAM_EXEC initplans params, if not executed
+ * till now.
+ */
+void
+ExecEvalParamExecParams(Bitmapset *params, EState *estate)
+{
+ ParamExecData *prm;
+ int paramid;
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ prm = &(estate->es_param_exec_vals[paramid]);
+
+ if (prm->execPlan != NULL)
+ {
+ /* Parameter not evaluated yet, so go do it */
+ ExecSetParamPlan(prm->execPlan, GetPerTupleExprContext(estate));
+ /* ExecSetParamPlan should have processed this param... */
+ Assert(prm->execPlan == NULL);
+ }
+ }
+}
+
+/*
* Evaluate a PARAM_EXTERN parameter.
*
* PARAM_EXTERN parameters must be sought in ecxt_param_list_info.
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index ce47f1d..d127031 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -23,12 +23,14 @@
#include "postgres.h"
+#include "executor/execExpr.h"
#include "executor/execParallel.h"
#include "executor/executor.h"
#include "executor/nodeBitmapHeapscan.h"
#include "executor/nodeCustom.h"
#include "executor/nodeForeignscan.h"
#include "executor/nodeSeqscan.h"
+#include "executor/nodeSubplan.h"
#include "executor/nodeIndexscan.h"
#include "executor/nodeIndexonlyscan.h"
#include "executor/tqueue.h"
@@ -54,6 +56,7 @@
#define PARALLEL_KEY_INSTRUMENTATION UINT64CONST(0xE000000000000005)
#define PARALLEL_KEY_DSA UINT64CONST(0xE000000000000006)
#define PARALLEL_KEY_QUERY_TEXT UINT64CONST(0xE000000000000007)
+#define PARALLEL_KEY_INITPLAN_PARAMS UINT64CONST(0xE000000000000008)
#define PARALLEL_TUPLE_QUEUE_SIZE 65536
@@ -381,7 +384,8 @@ ExecParallelReinitialize(ParallelExecutorInfo *pei)
* execution and return results to the main backend.
*/
ParallelExecutorInfo *
-ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers)
+ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
+ Bitmapset *initParam)
{
ParallelExecutorInfo *pei;
ParallelContext *pcxt;
@@ -390,10 +394,12 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers)
char *pstmt_data;
char *pstmt_space;
char *param_space;
+ char *initplan_param_space;
BufferUsage *bufusage_space;
SharedExecutorInstrumentation *instrumentation = NULL;
int pstmt_len;
int param_len;
+ int initplan_param_len;
int instrumentation_len = 0;
int instrument_offset = 0;
Size dsa_minsize = dsa_minimum_size();
@@ -405,6 +411,8 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers)
pei->finished = false;
pei->planstate = planstate;
+ ExecEvalParamExecParams(initParam, estate);
+
/* Fix up and serialize plan to be sent to workers. */
pstmt_data = ExecSerializePlan(planstate->plan, estate);
@@ -433,6 +441,11 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers)
shm_toc_estimate_chunk(&pcxt->estimator, param_len);
shm_toc_estimate_keys(&pcxt->estimator, 1);
+ /* Estimate space for initplan params. */
+ initplan_param_len = EstimateInitPlanParamsSpace(estate->es_param_exec_vals, initParam);
+ shm_toc_estimate_chunk(&pcxt->estimator, initplan_param_len);
+ shm_toc_estimate_keys(&pcxt->estimator, 1);
+
/*
* Estimate space for BufferUsage.
*
@@ -502,6 +515,11 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers)
shm_toc_insert(pcxt->toc, PARALLEL_KEY_PARAMS, param_space);
SerializeParamList(estate->es_param_list_info, ¶m_space);
+ /* Store serialized initplan params. */
+ initplan_param_space = shm_toc_allocate(pcxt->toc, initplan_param_len);
+ shm_toc_insert(pcxt->toc, PARALLEL_KEY_INITPLAN_PARAMS, initplan_param_space);
+ SerializeInitPlanParams(estate->es_param_exec_vals, initParam, &initplan_param_space);
+
/* Allocate space for each worker's BufferUsage; no need to initialize. */
bufusage_space = shm_toc_allocate(pcxt->toc,
mul_size(sizeof(BufferUsage), pcxt->nworkers));
@@ -692,6 +710,21 @@ ExecParallelGetReceiver(dsm_segment *seg, shm_toc *toc)
}
/*
+ * Copy the ParamExecData params corresponding to initplans from dynamic
+ * shared memory. This has to be done once the params are allocated by
+ * executor; that is after ExecutorStart().
+ */
+static void
+ExecParallelInitializeInitPlanParams(shm_toc *toc, ParamExecData *params)
+{
+ char *paramspace;
+
+ /* Reconstruct initplan params. */
+ paramspace = shm_toc_lookup(toc, PARALLEL_KEY_INITPLAN_PARAMS, false);
+ RestoreInitPlanParams(¶mspace, params);
+}
+
+/*
* Create a QueryDesc for the PlannedStmt we are to execute, and return it.
*/
static QueryDesc *
@@ -866,6 +899,7 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
/* Special executor initialization steps for parallel workers */
queryDesc->planstate->state->es_query_dsa = area;
+ ExecParallelInitializeInitPlanParams(toc, queryDesc->estate->es_param_exec_vals);
ExecParallelInitializeWorker(queryDesc->planstate, toc);
/* Run the plan */
diff --git a/src/backend/executor/nodeGather.c b/src/backend/executor/nodeGather.c
index e8d94ee..50152f4 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -156,7 +156,8 @@ ExecGather(PlanState *pstate)
if (!node->pei)
node->pei = ExecInitParallelPlan(node->ps.lefttree,
estate,
- gather->num_workers);
+ gather->num_workers,
+ gather->initParam);
/*
* Register backend workers. We might not get as many as we
diff --git a/src/backend/executor/nodeGatherMerge.c b/src/backend/executor/nodeGatherMerge.c
index 9a81e22..1e1db4f 100644
--- a/src/backend/executor/nodeGatherMerge.c
+++ b/src/backend/executor/nodeGatherMerge.c
@@ -190,7 +190,8 @@ ExecGatherMerge(PlanState *pstate)
if (!node->pei)
node->pei = ExecInitParallelPlan(node->ps.lefttree,
estate,
- gm->num_workers);
+ gm->num_workers,
+ gm->initParam);
/* Try to launch workers. */
pcxt = node->pei->pcxt;
diff --git a/src/backend/executor/nodeNestloop.c b/src/backend/executor/nodeNestloop.c
index 4447b7c..5c5dddb 100644
--- a/src/backend/executor/nodeNestloop.c
+++ b/src/backend/executor/nodeNestloop.c
@@ -130,6 +130,7 @@ ExecNestLoop(PlanState *pstate)
{
NestLoopParam *nlp = (NestLoopParam *) lfirst(lc);
int paramno = nlp->paramno;
+ TupleDesc tdesc = outerTupleSlot->tts_tupleDescriptor;
ParamExecData *prm;
prm = &(econtext->ecxt_param_exec_vals[paramno]);
@@ -140,6 +141,7 @@ ExecNestLoop(PlanState *pstate)
prm->value = slot_getattr(outerTupleSlot,
nlp->paramval->varattno,
&(prm->isnull));
+ prm->ptype = tdesc->attrs[nlp->paramval->varattno - 1]->atttypid;
/* Flag parameter value as changed */
innerPlan->chgParam = bms_add_member(innerPlan->chgParam,
paramno);
diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c
index fe10e80..9367175 100644
--- a/src/backend/executor/nodeSubplan.c
+++ b/src/backend/executor/nodeSubplan.c
@@ -30,12 +30,16 @@
#include <math.h>
#include "access/htup_details.h"
+#include "catalog/pg_type.h"
#include "executor/executor.h"
#include "executor/nodeSubplan.h"
#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
#include "miscadmin.h"
#include "optimizer/clauses.h"
+#include "storage/shmem.h"
#include "utils/array.h"
+#include "utils/datum.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
@@ -273,11 +277,13 @@ ExecScanSubPlan(SubPlanState *node,
forboth(l, subplan->parParam, pvar, node->args)
{
int paramid = lfirst_int(l);
+ ExprState *exprstate = (ExprState *) lfirst(pvar);
ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]);
- prm->value = ExecEvalExprSwitchContext((ExprState *) lfirst(pvar),
+ prm->value = ExecEvalExprSwitchContext(exprstate,
econtext,
&(prm->isnull));
+ prm->ptype = exprType((Node *) exprstate->expr);
planstate->chgParam = bms_add_member(planstate->chgParam, paramid);
}
@@ -390,6 +396,7 @@ ExecScanSubPlan(SubPlanState *node,
prmdata = &(econtext->ecxt_param_exec_vals[paramid]);
Assert(prmdata->execPlan == NULL);
prmdata->value = slot_getattr(slot, col, &(prmdata->isnull));
+ prmdata->ptype = tdesc->attrs[col - 1]->atttypid;
col++;
}
@@ -556,11 +563,13 @@ buildSubPlanHash(SubPlanState *node, ExprContext *econtext)
{
int paramid = lfirst_int(plst);
ParamExecData *prmdata;
+ TupleDesc tdesc = slot->tts_tupleDescriptor;
prmdata = &(innerecontext->ecxt_param_exec_vals[paramid]);
Assert(prmdata->execPlan == NULL);
prmdata->value = slot_getattr(slot, col,
&(prmdata->isnull));
+ prmdata->ptype = tdesc->attrs[col - 1]->atttypid;
col++;
}
slot = ExecProject(node->projRight);
@@ -924,6 +933,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
ListCell *l;
bool found = false;
ArrayBuildStateAny *astate = NULL;
+ Oid ptype;
if (subLinkType == ANY_SUBLINK ||
subLinkType == ALL_SUBLINK)
@@ -931,6 +941,8 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
if (subLinkType == CTE_SUBLINK)
elog(ERROR, "CTE subplans should not be executed via ExecSetParamPlan");
+ ptype = exprType((Node *) node->subplan);
+
/* Initialize ArrayBuildStateAny in caller's context, if needed */
if (subLinkType == ARRAY_SUBLINK)
astate = initArrayResultAny(subplan->firstColType,
@@ -953,11 +965,13 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
forboth(l, subplan->parParam, pvar, node->args)
{
int paramid = lfirst_int(l);
+ ExprState *exprstate = (ExprState *) lfirst(pvar);
ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]);
- prm->value = ExecEvalExprSwitchContext((ExprState *) lfirst(pvar),
+ prm->value = ExecEvalExprSwitchContext(exprstate,
econtext,
&(prm->isnull));
+ prm->ptype = exprType((Node *) exprstate->expr);
planstate->chgParam = bms_add_member(planstate->chgParam, paramid);
}
@@ -980,6 +994,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = BoolGetDatum(true);
+ prm->ptype = ptype;
prm->isnull = false;
found = true;
break;
@@ -1031,6 +1046,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = heap_getattr(node->curTuple, i, tdesc,
&(prm->isnull));
+ prm->ptype = tdesc->attrs[i - 1]->atttypid;
i++;
}
}
@@ -1053,6 +1069,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
true);
prm->execPlan = NULL;
prm->value = node->curArray;
+ prm->ptype = ptype;
prm->isnull = false;
}
else if (!found)
@@ -1065,6 +1082,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = BoolGetDatum(false);
+ prm->ptype = ptype;
prm->isnull = false;
}
else
@@ -1077,6 +1095,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = (Datum) 0;
+ prm->ptype = VOIDOID;
prm->isnull = true;
}
}
@@ -1207,3 +1226,136 @@ ExecAlternativeSubPlan(AlternativeSubPlanState *node,
return ExecSubPlan(activesp, econtext, isNull);
}
+
+/*
+ * Estimate the amount of space required to serialize the InitPlan params.
+ */
+Size
+EstimateInitPlanParamsSpace(ParamExecData *paramExecVals, Bitmapset *params)
+{
+ int paramid;
+ Size sz = sizeof(int);
+ ParamExecData *prm;
+
+ if (params == NULL)
+ return sz;
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ Oid typeOid;
+ int16 typLen;
+ bool typByVal;
+
+ prm = &(paramExecVals[paramid]);
+ typeOid = prm->ptype;
+
+ sz = add_size(sz, sizeof(int)); /* space for paramid */
+ sz = add_size(sz, sizeof(Oid)); /* space for type OID */
+
+ /* space for datum/isnull */
+ if (OidIsValid(typeOid))
+ get_typlenbyval(typeOid, &typLen, &typByVal);
+ else
+ {
+ /* If no type OID, assume by-value, like copyParamList does. */
+ typLen = sizeof(Datum);
+ typByVal = true;
+ }
+ sz = add_size(sz,
+ datumEstimateSpace(prm->value, prm->isnull, typByVal, typLen));
+ }
+ return sz;
+}
+
+/*
+ * Serialize ParamExecData params corresponding to initplans.
+ *
+ * We write the number of parameters first, as a 4-byte integer, and then
+ * write details for each parameter in turn. The details for each parameter
+ * consist of a 4-byte paramid (location of param in execution time internal
+ * parameter array), 4-byte type OID, and then the datum as serialized by
+ * datumSerialize().
+ *
+ * The above format is quite similar to the format used to serialize
+ * paramListInfo structure, so if we change either format, then consider to
+ * change at both the places.
+ */
+void
+SerializeInitPlanParams(ParamExecData *paramExecVals, Bitmapset *params,
+ char **start_address)
+{
+ int nparams;
+ int paramid;
+ ParamExecData *prm;
+
+ if (params == NULL)
+ nparams = 0;
+ else
+ nparams = bms_num_members(params);
+ memcpy(*start_address, &nparams, sizeof(int));
+ *start_address += sizeof(int);
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ Oid typeOid;
+ int16 typLen;
+ bool typByVal;
+
+ prm = &(paramExecVals[paramid]);
+ typeOid = prm->ptype;
+
+ /* Write paramid. */
+ memcpy(*start_address, ¶mid, sizeof(int));
+ *start_address += sizeof(int);
+
+ /* Write OID. */
+ memcpy(*start_address, &typeOid, sizeof(Oid));
+ *start_address += sizeof(Oid);
+
+ /* space for datum/isnull */
+ if (OidIsValid(typeOid))
+ get_typlenbyval(typeOid, &typLen, &typByVal);
+ else
+ {
+ /* If no type OID, assume by-value, like copyParamList does. */
+ typLen = sizeof(Datum);
+ typByVal = true;
+ }
+ datumSerialize(prm->value, prm->isnull, typByVal, typLen,
+ start_address);
+ }
+}
+
+/*
+ * Restore ParamExecData params corresponding to initplans.
+ */
+void
+RestoreInitPlanParams(char **start_address, ParamExecData *params)
+{
+ int nparams;
+ int i;
+ int paramid;
+
+ memcpy(&nparams, *start_address, sizeof(int));
+ *start_address += sizeof(int);
+
+ for (i = 0; i < nparams; i++)
+ {
+ ParamExecData *prm;
+
+ /* Read paramid */
+ memcpy(¶mid, *start_address, sizeof(int));
+ *start_address += sizeof(int);
+ prm = ¶ms[paramid];
+
+ /* Read type OID. */
+ memcpy(&prm->ptype, *start_address, sizeof(Oid));
+ *start_address += sizeof(Oid);
+
+ /* Read datum/isnull. */
+ prm->value = datumRestore(start_address, &prm->isnull);
+ prm->execPlan = NULL;
+ }
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 45a04b0..3e01857 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -363,6 +363,7 @@ _copyGather(const Gather *from)
COPY_SCALAR_FIELD(num_workers);
COPY_SCALAR_FIELD(single_copy);
COPY_SCALAR_FIELD(invisible);
+ COPY_BITMAPSET_FIELD(initParam);
return newnode;
}
@@ -389,6 +390,7 @@ _copyGatherMerge(const GatherMerge *from)
COPY_POINTER_FIELD(sortOperators, from->numCols * sizeof(Oid));
COPY_POINTER_FIELD(collations, from->numCols * sizeof(Oid));
COPY_POINTER_FIELD(nullsFirst, from->numCols * sizeof(bool));
+ COPY_BITMAPSET_FIELD(initParam);
return newnode;
}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 379d92a..95e9d37 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -481,6 +481,7 @@ _outGather(StringInfo str, const Gather *node)
WRITE_INT_FIELD(num_workers);
WRITE_BOOL_FIELD(single_copy);
WRITE_BOOL_FIELD(invisible);
+ WRITE_BITMAPSET_FIELD(initParam);
}
static void
@@ -510,6 +511,8 @@ _outGatherMerge(StringInfo str, const GatherMerge *node)
appendStringInfoString(str, " :nullsFirst");
for (i = 0; i < node->numCols; i++)
appendStringInfo(str, " %s", booltostr(node->nullsFirst[i]));
+
+ WRITE_BITMAPSET_FIELD(initParam);
}
static void
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 86c811d..c0ee903 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2165,6 +2165,7 @@ _readGather(void)
READ_INT_FIELD(num_workers);
READ_BOOL_FIELD(single_copy);
READ_BOOL_FIELD(invisible);
+ READ_BITMAPSET_FIELD(initParam);
READ_DONE();
}
@@ -2185,6 +2186,7 @@ _readGatherMerge(void)
READ_OID_ARRAY(sortOperators, local_node->numCols);
READ_OID_ARRAY(collations, local_node->numCols);
READ_BOOL_ARRAY(nullsFirst, local_node->numCols);
+ READ_BITMAPSET_FIELD(initParam);
READ_DONE();
}
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index f087ddb..50dce3c 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -38,6 +38,7 @@
#include "optimizer/planner.h"
#include "optimizer/prep.h"
#include "optimizer/restrictinfo.h"
+#include "optimizer/subselect.h"
#include "optimizer/tlist.h"
#include "optimizer/var.h"
#include "parser/parse_clause.h"
@@ -2202,6 +2203,14 @@ generate_gather_paths(PlannerInfo *root, RelOptInfo *rel)
Path *simple_gather_path;
ListCell *lc;
+ /*
+ * We don't want to generate gather or gather merge node if there are
+ * initplans at some query level below the current query level as those
+ * plans could be parallel-unsafe or undirect correlated plans.
+ */
+ if (contains_parallel_unsafe_param(root, rel))
+ return;
+
/* If there are no partial paths, there's nothing to do here. */
if (rel->partial_pathlist == NIL)
return;
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 5c934f2..adad0ef 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -6251,6 +6251,7 @@ make_gather(List *qptlist,
node->num_workers = nworkers;
node->single_copy = single_copy;
node->invisible = false;
+ node->initParam = NULL;
return node;
}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 2988c11..b965039 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -358,6 +358,14 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
{
Gather *gather = makeNode(Gather);
+ /*
+ * If there are any initPlans attached to the formerly-top plan node,
+ * move them up to the Gather node; same as we do for Material node in
+ * materialize_finished_plan.
+ */
+ gather->plan.initPlan = top_plan->initPlan;
+ top_plan->initPlan = NIL;
+
gather->plan.targetlist = top_plan->targetlist;
gather->plan.qual = NIL;
gather->plan.lefttree = top_plan;
@@ -3524,6 +3532,12 @@ create_grouping_paths(PlannerInfo *root,
ListCell *lc;
+ /*
+ * Don't parallelize the plan if there is an initplan below current query
+ * level. See generate_gather_paths() for detailed reason.
+ */
+ (void) contains_parallel_unsafe_param(root, input_rel);
+
/* For now, do all work in the (GROUP_AGG, NULL) upperrel */
grouped_rel = fetch_upper_rel(root, UPPERREL_GROUP_AGG, NULL);
@@ -4886,6 +4900,12 @@ create_ordered_paths(PlannerInfo *root,
RelOptInfo *ordered_rel;
ListCell *lc;
+ /*
+ * Don't parallelize the plan if there is an initplan below current query
+ * level. See generate_gather_paths() for detailed reason.
+ */
+ (void) contains_parallel_unsafe_param(root, input_rel);
+
/* For now, do all work in the (ORDERED, NULL) upperrel */
ordered_rel = fetch_upper_rel(root, UPPERREL_ORDERED, NULL);
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index b0c9e94..40c744b 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -104,6 +104,7 @@ static Node *fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context);
static bool fix_scan_expr_walker(Node *node, fix_scan_expr_context *context);
static void set_join_references(PlannerInfo *root, Join *join, int rtoffset);
static void set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset);
+static void set_param_references(PlannerInfo *root, Plan *plan);
static Node *convert_combining_aggrefs(Node *node, void *context);
static void set_dummy_tlist_references(Plan *plan, int rtoffset);
static indexed_tlist *build_tlist_index(List *tlist);
@@ -628,7 +629,10 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
case T_Gather:
case T_GatherMerge:
- set_upper_references(root, plan, rtoffset);
+ {
+ set_upper_references(root, plan, rtoffset);
+ set_param_references(root, plan);
+ }
break;
case T_Hash:
@@ -1784,6 +1788,45 @@ set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset)
}
/*
+ * set_param_references
+ * Initialize the initParam list in gather or gather merge node such that
+ * it contains reference of all the params that needs to be evaluated
+ * before execution of the node. It contains the initplan params that are
+ * being passed to the plan nodes below it.
+ */
+static void
+set_param_references(PlannerInfo *root, Plan *plan)
+{
+ Assert(IsA(plan, Gather) || IsA(plan, GatherMerge));
+
+ if (plan->lefttree->extParam)
+ {
+ PlannerInfo *proot;
+ Bitmapset *initSetParam = NULL;
+ ListCell *l;
+
+ for (proot = root; proot != NULL; proot = proot->parent_root)
+ {
+ foreach(l, proot->init_plans)
+ {
+ SubPlan *initsubplan = (SubPlan *) lfirst(l);
+ ListCell *l2;
+
+ foreach(l2, initsubplan->setParam)
+ {
+ initSetParam = bms_add_member(initSetParam, lfirst_int(l2));
+ }
+ }
+ }
+
+ if (IsA(plan, Gather))
+ ((Gather *) plan)->initParam = bms_intersect(plan->lefttree->extParam, initSetParam);
+ else
+ ((GatherMerge *) plan)->initParam = bms_intersect(plan->lefttree->extParam, initSetParam);
+ }
+}
+
+/*
* Recursively scan an expression tree and convert Aggrefs to the proper
* intermediate form for combining aggregates. This means (1) replacing each
* one's argument list with a single argument that is the original Aggref
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index ffbd3ee..ecdeb67 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -2135,13 +2135,11 @@ SS_identify_outer_params(PlannerInfo *root)
}
/*
- * SS_charge_for_initplans - account for initplans in Path costs & parallelism
+ * SS_charge_for_initplans - account for initplans in Path costs
*
* If any initPlans have been created in the current query level, they will
* get attached to the Plan tree created from whichever Path we select from
- * the given rel. Increment all that rel's Paths' costs to account for them,
- * and make sure the paths get marked as parallel-unsafe, since we can't
- * currently transmit initPlans to parallel workers.
+ * the given rel. Increment all that rel's Paths' costs to account for them.
*
* This is separate from SS_attach_initplans because we might conditionally
* create more initPlans during create_plan(), depending on which Path we
@@ -2173,7 +2171,7 @@ SS_charge_for_initplans(PlannerInfo *root, RelOptInfo *final_rel)
}
/*
- * Now adjust the costs and parallel_safe flags.
+ * Now adjust the costs.
*/
foreach(lc, final_rel->pathlist)
{
@@ -2181,7 +2179,6 @@ SS_charge_for_initplans(PlannerInfo *root, RelOptInfo *final_rel)
path->startup_cost += initplan_cost;
path->total_cost += initplan_cost;
- path->parallel_safe = false;
}
/* We needn't do set_cheapest() here, caller will do it */
@@ -2935,3 +2932,61 @@ SS_make_initplan_from_plan(PlannerInfo *root,
/* Set costs of SubPlan using info from the plan tree */
cost_subplan(subroot, node, plan);
}
+
+/*
+ * is_initplan_below_current_query_level - is there any initplan present below
+ * current query level.
+ */
+bool
+is_initplan_is_below_current_query_level(PlannerInfo *root)
+{
+ ListCell *lc;
+
+ /*
+ * If the subplan corresponding to the subroot is an initPlan, it'll be
+ * attached to its parent root. Hence, we check the query level of its
+ * parent root and if any init_plans are attached there.
+ */
+ foreach(lc, root->glob->subroots)
+ {
+ PlannerInfo *subroot = (PlannerInfo *) lfirst(lc);
+ PlannerInfo *proot = subroot->parent_root;
+
+ if (proot->query_level > root->query_level && proot->init_plans)
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * contains_parallel_unsafe_param - Check if there is any initplan present below
+ * current query level, mark all the partial and non-partial paths for a relation
+ * at this level as parallel-unsafe.
+ */
+bool
+contains_parallel_unsafe_param(PlannerInfo *root, RelOptInfo *rel)
+{
+ ListCell *lc;
+
+ if (is_initplan_is_below_current_query_level(root))
+ {
+ foreach(lc, rel->partial_pathlist)
+ {
+ Path *subpath = (Path *) lfirst(lc);
+
+ subpath->parallel_safe = false;
+ }
+ foreach(lc, rel->pathlist)
+ {
+ Path *subpath = (Path *) lfirst(lc);
+
+ subpath->parallel_safe = false;
+ }
+ rel->consider_parallel = false;
+
+ return true;
+ }
+
+ return false;
+}
\ No newline at end of file
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 8b4425d..f1f63ce 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -91,6 +91,7 @@ typedef struct
typedef struct
{
+ PlannerInfo *root;
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 */
@@ -1069,6 +1070,7 @@ max_parallel_hazard(Query *parse)
{
max_parallel_hazard_context context;
+ context.root = NULL;
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_UNSAFE;
context.safe_param_ids = NIL;
@@ -1098,6 +1100,7 @@ is_parallel_safe(PlannerInfo *root, Node *node)
root->glob->nParamExec == 0)
return true;
/* Else use max_parallel_hazard's search logic, but stop on RESTRICTED */
+ context.root = root;
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_RESTRICTED;
context.safe_param_ids = NIL;
@@ -1222,21 +1225,47 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
}
/*
- * We can't pass Params to workers at the moment either, so they are also
- * parallel-restricted, unless they are PARAM_EXEC Params listed in
+ * As of now, we can only pass Params that refer to the same or parent
+ * query level (see generate_gather_paths) or they are listed in
* safe_param_ids, meaning they could be generated within the worker.
*/
else if (IsA(node, Param))
{
+ int paramid;
+ PlannerInfo *root;
Param *param = (Param *) node;
- if (param->paramkind != PARAM_EXEC ||
- !list_member_int(context->safe_param_ids, param->paramid))
+ if (list_member_int(context->safe_param_ids, param->paramid))
+ return false;
+
+ root = context->root;
+ paramid = ((Param *) node)->paramid;
+
+ if (root)
{
- if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
- return true;
+ PlannerInfo *proot;
+ ListCell *l;
+
+ for (proot = root; proot != NULL; proot = proot->parent_root)
+ {
+ foreach(l, proot->init_plans)
+ {
+ SubPlan *initsubplan = (SubPlan *) lfirst(l);
+ ListCell *l2;
+
+ foreach(l2, initsubplan->setParam)
+ {
+ int initparam = lfirst_int(l2);
+
+ if (paramid == initparam)
+ return false;
+ }
+ }
+ }
}
- return false; /* nothing to recurse to */
+
+ if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
+ return true;
}
/*
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 8ee0496..c44ad88 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -611,6 +611,7 @@ extern ExprEvalOp ExecEvalStepOp(ExprState *state, ExprEvalStep *op);
*/
extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
+extern void ExecEvalParamExecParams(Bitmapset *params, EState *estate);
extern void ExecEvalParamExtern(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
extern void ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op);
diff --git a/src/include/executor/execParallel.h b/src/include/executor/execParallel.h
index bd0a87f..72eda9e 100644
--- a/src/include/executor/execParallel.h
+++ b/src/include/executor/execParallel.h
@@ -33,7 +33,7 @@ typedef struct ParallelExecutorInfo
} ParallelExecutorInfo;
extern ParallelExecutorInfo *ExecInitParallelPlan(PlanState *planstate,
- EState *estate, int nworkers);
+ EState *estate, int nworkers, Bitmapset *initParam);
extern void ExecParallelFinish(ParallelExecutorInfo *pei);
extern void ExecParallelCleanup(ParallelExecutorInfo *pei);
extern void ExecParallelReinitialize(ParallelExecutorInfo *pei);
diff --git a/src/include/executor/nodeSubplan.h b/src/include/executor/nodeSubplan.h
index 5dbaeeb..40b1a34 100644
--- a/src/include/executor/nodeSubplan.h
+++ b/src/include/executor/nodeSubplan.h
@@ -28,4 +28,10 @@ extern void ExecReScanSetParamPlan(SubPlanState *node, PlanState *parent);
extern void ExecSetParamPlan(SubPlanState *node, ExprContext *econtext);
+extern Size EstimateInitPlanParamsSpace(ParamExecData *paramExecVals, Bitmapset *params);
+
+extern void SerializeInitPlanParams(ParamExecData *paramExecVals, Bitmapset *params, char **start_address);
+
+extern void RestoreInitPlanParams(char **start_address, ParamExecData *params);
+
#endif /* NODESUBPLAN_H */
diff --git a/src/include/nodes/params.h b/src/include/nodes/params.h
index 55219da..e96a1ea 100644
--- a/src/include/nodes/params.h
+++ b/src/include/nodes/params.h
@@ -98,6 +98,16 @@ typedef struct ParamExecData
{
void *execPlan; /* should be "SubPlanState *" */
Datum value;
+
+ /*
+ * parameter's datatype, or 0. This is required so that datum value can
+ * be read and used for other purposes like passing it to worker backend
+ * via shared memory. This is required only for initPlan's evaluation,
+ * however for consistency we set this for Subplan as well. We left it
+ * for other cases like CTE or RecursiveUnion cases where this structure
+ * is not used for evaluation of subplans.
+ */
+ Oid ptype;
bool isnull;
} ParamExecData;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index f1a1b24..16761ba 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -833,6 +833,8 @@ typedef struct Gather
int num_workers;
bool single_copy;
bool invisible; /* suppress EXPLAIN display (for testing)? */
+ Bitmapset *initParam; /* param id's of initplans which are referred
+ * at gather or one of it's child node */
} Gather;
/* ------------
@@ -849,6 +851,8 @@ typedef struct GatherMerge
Oid *sortOperators; /* OIDs of operators to sort them by */
Oid *collations; /* OIDs of collations */
bool *nullsFirst; /* NULLS FIRST/LAST directions */
+ Bitmapset *initParam; /* param id's of initplans which are referred
+ * at gather merge or one of it's child node */
} GatherMerge;
/* ----------------
diff --git a/src/include/optimizer/subselect.h b/src/include/optimizer/subselect.h
index ecd2011..a35df55 100644
--- a/src/include/optimizer/subselect.h
+++ b/src/include/optimizer/subselect.h
@@ -35,6 +35,8 @@ extern Param *SS_make_initplan_output_param(PlannerInfo *root,
extern void SS_make_initplan_from_plan(PlannerInfo *root,
PlannerInfo *subroot, Plan *plan,
Param *prm);
+extern bool initplan_is_below_current_query_level(PlannerInfo *root);
+extern bool contains_parallel_unsafe_param(PlannerInfo *root, RelOptInfo *rel);
extern Param *assign_nestloop_param_var(PlannerInfo *root, Var *var);
extern Param *assign_nestloop_param_placeholdervar(PlannerInfo *root,
PlaceHolderVar *phv);
diff --git a/src/test/regress/expected/select_parallel.out b/src/test/regress/expected/select_parallel.out
index 3e35e96..56133fe 100644
--- a/src/test/regress/expected/select_parallel.out
+++ b/src/test/regress/expected/select_parallel.out
@@ -139,6 +139,39 @@ explain (costs off)
(4 rows)
alter table tenk2 reset (parallel_workers);
+--test parallel plan for a query containing initplan.
+set enable_indexscan = off;
+set enable_indexonlyscan = off;
+set enable_bitmapscan = off;
+alter table tenk2 set (parallel_workers = 2);
+explain (costs off)
+ select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+ QUERY PLAN
+------------------------------------------------------
+ Aggregate
+ InitPlan 1 (returns $1)
+ -> Finalize Aggregate
+ -> Gather
+ Workers Planned: 2
+ -> Partial Aggregate
+ -> Parallel Seq Scan on tenk2
+ -> Gather
+ Workers Planned: 4
+ Params Evaluated: $1
+ -> Parallel Seq Scan on tenk1
+ Filter: (unique1 = $1)
+(12 rows)
+
+select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+ count
+-------
+ 1
+(1 row)
+
+reset enable_indexscan;
+reset enable_indexonlyscan;
+reset enable_bitmapscan;
+alter table tenk2 reset (parallel_workers);
-- test parallel index scans.
set enable_seqscan to off;
set enable_bitmapscan to off;
diff --git a/src/test/regress/sql/select_parallel.sql b/src/test/regress/sql/select_parallel.sql
index d2d262c..57a4944 100644
--- a/src/test/regress/sql/select_parallel.sql
+++ b/src/test/regress/sql/select_parallel.sql
@@ -52,6 +52,21 @@ explain (costs off)
(select ten from tenk2);
alter table tenk2 reset (parallel_workers);
+--test parallel plan for a query containing initplan.
+set enable_indexscan = off;
+set enable_indexonlyscan = off;
+set enable_bitmapscan = off;
+alter table tenk2 set (parallel_workers = 2);
+
+explain (costs off)
+ select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+
+reset enable_indexscan;
+reset enable_indexonlyscan;
+reset enable_bitmapscan;
+alter table tenk2 reset (parallel_workers);
+
-- test parallel index scans.
set enable_seqscan to off;
set enable_bitmapscan to off;
On Fri, Aug 11, 2017 at 1:18 AM, Amit Kapila <amit.kapila16@gmail.com>
wrote:
On Wed, Aug 9, 2017 at 6:51 PM, Haribabu Kommi <kommi.haribabu@gmail.com>
wrote:+ if (IsA(plan, Gather)) + ((Gather *) plan)->initParam = bms_intersect(plan->lefttree->extParam, initSetParam); + else if (IsA(plan, GatherMerge)) + ((GatherMerge *) plan)->initParam = bms_intersect(plan->lefttree->extParam, initSetParam); + else + elog(ERROR, "unrecognized node type: %d", nodeTag(plan));The else case is not possible, because it is already validated for
Gather or
GatherMerge.
Can we change it simple if and else?As we already have an assert in this function to protect from any
other node type (nodes other than Gather and Gather Merge), it makes
sense to change the code to just if...else, so changed accordingly.
Thanks for the updated patch. Patch looks fine. I just have some
minor comments.
+ * ExecEvalParamExecParams
+ *
+ * Execute the subplan stored in PARAM_EXEC initplans params, if not
executed
+ * till now.
+ */
+void
+ExecEvalParamExecParams(Bitmapset *params, EState *estate)
I feel it is better to explain when this function executes the sub plans
that are
not executed till now? Means like in what scenario?
+ if (params == NULL)
+ nparams = 0;
+ else
+ nparams = bms_num_members(params);
bms_num_members return 0 in case if the params is NULL.
Is it fine to keep the specific check for NULL is required for performance
benefit
or just remove it? Anything is fine for me.
+ if (IsA(plan, Gather))
+ ((Gather *) plan)->initParam = bms_intersect(plan->lefttree->extParam,
initSetParam);
+ else
+ ((GatherMerge *) plan)->initParam =
bms_intersect(plan->lefttree->extParam, initSetParam);
I think the above code is to find out the common parameters that are prsent
in the external
and out params. It may be better to explain the logic in the comments.
Regards,
Hari Babu
Fujitsu Australia
On Sun, Aug 13, 2017 at 6:49 PM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:
On Fri, Aug 11, 2017 at 1:18 AM, Amit Kapila <amit.kapila16@gmail.com>
wrote:Thanks for the updated patch. Patch looks fine. I just have some
minor comments.+ * ExecEvalParamExecParams + * + * Execute the subplan stored in PARAM_EXEC initplans params, if not executed + * till now. + */ +void +ExecEvalParamExecParams(Bitmapset *params, EState *estate)I feel it is better to explain when this function executes the sub plans
that are
not executed till now? Means like in what scenario?
It just means that it will execute the same initplan (subplan) just
once in master backend even if it used in multiple places. This is
the same kind of usage as we have in ExecEvalParamExec. You can find
its usage by using some query like
explain analyse select sum(t1.i) from t1, t2 where t1.j=t2.j and t1.k
= (select count(k) from t3) and t1.k=t2.k;
Ensure you insert some rows in t1 and t2 that match the count from t3.
If you are using the schema and data given in Kuntal's script in email
above, then you need to insert something like
t1(900,900,900);t2(900,900,900);
It generates plan like below:
postgres=# explain analyse select sum(t1.i) from t1, t2 where
t1.j=t2.j and t1.k = (select count(k) from t3) and t1.k=t2.k;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------
Aggregate (cost=29.65..29.66 rows=1 width=8) (actual
time=22572.521..22572.521 rows=1 loops=1)
InitPlan 1 (returns $0)
-> Finalize Aggregate (cost=9.70..9.71 rows=1 width=8) (actual
time=4345.110..4345.111 rows=1 loops=1)
-> Gather (cost=9.69..9.70 rows=2 width=8) (actual
time=4285.019..4345.098 rows=3 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Partial Aggregate (cost=9.69..9.70 rows=1
width=8) (actual time=0.154..0.155 rows=1 loops=3)
-> Parallel Seq Scan on t3 (cost=0.00..8.75
rows=375 width=4) (actual time=0.011..0.090 rows=300 loops=3)
-> Nested Loop (cost=0.00..19.93 rows=1 width=4) (actual
time=22499.918..22572.512 rows=1 loops=1)
Join Filter: (t1.j = t2.j)
-> Gather (cost=0.00..9.67 rows=2 width=12) (actual
time=10521.356..10521.363 rows=1 loops=1)
Workers Planned: 2
Params Evaluated: $0
Workers Launched: 2
-> Parallel Seq Scan on t1 (cost=0.00..9.67 rows=1
width=12) (actual time=0.506..0.507 rows=0 loops=3)
Filter: (k = $0)
Rows Removed by Filter: 299
-> Materialize (cost=0.00..10.21 rows=2 width=8) (actual
time=11978.557..12051.142 rows=1 loops=1)
-> Gather (cost=0.00..10.20 rows=2 width=8) (actual
time=11978.530..12051.113 rows=1 loops=1)
Workers Planned: 2
Params Evaluated: $0
Workers Launched: 2
-> Parallel Seq Scan on t2 (cost=0.00..10.20
rows=1 width=8) (actual time=0.067..0.067 rows=0 loops=3)
Filter: (k = $0)
Rows Removed by Filter: 333
Planning time: 15103.237 ms
Execution time: 22574.703 ms
(27 rows)
You can notice that initplan is used at multiple nodes, but it will be
evaluated just once. If you want, I can add a sentence in the
comments, but I think this is somewhat obvious and the same use case
already exists. Let me know if you still think that comments need to
be expanded?
+ if (params == NULL) + nparams = 0; + else + nparams = bms_num_members(params);bms_num_members return 0 in case if the params is NULL.
Is it fine to keep the specific check for NULL is required for performance
benefit
or just remove it? Anything is fine for me.
I don't see any performance benefit here, so removed the if check.
+ if (IsA(plan, Gather)) + ((Gather *) plan)->initParam = bms_intersect(plan->lefttree->extParam, initSetParam); + else + ((GatherMerge *) plan)->initParam = bms_intersect(plan->lefttree->extParam, initSetParam);I think the above code is to find out the common parameters that are prsent
in the external
and out params.
Here, we want to save all the initplan params that can be used below
the gather node. extParam contains the set of all external PARAM_EXEC
params that can be used below gather node and we just want initplan
params out of those.
It may be better to explain the logic in the comments.
I have kept comments atop of the function set_param_references to
explain the context, but now I have added few more in the code as
suggested by you. See if that suffices the need.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
pq_pushdown_initplan_v7.patchapplication/octet-stream; name=pq_pushdown_initplan_v7.patchDownload
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 7648201..83a26ec 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -107,6 +107,7 @@ static void show_tidbitmap_info(BitmapHeapScanState *planstate,
static void show_instrumentation_count(const char *qlabel, int which,
PlanState *planstate, ExplainState *es);
static void show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es);
+static void show_eval_params(Bitmapset *bms_params, ExplainState *es);
static const char *explain_get_index_name(Oid indexId);
static void show_buffer_usage(ExplainState *es, const BufferUsage *usage);
static void ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir,
@@ -637,7 +638,17 @@ ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc)
*/
ps = queryDesc->planstate;
if (IsA(ps, GatherState) &&((Gather *) ps->plan)->invisible)
+ {
+ List *initPlanState = NULL;
+ PlanState *save_ps;
+
+ /* initplans are always attached to the top node (cf standard_planner) */
+ save_ps = ps;
+ initPlanState = ps->initPlan;
ps = outerPlanState(ps);
+ ps->initPlan = initPlanState;
+ save_ps->initPlan = NIL;
+ }
ExplainNode(ps, NIL, NULL, NULL, es);
}
@@ -1434,6 +1445,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
planstate, es);
ExplainPropertyInteger("Workers Planned",
gather->num_workers, es);
+
+ /* Show params evaluated at gather node */
+ if (gather->initParam)
+ show_eval_params(gather->initParam, es);
+
if (es->analyze)
{
int nworkers;
@@ -1456,6 +1472,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
planstate, es);
ExplainPropertyInteger("Workers Planned",
gm->num_workers, es);
+
+ /* Show params evaluated at gather-merge node */
+ if (gm->initParam)
+ show_eval_params(gm->initParam, es);
+
if (es->analyze)
{
int nworkers;
@@ -2430,6 +2451,28 @@ show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es)
}
/*
+ * Show initplan params evaluated at gather or gather merge node.
+ */
+static void
+show_eval_params(Bitmapset *bms_params, ExplainState *es)
+{
+ int paramid = -1;
+ List *params = NIL;
+
+ Assert(bms_params);
+
+ while ((paramid = bms_next_member(bms_params, paramid)) >= 0)
+ {
+ char param[32];
+
+ snprintf(param, sizeof(param), "$%d", paramid);
+ params = lappend(params, pstrdup(param));
+ }
+
+ ExplainPropertyList("Params Evaluated", params, es);
+}
+
+/*
* Fetch the name of an index in an EXPLAIN
*
* We allow plugins to get control here so that plans involving hypothetical
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index f2a52f62..ed1449d 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -1861,6 +1861,33 @@ ExecEvalParamExec(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
}
/*
+ * ExecEvalParamExecParams
+ *
+ * Execute the subplan stored in PARAM_EXEC initplans params, if not executed
+ * till now.
+ */
+void
+ExecEvalParamExecParams(Bitmapset *params, EState *estate)
+{
+ ParamExecData *prm;
+ int paramid;
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ prm = &(estate->es_param_exec_vals[paramid]);
+
+ if (prm->execPlan != NULL)
+ {
+ /* Parameter not evaluated yet, so go do it */
+ ExecSetParamPlan(prm->execPlan, GetPerTupleExprContext(estate));
+ /* ExecSetParamPlan should have processed this param... */
+ Assert(prm->execPlan == NULL);
+ }
+ }
+}
+
+/*
* Evaluate a PARAM_EXTERN parameter.
*
* PARAM_EXTERN parameters must be sought in ecxt_param_list_info.
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index ce47f1d..d127031 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -23,12 +23,14 @@
#include "postgres.h"
+#include "executor/execExpr.h"
#include "executor/execParallel.h"
#include "executor/executor.h"
#include "executor/nodeBitmapHeapscan.h"
#include "executor/nodeCustom.h"
#include "executor/nodeForeignscan.h"
#include "executor/nodeSeqscan.h"
+#include "executor/nodeSubplan.h"
#include "executor/nodeIndexscan.h"
#include "executor/nodeIndexonlyscan.h"
#include "executor/tqueue.h"
@@ -54,6 +56,7 @@
#define PARALLEL_KEY_INSTRUMENTATION UINT64CONST(0xE000000000000005)
#define PARALLEL_KEY_DSA UINT64CONST(0xE000000000000006)
#define PARALLEL_KEY_QUERY_TEXT UINT64CONST(0xE000000000000007)
+#define PARALLEL_KEY_INITPLAN_PARAMS UINT64CONST(0xE000000000000008)
#define PARALLEL_TUPLE_QUEUE_SIZE 65536
@@ -381,7 +384,8 @@ ExecParallelReinitialize(ParallelExecutorInfo *pei)
* execution and return results to the main backend.
*/
ParallelExecutorInfo *
-ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers)
+ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
+ Bitmapset *initParam)
{
ParallelExecutorInfo *pei;
ParallelContext *pcxt;
@@ -390,10 +394,12 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers)
char *pstmt_data;
char *pstmt_space;
char *param_space;
+ char *initplan_param_space;
BufferUsage *bufusage_space;
SharedExecutorInstrumentation *instrumentation = NULL;
int pstmt_len;
int param_len;
+ int initplan_param_len;
int instrumentation_len = 0;
int instrument_offset = 0;
Size dsa_minsize = dsa_minimum_size();
@@ -405,6 +411,8 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers)
pei->finished = false;
pei->planstate = planstate;
+ ExecEvalParamExecParams(initParam, estate);
+
/* Fix up and serialize plan to be sent to workers. */
pstmt_data = ExecSerializePlan(planstate->plan, estate);
@@ -433,6 +441,11 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers)
shm_toc_estimate_chunk(&pcxt->estimator, param_len);
shm_toc_estimate_keys(&pcxt->estimator, 1);
+ /* Estimate space for initplan params. */
+ initplan_param_len = EstimateInitPlanParamsSpace(estate->es_param_exec_vals, initParam);
+ shm_toc_estimate_chunk(&pcxt->estimator, initplan_param_len);
+ shm_toc_estimate_keys(&pcxt->estimator, 1);
+
/*
* Estimate space for BufferUsage.
*
@@ -502,6 +515,11 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers)
shm_toc_insert(pcxt->toc, PARALLEL_KEY_PARAMS, param_space);
SerializeParamList(estate->es_param_list_info, ¶m_space);
+ /* Store serialized initplan params. */
+ initplan_param_space = shm_toc_allocate(pcxt->toc, initplan_param_len);
+ shm_toc_insert(pcxt->toc, PARALLEL_KEY_INITPLAN_PARAMS, initplan_param_space);
+ SerializeInitPlanParams(estate->es_param_exec_vals, initParam, &initplan_param_space);
+
/* Allocate space for each worker's BufferUsage; no need to initialize. */
bufusage_space = shm_toc_allocate(pcxt->toc,
mul_size(sizeof(BufferUsage), pcxt->nworkers));
@@ -692,6 +710,21 @@ ExecParallelGetReceiver(dsm_segment *seg, shm_toc *toc)
}
/*
+ * Copy the ParamExecData params corresponding to initplans from dynamic
+ * shared memory. This has to be done once the params are allocated by
+ * executor; that is after ExecutorStart().
+ */
+static void
+ExecParallelInitializeInitPlanParams(shm_toc *toc, ParamExecData *params)
+{
+ char *paramspace;
+
+ /* Reconstruct initplan params. */
+ paramspace = shm_toc_lookup(toc, PARALLEL_KEY_INITPLAN_PARAMS, false);
+ RestoreInitPlanParams(¶mspace, params);
+}
+
+/*
* Create a QueryDesc for the PlannedStmt we are to execute, and return it.
*/
static QueryDesc *
@@ -866,6 +899,7 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
/* Special executor initialization steps for parallel workers */
queryDesc->planstate->state->es_query_dsa = area;
+ ExecParallelInitializeInitPlanParams(toc, queryDesc->estate->es_param_exec_vals);
ExecParallelInitializeWorker(queryDesc->planstate, toc);
/* Run the plan */
diff --git a/src/backend/executor/nodeGather.c b/src/backend/executor/nodeGather.c
index e8d94ee..50152f4 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -156,7 +156,8 @@ ExecGather(PlanState *pstate)
if (!node->pei)
node->pei = ExecInitParallelPlan(node->ps.lefttree,
estate,
- gather->num_workers);
+ gather->num_workers,
+ gather->initParam);
/*
* Register backend workers. We might not get as many as we
diff --git a/src/backend/executor/nodeGatherMerge.c b/src/backend/executor/nodeGatherMerge.c
index 9a81e22..1e1db4f 100644
--- a/src/backend/executor/nodeGatherMerge.c
+++ b/src/backend/executor/nodeGatherMerge.c
@@ -190,7 +190,8 @@ ExecGatherMerge(PlanState *pstate)
if (!node->pei)
node->pei = ExecInitParallelPlan(node->ps.lefttree,
estate,
- gm->num_workers);
+ gm->num_workers,
+ gm->initParam);
/* Try to launch workers. */
pcxt = node->pei->pcxt;
diff --git a/src/backend/executor/nodeNestloop.c b/src/backend/executor/nodeNestloop.c
index 4447b7c..5c5dddb 100644
--- a/src/backend/executor/nodeNestloop.c
+++ b/src/backend/executor/nodeNestloop.c
@@ -130,6 +130,7 @@ ExecNestLoop(PlanState *pstate)
{
NestLoopParam *nlp = (NestLoopParam *) lfirst(lc);
int paramno = nlp->paramno;
+ TupleDesc tdesc = outerTupleSlot->tts_tupleDescriptor;
ParamExecData *prm;
prm = &(econtext->ecxt_param_exec_vals[paramno]);
@@ -140,6 +141,7 @@ ExecNestLoop(PlanState *pstate)
prm->value = slot_getattr(outerTupleSlot,
nlp->paramval->varattno,
&(prm->isnull));
+ prm->ptype = tdesc->attrs[nlp->paramval->varattno - 1]->atttypid;
/* Flag parameter value as changed */
innerPlan->chgParam = bms_add_member(innerPlan->chgParam,
paramno);
diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c
index fe10e80..b1bc7a7 100644
--- a/src/backend/executor/nodeSubplan.c
+++ b/src/backend/executor/nodeSubplan.c
@@ -30,12 +30,16 @@
#include <math.h>
#include "access/htup_details.h"
+#include "catalog/pg_type.h"
#include "executor/executor.h"
#include "executor/nodeSubplan.h"
#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
#include "miscadmin.h"
#include "optimizer/clauses.h"
+#include "storage/shmem.h"
#include "utils/array.h"
+#include "utils/datum.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
@@ -273,11 +277,13 @@ ExecScanSubPlan(SubPlanState *node,
forboth(l, subplan->parParam, pvar, node->args)
{
int paramid = lfirst_int(l);
+ ExprState *exprstate = (ExprState *) lfirst(pvar);
ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]);
- prm->value = ExecEvalExprSwitchContext((ExprState *) lfirst(pvar),
+ prm->value = ExecEvalExprSwitchContext(exprstate,
econtext,
&(prm->isnull));
+ prm->ptype = exprType((Node *) exprstate->expr);
planstate->chgParam = bms_add_member(planstate->chgParam, paramid);
}
@@ -390,6 +396,7 @@ ExecScanSubPlan(SubPlanState *node,
prmdata = &(econtext->ecxt_param_exec_vals[paramid]);
Assert(prmdata->execPlan == NULL);
prmdata->value = slot_getattr(slot, col, &(prmdata->isnull));
+ prmdata->ptype = tdesc->attrs[col - 1]->atttypid;
col++;
}
@@ -556,11 +563,13 @@ buildSubPlanHash(SubPlanState *node, ExprContext *econtext)
{
int paramid = lfirst_int(plst);
ParamExecData *prmdata;
+ TupleDesc tdesc = slot->tts_tupleDescriptor;
prmdata = &(innerecontext->ecxt_param_exec_vals[paramid]);
Assert(prmdata->execPlan == NULL);
prmdata->value = slot_getattr(slot, col,
&(prmdata->isnull));
+ prmdata->ptype = tdesc->attrs[col - 1]->atttypid;
col++;
}
slot = ExecProject(node->projRight);
@@ -924,6 +933,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
ListCell *l;
bool found = false;
ArrayBuildStateAny *astate = NULL;
+ Oid ptype;
if (subLinkType == ANY_SUBLINK ||
subLinkType == ALL_SUBLINK)
@@ -931,6 +941,8 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
if (subLinkType == CTE_SUBLINK)
elog(ERROR, "CTE subplans should not be executed via ExecSetParamPlan");
+ ptype = exprType((Node *) node->subplan);
+
/* Initialize ArrayBuildStateAny in caller's context, if needed */
if (subLinkType == ARRAY_SUBLINK)
astate = initArrayResultAny(subplan->firstColType,
@@ -953,11 +965,13 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
forboth(l, subplan->parParam, pvar, node->args)
{
int paramid = lfirst_int(l);
+ ExprState *exprstate = (ExprState *) lfirst(pvar);
ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]);
- prm->value = ExecEvalExprSwitchContext((ExprState *) lfirst(pvar),
+ prm->value = ExecEvalExprSwitchContext(exprstate,
econtext,
&(prm->isnull));
+ prm->ptype = exprType((Node *) exprstate->expr);
planstate->chgParam = bms_add_member(planstate->chgParam, paramid);
}
@@ -980,6 +994,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = BoolGetDatum(true);
+ prm->ptype = ptype;
prm->isnull = false;
found = true;
break;
@@ -1031,6 +1046,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = heap_getattr(node->curTuple, i, tdesc,
&(prm->isnull));
+ prm->ptype = tdesc->attrs[i - 1]->atttypid;
i++;
}
}
@@ -1053,6 +1069,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
true);
prm->execPlan = NULL;
prm->value = node->curArray;
+ prm->ptype = ptype;
prm->isnull = false;
}
else if (!found)
@@ -1065,6 +1082,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = BoolGetDatum(false);
+ prm->ptype = ptype;
prm->isnull = false;
}
else
@@ -1077,6 +1095,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = (Datum) 0;
+ prm->ptype = VOIDOID;
prm->isnull = true;
}
}
@@ -1207,3 +1226,133 @@ ExecAlternativeSubPlan(AlternativeSubPlanState *node,
return ExecSubPlan(activesp, econtext, isNull);
}
+
+/*
+ * Estimate the amount of space required to serialize the InitPlan params.
+ */
+Size
+EstimateInitPlanParamsSpace(ParamExecData *paramExecVals, Bitmapset *params)
+{
+ int paramid;
+ Size sz = sizeof(int);
+ ParamExecData *prm;
+
+ if (params == NULL)
+ return sz;
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ Oid typeOid;
+ int16 typLen;
+ bool typByVal;
+
+ prm = &(paramExecVals[paramid]);
+ typeOid = prm->ptype;
+
+ sz = add_size(sz, sizeof(int)); /* space for paramid */
+ sz = add_size(sz, sizeof(Oid)); /* space for type OID */
+
+ /* space for datum/isnull */
+ if (OidIsValid(typeOid))
+ get_typlenbyval(typeOid, &typLen, &typByVal);
+ else
+ {
+ /* If no type OID, assume by-value, like copyParamList does. */
+ typLen = sizeof(Datum);
+ typByVal = true;
+ }
+ sz = add_size(sz,
+ datumEstimateSpace(prm->value, prm->isnull, typByVal, typLen));
+ }
+ return sz;
+}
+
+/*
+ * Serialize ParamExecData params corresponding to initplans.
+ *
+ * We write the number of parameters first, as a 4-byte integer, and then
+ * write details for each parameter in turn. The details for each parameter
+ * consist of a 4-byte paramid (location of param in execution time internal
+ * parameter array), 4-byte type OID, and then the datum as serialized by
+ * datumSerialize().
+ *
+ * The above format is quite similar to the format used to serialize
+ * paramListInfo structure, so if we change either format, then consider to
+ * change at both the places.
+ */
+void
+SerializeInitPlanParams(ParamExecData *paramExecVals, Bitmapset *params,
+ char **start_address)
+{
+ int nparams;
+ int paramid;
+ ParamExecData *prm;
+
+ nparams = bms_num_members(params);
+ memcpy(*start_address, &nparams, sizeof(int));
+ *start_address += sizeof(int);
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ Oid typeOid;
+ int16 typLen;
+ bool typByVal;
+
+ prm = &(paramExecVals[paramid]);
+ typeOid = prm->ptype;
+
+ /* Write paramid. */
+ memcpy(*start_address, ¶mid, sizeof(int));
+ *start_address += sizeof(int);
+
+ /* Write OID. */
+ memcpy(*start_address, &typeOid, sizeof(Oid));
+ *start_address += sizeof(Oid);
+
+ /* space for datum/isnull */
+ if (OidIsValid(typeOid))
+ get_typlenbyval(typeOid, &typLen, &typByVal);
+ else
+ {
+ /* If no type OID, assume by-value, like copyParamList does. */
+ typLen = sizeof(Datum);
+ typByVal = true;
+ }
+ datumSerialize(prm->value, prm->isnull, typByVal, typLen,
+ start_address);
+ }
+}
+
+/*
+ * Restore ParamExecData params corresponding to initplans.
+ */
+void
+RestoreInitPlanParams(char **start_address, ParamExecData *params)
+{
+ int nparams;
+ int i;
+ int paramid;
+
+ memcpy(&nparams, *start_address, sizeof(int));
+ *start_address += sizeof(int);
+
+ for (i = 0; i < nparams; i++)
+ {
+ ParamExecData *prm;
+
+ /* Read paramid */
+ memcpy(¶mid, *start_address, sizeof(int));
+ *start_address += sizeof(int);
+ prm = ¶ms[paramid];
+
+ /* Read type OID. */
+ memcpy(&prm->ptype, *start_address, sizeof(Oid));
+ *start_address += sizeof(Oid);
+
+ /* Read datum/isnull. */
+ prm->value = datumRestore(start_address, &prm->isnull);
+ prm->execPlan = NULL;
+ }
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 45a04b0..3e01857 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -363,6 +363,7 @@ _copyGather(const Gather *from)
COPY_SCALAR_FIELD(num_workers);
COPY_SCALAR_FIELD(single_copy);
COPY_SCALAR_FIELD(invisible);
+ COPY_BITMAPSET_FIELD(initParam);
return newnode;
}
@@ -389,6 +390,7 @@ _copyGatherMerge(const GatherMerge *from)
COPY_POINTER_FIELD(sortOperators, from->numCols * sizeof(Oid));
COPY_POINTER_FIELD(collations, from->numCols * sizeof(Oid));
COPY_POINTER_FIELD(nullsFirst, from->numCols * sizeof(bool));
+ COPY_BITMAPSET_FIELD(initParam);
return newnode;
}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 379d92a..95e9d37 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -481,6 +481,7 @@ _outGather(StringInfo str, const Gather *node)
WRITE_INT_FIELD(num_workers);
WRITE_BOOL_FIELD(single_copy);
WRITE_BOOL_FIELD(invisible);
+ WRITE_BITMAPSET_FIELD(initParam);
}
static void
@@ -510,6 +511,8 @@ _outGatherMerge(StringInfo str, const GatherMerge *node)
appendStringInfoString(str, " :nullsFirst");
for (i = 0; i < node->numCols; i++)
appendStringInfo(str, " %s", booltostr(node->nullsFirst[i]));
+
+ WRITE_BITMAPSET_FIELD(initParam);
}
static void
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 86c811d..c0ee903 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2165,6 +2165,7 @@ _readGather(void)
READ_INT_FIELD(num_workers);
READ_BOOL_FIELD(single_copy);
READ_BOOL_FIELD(invisible);
+ READ_BITMAPSET_FIELD(initParam);
READ_DONE();
}
@@ -2185,6 +2186,7 @@ _readGatherMerge(void)
READ_OID_ARRAY(sortOperators, local_node->numCols);
READ_OID_ARRAY(collations, local_node->numCols);
READ_BOOL_ARRAY(nullsFirst, local_node->numCols);
+ READ_BITMAPSET_FIELD(initParam);
READ_DONE();
}
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index f087ddb..50dce3c 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -38,6 +38,7 @@
#include "optimizer/planner.h"
#include "optimizer/prep.h"
#include "optimizer/restrictinfo.h"
+#include "optimizer/subselect.h"
#include "optimizer/tlist.h"
#include "optimizer/var.h"
#include "parser/parse_clause.h"
@@ -2202,6 +2203,14 @@ generate_gather_paths(PlannerInfo *root, RelOptInfo *rel)
Path *simple_gather_path;
ListCell *lc;
+ /*
+ * We don't want to generate gather or gather merge node if there are
+ * initplans at some query level below the current query level as those
+ * plans could be parallel-unsafe or undirect correlated plans.
+ */
+ if (contains_parallel_unsafe_param(root, rel))
+ return;
+
/* If there are no partial paths, there's nothing to do here. */
if (rel->partial_pathlist == NIL)
return;
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 5c934f2..adad0ef 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -6251,6 +6251,7 @@ make_gather(List *qptlist,
node->num_workers = nworkers;
node->single_copy = single_copy;
node->invisible = false;
+ node->initParam = NULL;
return node;
}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 2988c11..b965039 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -358,6 +358,14 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
{
Gather *gather = makeNode(Gather);
+ /*
+ * If there are any initPlans attached to the formerly-top plan node,
+ * move them up to the Gather node; same as we do for Material node in
+ * materialize_finished_plan.
+ */
+ gather->plan.initPlan = top_plan->initPlan;
+ top_plan->initPlan = NIL;
+
gather->plan.targetlist = top_plan->targetlist;
gather->plan.qual = NIL;
gather->plan.lefttree = top_plan;
@@ -3524,6 +3532,12 @@ create_grouping_paths(PlannerInfo *root,
ListCell *lc;
+ /*
+ * Don't parallelize the plan if there is an initplan below current query
+ * level. See generate_gather_paths() for detailed reason.
+ */
+ (void) contains_parallel_unsafe_param(root, input_rel);
+
/* For now, do all work in the (GROUP_AGG, NULL) upperrel */
grouped_rel = fetch_upper_rel(root, UPPERREL_GROUP_AGG, NULL);
@@ -4886,6 +4900,12 @@ create_ordered_paths(PlannerInfo *root,
RelOptInfo *ordered_rel;
ListCell *lc;
+ /*
+ * Don't parallelize the plan if there is an initplan below current query
+ * level. See generate_gather_paths() for detailed reason.
+ */
+ (void) contains_parallel_unsafe_param(root, input_rel);
+
/* For now, do all work in the (ORDERED, NULL) upperrel */
ordered_rel = fetch_upper_rel(root, UPPERREL_ORDERED, NULL);
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index b0c9e94..5fa2bb1 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -104,6 +104,7 @@ static Node *fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context);
static bool fix_scan_expr_walker(Node *node, fix_scan_expr_context *context);
static void set_join_references(PlannerInfo *root, Join *join, int rtoffset);
static void set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset);
+static void set_param_references(PlannerInfo *root, Plan *plan);
static Node *convert_combining_aggrefs(Node *node, void *context);
static void set_dummy_tlist_references(Plan *plan, int rtoffset);
static indexed_tlist *build_tlist_index(List *tlist);
@@ -628,7 +629,10 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
case T_Gather:
case T_GatherMerge:
- set_upper_references(root, plan, rtoffset);
+ {
+ set_upper_references(root, plan, rtoffset);
+ set_param_references(root, plan);
+ }
break;
case T_Hash:
@@ -1784,6 +1788,49 @@ set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset)
}
/*
+ * set_param_references
+ * Initialize the initParam list in gather or gather merge node such that
+ * it contains reference of all the params that needs to be evaluated
+ * before execution of the node. It contains the initplan params that are
+ * being passed to the plan nodes below it.
+ */
+static void
+set_param_references(PlannerInfo *root, Plan *plan)
+{
+ Assert(IsA(plan, Gather) || IsA(plan, GatherMerge));
+
+ if (plan->lefttree->extParam)
+ {
+ PlannerInfo *proot;
+ Bitmapset *initSetParam = NULL;
+ ListCell *l;
+
+ for (proot = root; proot != NULL; proot = proot->parent_root)
+ {
+ foreach(l, proot->init_plans)
+ {
+ SubPlan *initsubplan = (SubPlan *) lfirst(l);
+ ListCell *l2;
+
+ foreach(l2, initsubplan->setParam)
+ {
+ initSetParam = bms_add_member(initSetParam, lfirst_int(l2));
+ }
+ }
+ }
+
+ /*
+ * Remember the list of all external initplan params that are used by
+ * the children of gather or gather merge node.
+ */
+ if (IsA(plan, Gather))
+ ((Gather *) plan)->initParam = bms_intersect(plan->lefttree->extParam, initSetParam);
+ else
+ ((GatherMerge *) plan)->initParam = bms_intersect(plan->lefttree->extParam, initSetParam);
+ }
+}
+
+/*
* Recursively scan an expression tree and convert Aggrefs to the proper
* intermediate form for combining aggregates. This means (1) replacing each
* one's argument list with a single argument that is the original Aggref
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index ffbd3ee..ecdeb67 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -2135,13 +2135,11 @@ SS_identify_outer_params(PlannerInfo *root)
}
/*
- * SS_charge_for_initplans - account for initplans in Path costs & parallelism
+ * SS_charge_for_initplans - account for initplans in Path costs
*
* If any initPlans have been created in the current query level, they will
* get attached to the Plan tree created from whichever Path we select from
- * the given rel. Increment all that rel's Paths' costs to account for them,
- * and make sure the paths get marked as parallel-unsafe, since we can't
- * currently transmit initPlans to parallel workers.
+ * the given rel. Increment all that rel's Paths' costs to account for them.
*
* This is separate from SS_attach_initplans because we might conditionally
* create more initPlans during create_plan(), depending on which Path we
@@ -2173,7 +2171,7 @@ SS_charge_for_initplans(PlannerInfo *root, RelOptInfo *final_rel)
}
/*
- * Now adjust the costs and parallel_safe flags.
+ * Now adjust the costs.
*/
foreach(lc, final_rel->pathlist)
{
@@ -2181,7 +2179,6 @@ SS_charge_for_initplans(PlannerInfo *root, RelOptInfo *final_rel)
path->startup_cost += initplan_cost;
path->total_cost += initplan_cost;
- path->parallel_safe = false;
}
/* We needn't do set_cheapest() here, caller will do it */
@@ -2935,3 +2932,61 @@ SS_make_initplan_from_plan(PlannerInfo *root,
/* Set costs of SubPlan using info from the plan tree */
cost_subplan(subroot, node, plan);
}
+
+/*
+ * is_initplan_below_current_query_level - is there any initplan present below
+ * current query level.
+ */
+bool
+is_initplan_is_below_current_query_level(PlannerInfo *root)
+{
+ ListCell *lc;
+
+ /*
+ * If the subplan corresponding to the subroot is an initPlan, it'll be
+ * attached to its parent root. Hence, we check the query level of its
+ * parent root and if any init_plans are attached there.
+ */
+ foreach(lc, root->glob->subroots)
+ {
+ PlannerInfo *subroot = (PlannerInfo *) lfirst(lc);
+ PlannerInfo *proot = subroot->parent_root;
+
+ if (proot->query_level > root->query_level && proot->init_plans)
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * contains_parallel_unsafe_param - Check if there is any initplan present below
+ * current query level, mark all the partial and non-partial paths for a relation
+ * at this level as parallel-unsafe.
+ */
+bool
+contains_parallel_unsafe_param(PlannerInfo *root, RelOptInfo *rel)
+{
+ ListCell *lc;
+
+ if (is_initplan_is_below_current_query_level(root))
+ {
+ foreach(lc, rel->partial_pathlist)
+ {
+ Path *subpath = (Path *) lfirst(lc);
+
+ subpath->parallel_safe = false;
+ }
+ foreach(lc, rel->pathlist)
+ {
+ Path *subpath = (Path *) lfirst(lc);
+
+ subpath->parallel_safe = false;
+ }
+ rel->consider_parallel = false;
+
+ return true;
+ }
+
+ return false;
+}
\ No newline at end of file
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 8b4425d..f1f63ce 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -91,6 +91,7 @@ typedef struct
typedef struct
{
+ PlannerInfo *root;
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 */
@@ -1069,6 +1070,7 @@ max_parallel_hazard(Query *parse)
{
max_parallel_hazard_context context;
+ context.root = NULL;
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_UNSAFE;
context.safe_param_ids = NIL;
@@ -1098,6 +1100,7 @@ is_parallel_safe(PlannerInfo *root, Node *node)
root->glob->nParamExec == 0)
return true;
/* Else use max_parallel_hazard's search logic, but stop on RESTRICTED */
+ context.root = root;
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_RESTRICTED;
context.safe_param_ids = NIL;
@@ -1222,21 +1225,47 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
}
/*
- * We can't pass Params to workers at the moment either, so they are also
- * parallel-restricted, unless they are PARAM_EXEC Params listed in
+ * As of now, we can only pass Params that refer to the same or parent
+ * query level (see generate_gather_paths) or they are listed in
* safe_param_ids, meaning they could be generated within the worker.
*/
else if (IsA(node, Param))
{
+ int paramid;
+ PlannerInfo *root;
Param *param = (Param *) node;
- if (param->paramkind != PARAM_EXEC ||
- !list_member_int(context->safe_param_ids, param->paramid))
+ if (list_member_int(context->safe_param_ids, param->paramid))
+ return false;
+
+ root = context->root;
+ paramid = ((Param *) node)->paramid;
+
+ if (root)
{
- if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
- return true;
+ PlannerInfo *proot;
+ ListCell *l;
+
+ for (proot = root; proot != NULL; proot = proot->parent_root)
+ {
+ foreach(l, proot->init_plans)
+ {
+ SubPlan *initsubplan = (SubPlan *) lfirst(l);
+ ListCell *l2;
+
+ foreach(l2, initsubplan->setParam)
+ {
+ int initparam = lfirst_int(l2);
+
+ if (paramid == initparam)
+ return false;
+ }
+ }
+ }
}
- return false; /* nothing to recurse to */
+
+ if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
+ return true;
}
/*
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 8ee0496..c44ad88 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -611,6 +611,7 @@ extern ExprEvalOp ExecEvalStepOp(ExprState *state, ExprEvalStep *op);
*/
extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
+extern void ExecEvalParamExecParams(Bitmapset *params, EState *estate);
extern void ExecEvalParamExtern(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
extern void ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op);
diff --git a/src/include/executor/execParallel.h b/src/include/executor/execParallel.h
index bd0a87f..72eda9e 100644
--- a/src/include/executor/execParallel.h
+++ b/src/include/executor/execParallel.h
@@ -33,7 +33,7 @@ typedef struct ParallelExecutorInfo
} ParallelExecutorInfo;
extern ParallelExecutorInfo *ExecInitParallelPlan(PlanState *planstate,
- EState *estate, int nworkers);
+ EState *estate, int nworkers, Bitmapset *initParam);
extern void ExecParallelFinish(ParallelExecutorInfo *pei);
extern void ExecParallelCleanup(ParallelExecutorInfo *pei);
extern void ExecParallelReinitialize(ParallelExecutorInfo *pei);
diff --git a/src/include/executor/nodeSubplan.h b/src/include/executor/nodeSubplan.h
index 5dbaeeb..40b1a34 100644
--- a/src/include/executor/nodeSubplan.h
+++ b/src/include/executor/nodeSubplan.h
@@ -28,4 +28,10 @@ extern void ExecReScanSetParamPlan(SubPlanState *node, PlanState *parent);
extern void ExecSetParamPlan(SubPlanState *node, ExprContext *econtext);
+extern Size EstimateInitPlanParamsSpace(ParamExecData *paramExecVals, Bitmapset *params);
+
+extern void SerializeInitPlanParams(ParamExecData *paramExecVals, Bitmapset *params, char **start_address);
+
+extern void RestoreInitPlanParams(char **start_address, ParamExecData *params);
+
#endif /* NODESUBPLAN_H */
diff --git a/src/include/nodes/params.h b/src/include/nodes/params.h
index 55219da..e96a1ea 100644
--- a/src/include/nodes/params.h
+++ b/src/include/nodes/params.h
@@ -98,6 +98,16 @@ typedef struct ParamExecData
{
void *execPlan; /* should be "SubPlanState *" */
Datum value;
+
+ /*
+ * parameter's datatype, or 0. This is required so that datum value can
+ * be read and used for other purposes like passing it to worker backend
+ * via shared memory. This is required only for initPlan's evaluation,
+ * however for consistency we set this for Subplan as well. We left it
+ * for other cases like CTE or RecursiveUnion cases where this structure
+ * is not used for evaluation of subplans.
+ */
+ Oid ptype;
bool isnull;
} ParamExecData;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 7c51e7f..44d0abf 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -833,6 +833,8 @@ typedef struct Gather
int num_workers;
bool single_copy;
bool invisible; /* suppress EXPLAIN display (for testing)? */
+ Bitmapset *initParam; /* param id's of initplans which are referred
+ * at gather or one of it's child node */
} Gather;
/* ------------
@@ -849,6 +851,8 @@ typedef struct GatherMerge
Oid *sortOperators; /* OIDs of operators to sort them by */
Oid *collations; /* OIDs of collations */
bool *nullsFirst; /* NULLS FIRST/LAST directions */
+ Bitmapset *initParam; /* param id's of initplans which are referred
+ * at gather merge or one of it's child node */
} GatherMerge;
/* ----------------
diff --git a/src/include/optimizer/subselect.h b/src/include/optimizer/subselect.h
index ecd2011..a35df55 100644
--- a/src/include/optimizer/subselect.h
+++ b/src/include/optimizer/subselect.h
@@ -35,6 +35,8 @@ extern Param *SS_make_initplan_output_param(PlannerInfo *root,
extern void SS_make_initplan_from_plan(PlannerInfo *root,
PlannerInfo *subroot, Plan *plan,
Param *prm);
+extern bool initplan_is_below_current_query_level(PlannerInfo *root);
+extern bool contains_parallel_unsafe_param(PlannerInfo *root, RelOptInfo *rel);
extern Param *assign_nestloop_param_var(PlannerInfo *root, Var *var);
extern Param *assign_nestloop_param_placeholdervar(PlannerInfo *root,
PlaceHolderVar *phv);
diff --git a/src/test/regress/expected/select_parallel.out b/src/test/regress/expected/select_parallel.out
index 3e35e96..56133fe 100644
--- a/src/test/regress/expected/select_parallel.out
+++ b/src/test/regress/expected/select_parallel.out
@@ -139,6 +139,39 @@ explain (costs off)
(4 rows)
alter table tenk2 reset (parallel_workers);
+--test parallel plan for a query containing initplan.
+set enable_indexscan = off;
+set enable_indexonlyscan = off;
+set enable_bitmapscan = off;
+alter table tenk2 set (parallel_workers = 2);
+explain (costs off)
+ select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+ QUERY PLAN
+------------------------------------------------------
+ Aggregate
+ InitPlan 1 (returns $1)
+ -> Finalize Aggregate
+ -> Gather
+ Workers Planned: 2
+ -> Partial Aggregate
+ -> Parallel Seq Scan on tenk2
+ -> Gather
+ Workers Planned: 4
+ Params Evaluated: $1
+ -> Parallel Seq Scan on tenk1
+ Filter: (unique1 = $1)
+(12 rows)
+
+select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+ count
+-------
+ 1
+(1 row)
+
+reset enable_indexscan;
+reset enable_indexonlyscan;
+reset enable_bitmapscan;
+alter table tenk2 reset (parallel_workers);
-- test parallel index scans.
set enable_seqscan to off;
set enable_bitmapscan to off;
diff --git a/src/test/regress/sql/select_parallel.sql b/src/test/regress/sql/select_parallel.sql
index d2d262c..57a4944 100644
--- a/src/test/regress/sql/select_parallel.sql
+++ b/src/test/regress/sql/select_parallel.sql
@@ -52,6 +52,21 @@ explain (costs off)
(select ten from tenk2);
alter table tenk2 reset (parallel_workers);
+--test parallel plan for a query containing initplan.
+set enable_indexscan = off;
+set enable_indexonlyscan = off;
+set enable_bitmapscan = off;
+alter table tenk2 set (parallel_workers = 2);
+
+explain (costs off)
+ select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+
+reset enable_indexscan;
+reset enable_indexonlyscan;
+reset enable_bitmapscan;
+alter table tenk2 reset (parallel_workers);
+
-- test parallel index scans.
set enable_seqscan to off;
set enable_bitmapscan to off;
On Mon, Aug 14, 2017 at 8:41 PM, Amit Kapila <amit.kapila16@gmail.com>
wrote:
On Sun, Aug 13, 2017 at 6:49 PM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:On Fri, Aug 11, 2017 at 1:18 AM, Amit Kapila <amit.kapila16@gmail.com>
wrote:Thanks for the updated patch. Patch looks fine. I just have some
minor comments.+ * ExecEvalParamExecParams + * + * Execute the subplan stored in PARAM_EXEC initplans params, if not executed + * till now. + */ +void +ExecEvalParamExecParams(Bitmapset *params, EState *estate)I feel it is better to explain when this function executes the sub plans
that are
not executed till now? Means like in what scenario?It just means that it will execute the same initplan (subplan) just
once in master backend even if it used in multiple places. This is
the same kind of usage as we have in ExecEvalParamExec. You can find
its usage by using some query likeexplain analyse select sum(t1.i) from t1, t2 where t1.j=t2.j and t1.k
= (select count(k) from t3) and t1.k=t2.k;Ensure you insert some rows in t1 and t2 that match the count from t3.
If you are using the schema and data given in Kuntal's script in email
above, then you need to insert something like
t1(900,900,900);t2(900,900,900);It generates plan like below:
postgres=# explain analyse select sum(t1.i) from t1, t2 where
t1.j=t2.j and t1.k = (select count(k) from t3) and t1.k=t2.k;
QUERY PLAN
------------------------------------------------------------
-----------------------------------------------------------------------
Aggregate (cost=29.65..29.66 rows=1 width=8) (actual
time=22572.521..22572.521 rows=1 loops=1)
InitPlan 1 (returns $0)
-> Finalize Aggregate (cost=9.70..9.71 rows=1 width=8) (actual
time=4345.110..4345.111 rows=1 loops=1)
-> Gather (cost=9.69..9.70 rows=2 width=8) (actual
time=4285.019..4345.098 rows=3 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Partial Aggregate (cost=9.69..9.70 rows=1
width=8) (actual time=0.154..0.155 rows=1 loops=3)
-> Parallel Seq Scan on t3 (cost=0.00..8.75
rows=375 width=4) (actual time=0.011..0.090 rows=300 loops=3)
-> Nested Loop (cost=0.00..19.93 rows=1 width=4) (actual
time=22499.918..22572.512 rows=1 loops=1)
Join Filter: (t1.j = t2.j)
-> Gather (cost=0.00..9.67 rows=2 width=12) (actual
time=10521.356..10521.363 rows=1 loops=1)
Workers Planned: 2
Params Evaluated: $0
Workers Launched: 2
-> Parallel Seq Scan on t1 (cost=0.00..9.67 rows=1
width=12) (actual time=0.506..0.507 rows=0 loops=3)
Filter: (k = $0)
Rows Removed by Filter: 299
-> Materialize (cost=0.00..10.21 rows=2 width=8) (actual
time=11978.557..12051.142 rows=1 loops=1)
-> Gather (cost=0.00..10.20 rows=2 width=8) (actual
time=11978.530..12051.113 rows=1 loops=1)
Workers Planned: 2
Params Evaluated: $0
Workers Launched: 2
-> Parallel Seq Scan on t2 (cost=0.00..10.20
rows=1 width=8) (actual time=0.067..0.067 rows=0 loops=3)
Filter: (k = $0)
Rows Removed by Filter: 333
Planning time: 15103.237 ms
Execution time: 22574.703 ms
(27 rows)You can notice that initplan is used at multiple nodes, but it will be
evaluated just once. If you want, I can add a sentence in the
comments, but I think this is somewhat obvious and the same use case
already exists. Let me know if you still think that comments need to
be expanded?
Thanks for providing details. Yes it is clear to me.
+ if (IsA(plan, Gather)) + ((Gather *) plan)->initParam = bms_intersect(plan->lefttree->extParam, initSetParam); + else + ((GatherMerge *) plan)->initParam = bms_intersect(plan->lefttree->extParam, initSetParam);I think the above code is to find out the common parameters that are
prsent
in the external
and out params.Here, we want to save all the initplan params that can be used below
the gather node. extParam contains the set of all external PARAM_EXEC
params that can be used below gather node and we just want initplan
params out of those.It may be better to explain the logic in the comments.
I have kept comments atop of the function set_param_references to
explain the context, but now I have added few more in the code as
suggested by you. See if that suffices the need.
Thanks for adding more details. It is easy to understand.
I marked the patch as ready for committer in the commitfest.
Regards,
Hari Babu
Fujitsu Australia
On Mon, Aug 21, 2017 at 1:44 PM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:
Thanks for adding more details. It is easy to understand.
I marked the patch as ready for committer in the commitfest.
Thank you.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Aug 21, 2017 at 2:40 PM, Amit Kapila <amit.kapila16@gmail.com> wrote:
On Mon, Aug 21, 2017 at 1:44 PM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:Thanks for adding more details. It is easy to understand.
I marked the patch as ready for committer in the commitfest.
Rebased the patch. The output of test case added by the patch is also
slightly changed because of the recent commit
7df2c1f8daeb361133ac8bdeaf59ceb0484e315a. I have verified that the
new test result is as expected.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
pq_pushdown_initplan_v8.patchapplication/octet-stream; name=pq_pushdown_initplan_v8.patchDownload
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 4cee357..9e02a46 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -107,6 +107,7 @@ static void show_tidbitmap_info(BitmapHeapScanState *planstate,
static void show_instrumentation_count(const char *qlabel, int which,
PlanState *planstate, ExplainState *es);
static void show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es);
+static void show_eval_params(Bitmapset *bms_params, ExplainState *es);
static const char *explain_get_index_name(Oid indexId);
static void show_buffer_usage(ExplainState *es, const BufferUsage *usage);
static void ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir,
@@ -637,7 +638,17 @@ ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc)
*/
ps = queryDesc->planstate;
if (IsA(ps, GatherState) &&((Gather *) ps->plan)->invisible)
+ {
+ List *initPlanState = NULL;
+ PlanState *save_ps;
+
+ /* initplans are always attached to the top node (cf standard_planner) */
+ save_ps = ps;
+ initPlanState = ps->initPlan;
ps = outerPlanState(ps);
+ ps->initPlan = initPlanState;
+ save_ps->initPlan = NIL;
+ }
ExplainNode(ps, NIL, NULL, NULL, es);
}
@@ -1447,6 +1458,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
planstate, es);
ExplainPropertyInteger("Workers Planned",
gather->num_workers, es);
+
+ /* Show params evaluated at gather node */
+ if (gather->initParam)
+ show_eval_params(gather->initParam, es);
+
if (es->analyze)
{
int nworkers;
@@ -1469,6 +1485,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
planstate, es);
ExplainPropertyInteger("Workers Planned",
gm->num_workers, es);
+
+ /* Show params evaluated at gather-merge node */
+ if (gm->initParam)
+ show_eval_params(gm->initParam, es);
+
if (es->analyze)
{
int nworkers;
@@ -2494,6 +2515,29 @@ show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es)
}
/*
+ * Show initplan params evaluated at gather or gather merge node.
+ */
+static void
+show_eval_params(Bitmapset *bms_params, ExplainState *es)
+{
+ int paramid = -1;
+ List *params = NIL;
+
+ Assert(bms_params);
+
+ while ((paramid = bms_next_member(bms_params, paramid)) >= 0)
+ {
+ char param[32];
+
+ snprintf(param, sizeof(param), "$%d", paramid);
+ params = lappend(params, pstrdup(param));
+ }
+
+ if (params)
+ ExplainPropertyList("Params Evaluated", params, es);
+}
+
+/*
* Fetch the name of an index in an EXPLAIN
*
* We allow plugins to get control here so that plans involving hypothetical
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 83e0447..ee67192 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -1861,6 +1861,33 @@ ExecEvalParamExec(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
}
/*
+ * ExecEvalParamExecParams
+ *
+ * Execute the subplan stored in PARAM_EXEC initplans params, if not executed
+ * till now.
+ */
+void
+ExecEvalParamExecParams(Bitmapset *params, EState *estate)
+{
+ ParamExecData *prm;
+ int paramid;
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ prm = &(estate->es_param_exec_vals[paramid]);
+
+ if (prm->execPlan != NULL)
+ {
+ /* Parameter not evaluated yet, so go do it */
+ ExecSetParamPlan(prm->execPlan, GetPerTupleExprContext(estate));
+ /* ExecSetParamPlan should have processed this param... */
+ Assert(prm->execPlan == NULL);
+ }
+ }
+}
+
+/*
* Evaluate a PARAM_EXTERN parameter.
*
* PARAM_EXTERN parameters must be sought in ecxt_param_list_info.
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index c713b85..78aa616 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -23,6 +23,7 @@
#include "postgres.h"
+#include "executor/execExpr.h"
#include "executor/execParallel.h"
#include "executor/executor.h"
#include "executor/nodeBitmapHeapscan.h"
@@ -31,6 +32,7 @@
#include "executor/nodeIndexscan.h"
#include "executor/nodeIndexonlyscan.h"
#include "executor/nodeSeqscan.h"
+#include "executor/nodeSubplan.h"
#include "executor/nodeSort.h"
#include "executor/tqueue.h"
#include "nodes/nodeFuncs.h"
@@ -56,6 +58,7 @@
#define PARALLEL_KEY_INSTRUMENTATION UINT64CONST(0xE000000000000006)
#define PARALLEL_KEY_DSA UINT64CONST(0xE000000000000007)
#define PARALLEL_KEY_QUERY_TEXT UINT64CONST(0xE000000000000008)
+#define PARALLEL_KEY_INITPLAN_PARAMS UINT64CONST(0xE000000000000009)
#define PARALLEL_TUPLE_QUEUE_SIZE 65536
@@ -395,7 +398,8 @@ ExecParallelSetupTupleQueues(ParallelContext *pcxt, bool reinitialize)
* execution and return results to the main backend.
*/
ParallelExecutorInfo *
-ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
+ExecInitParallelPlan(PlanState *planstate, EState *estate,
+ Bitmapset *initParam, int nworkers,
int64 tuples_needed)
{
ParallelExecutorInfo *pei;
@@ -406,10 +410,12 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
char *pstmt_data;
char *pstmt_space;
char *param_space;
+ char *initplan_param_space;
BufferUsage *bufusage_space;
SharedExecutorInstrumentation *instrumentation = NULL;
int pstmt_len;
int param_len;
+ int initplan_param_len;
int instrumentation_len = 0;
int instrument_offset = 0;
Size dsa_minsize = dsa_minimum_size();
@@ -421,6 +427,8 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
pei->finished = false;
pei->planstate = planstate;
+ ExecEvalParamExecParams(initParam, estate);
+
/* Fix up and serialize plan to be sent to workers. */
pstmt_data = ExecSerializePlan(planstate->plan, estate);
@@ -454,6 +462,11 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
shm_toc_estimate_chunk(&pcxt->estimator, param_len);
shm_toc_estimate_keys(&pcxt->estimator, 1);
+ /* Estimate space for initplan params. */
+ initplan_param_len = EstimateInitPlanParamsSpace(estate->es_param_exec_vals, initParam);
+ shm_toc_estimate_chunk(&pcxt->estimator, initplan_param_len);
+ shm_toc_estimate_keys(&pcxt->estimator, 1);
+
/*
* Estimate space for BufferUsage.
*
@@ -528,6 +541,11 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
shm_toc_insert(pcxt->toc, PARALLEL_KEY_PARAMS, param_space);
SerializeParamList(estate->es_param_list_info, ¶m_space);
+ /* Store serialized initplan params. */
+ initplan_param_space = shm_toc_allocate(pcxt->toc, initplan_param_len);
+ shm_toc_insert(pcxt->toc, PARALLEL_KEY_INITPLAN_PARAMS, initplan_param_space);
+ SerializeInitPlanParams(estate->es_param_exec_vals, initParam, &initplan_param_space);
+
/* Allocate space for each worker's BufferUsage; no need to initialize. */
bufusage_space = shm_toc_allocate(pcxt->toc,
mul_size(sizeof(BufferUsage), pcxt->nworkers));
@@ -801,6 +819,21 @@ ExecParallelGetReceiver(dsm_segment *seg, shm_toc *toc)
}
/*
+ * Copy the ParamExecData params corresponding to initplans from dynamic
+ * shared memory. This has to be done once the params are allocated by
+ * executor; that is after ExecutorStart().
+ */
+static void
+ExecParallelInitializeInitPlanParams(shm_toc *toc, ParamExecData *params)
+{
+ char *paramspace;
+
+ /* Reconstruct initplan params. */
+ paramspace = shm_toc_lookup(toc, PARALLEL_KEY_INITPLAN_PARAMS, false);
+ RestoreInitPlanParams(¶mspace, params);
+}
+
+/*
* Create a QueryDesc for the PlannedStmt we are to execute, and return it.
*/
static QueryDesc *
@@ -985,6 +1018,7 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
/* Special executor initialization steps for parallel workers */
queryDesc->planstate->state->es_query_dsa = area;
+ ExecParallelInitializeInitPlanParams(toc, queryDesc->estate->es_param_exec_vals);
ExecParallelInitializeWorker(queryDesc->planstate, toc);
/* Pass down any tuple bound */
diff --git a/src/backend/executor/nodeGather.c b/src/backend/executor/nodeGather.c
index d93fbac..6c50e8e 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -159,6 +159,7 @@ ExecGather(PlanState *pstate)
if (!node->pei)
node->pei = ExecInitParallelPlan(node->ps.lefttree,
estate,
+ gather->initParam,
gather->num_workers,
node->tuples_needed);
else
diff --git a/src/backend/executor/nodeGatherMerge.c b/src/backend/executor/nodeGatherMerge.c
index 67da5ff..15d8051 100644
--- a/src/backend/executor/nodeGatherMerge.c
+++ b/src/backend/executor/nodeGatherMerge.c
@@ -198,6 +198,7 @@ ExecGatherMerge(PlanState *pstate)
if (!node->pei)
node->pei = ExecInitParallelPlan(node->ps.lefttree,
estate,
+ gm->initParam,
gm->num_workers,
node->tuples_needed);
else
diff --git a/src/backend/executor/nodeNestloop.c b/src/backend/executor/nodeNestloop.c
index 4447b7c..52514e4 100644
--- a/src/backend/executor/nodeNestloop.c
+++ b/src/backend/executor/nodeNestloop.c
@@ -130,6 +130,7 @@ ExecNestLoop(PlanState *pstate)
{
NestLoopParam *nlp = (NestLoopParam *) lfirst(lc);
int paramno = nlp->paramno;
+ TupleDesc tdesc = outerTupleSlot->tts_tupleDescriptor;
ParamExecData *prm;
prm = &(econtext->ecxt_param_exec_vals[paramno]);
@@ -140,6 +141,7 @@ ExecNestLoop(PlanState *pstate)
prm->value = slot_getattr(outerTupleSlot,
nlp->paramval->varattno,
&(prm->isnull));
+ prm->ptype = TupleDescAttr(tdesc, nlp->paramval->varattno - 1)->atttypid;
/* Flag parameter value as changed */
innerPlan->chgParam = bms_add_member(innerPlan->chgParam,
paramno);
diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c
index 77ef6f3..06409ed 100644
--- a/src/backend/executor/nodeSubplan.c
+++ b/src/backend/executor/nodeSubplan.c
@@ -30,12 +30,16 @@
#include <math.h>
#include "access/htup_details.h"
+#include "catalog/pg_type.h"
#include "executor/executor.h"
#include "executor/nodeSubplan.h"
#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
#include "miscadmin.h"
#include "optimizer/clauses.h"
+#include "storage/shmem.h"
#include "utils/array.h"
+#include "utils/datum.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
@@ -273,11 +277,13 @@ ExecScanSubPlan(SubPlanState *node,
forboth(l, subplan->parParam, pvar, node->args)
{
int paramid = lfirst_int(l);
+ ExprState *exprstate = (ExprState *) lfirst(pvar);
ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]);
- prm->value = ExecEvalExprSwitchContext((ExprState *) lfirst(pvar),
+ prm->value = ExecEvalExprSwitchContext(exprstate,
econtext,
&(prm->isnull));
+ prm->ptype = exprType((Node *) exprstate->expr);
planstate->chgParam = bms_add_member(planstate->chgParam, paramid);
}
@@ -390,6 +396,7 @@ ExecScanSubPlan(SubPlanState *node,
prmdata = &(econtext->ecxt_param_exec_vals[paramid]);
Assert(prmdata->execPlan == NULL);
prmdata->value = slot_getattr(slot, col, &(prmdata->isnull));
+ prmdata->ptype = TupleDescAttr(tdesc, col - 1)->atttypid;
col++;
}
@@ -556,11 +563,13 @@ buildSubPlanHash(SubPlanState *node, ExprContext *econtext)
{
int paramid = lfirst_int(plst);
ParamExecData *prmdata;
+ TupleDesc tdesc = slot->tts_tupleDescriptor;
prmdata = &(innerecontext->ecxt_param_exec_vals[paramid]);
Assert(prmdata->execPlan == NULL);
prmdata->value = slot_getattr(slot, col,
&(prmdata->isnull));
+ prmdata->ptype = TupleDescAttr(tdesc, col - 1)->atttypid;
col++;
}
slot = ExecProject(node->projRight);
@@ -924,6 +933,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
ListCell *l;
bool found = false;
ArrayBuildStateAny *astate = NULL;
+ Oid ptype;
if (subLinkType == ANY_SUBLINK ||
subLinkType == ALL_SUBLINK)
@@ -931,6 +941,8 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
if (subLinkType == CTE_SUBLINK)
elog(ERROR, "CTE subplans should not be executed via ExecSetParamPlan");
+ ptype = exprType((Node *) node->subplan);
+
/* Initialize ArrayBuildStateAny in caller's context, if needed */
if (subLinkType == ARRAY_SUBLINK)
astate = initArrayResultAny(subplan->firstColType,
@@ -953,11 +965,13 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
forboth(l, subplan->parParam, pvar, node->args)
{
int paramid = lfirst_int(l);
+ ExprState *exprstate = (ExprState *) lfirst(pvar);
ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]);
- prm->value = ExecEvalExprSwitchContext((ExprState *) lfirst(pvar),
+ prm->value = ExecEvalExprSwitchContext(exprstate,
econtext,
&(prm->isnull));
+ prm->ptype = exprType((Node *) exprstate->expr);
planstate->chgParam = bms_add_member(planstate->chgParam, paramid);
}
@@ -980,6 +994,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = BoolGetDatum(true);
+ prm->ptype = ptype;
prm->isnull = false;
found = true;
break;
@@ -1031,6 +1046,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = heap_getattr(node->curTuple, i, tdesc,
&(prm->isnull));
+ prm->ptype = TupleDescAttr(tdesc, i - 1)->atttypid;
i++;
}
}
@@ -1053,6 +1069,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
true);
prm->execPlan = NULL;
prm->value = node->curArray;
+ prm->ptype = ptype;
prm->isnull = false;
}
else if (!found)
@@ -1065,6 +1082,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = BoolGetDatum(false);
+ prm->ptype = ptype;
prm->isnull = false;
}
else
@@ -1077,6 +1095,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = (Datum) 0;
+ prm->ptype = VOIDOID;
prm->isnull = true;
}
}
@@ -1207,3 +1226,133 @@ ExecAlternativeSubPlan(AlternativeSubPlanState *node,
return ExecSubPlan(activesp, econtext, isNull);
}
+
+/*
+ * Estimate the amount of space required to serialize the InitPlan params.
+ */
+Size
+EstimateInitPlanParamsSpace(ParamExecData *paramExecVals, Bitmapset *params)
+{
+ int paramid;
+ Size sz = sizeof(int);
+ ParamExecData *prm;
+
+ if (params == NULL)
+ return sz;
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ Oid typeOid;
+ int16 typLen;
+ bool typByVal;
+
+ prm = &(paramExecVals[paramid]);
+ typeOid = prm->ptype;
+
+ sz = add_size(sz, sizeof(int)); /* space for paramid */
+ sz = add_size(sz, sizeof(Oid)); /* space for type OID */
+
+ /* space for datum/isnull */
+ if (OidIsValid(typeOid))
+ get_typlenbyval(typeOid, &typLen, &typByVal);
+ else
+ {
+ /* If no type OID, assume by-value, like copyParamList does. */
+ typLen = sizeof(Datum);
+ typByVal = true;
+ }
+ sz = add_size(sz,
+ datumEstimateSpace(prm->value, prm->isnull, typByVal, typLen));
+ }
+ return sz;
+}
+
+/*
+ * Serialize ParamExecData params corresponding to initplans.
+ *
+ * We write the number of parameters first, as a 4-byte integer, and then
+ * write details for each parameter in turn. The details for each parameter
+ * consist of a 4-byte paramid (location of param in execution time internal
+ * parameter array), 4-byte type OID, and then the datum as serialized by
+ * datumSerialize().
+ *
+ * The above format is quite similar to the format used to serialize
+ * paramListInfo structure, so if we change either format, then consider to
+ * change at both the places.
+ */
+void
+SerializeInitPlanParams(ParamExecData *paramExecVals, Bitmapset *params,
+ char **start_address)
+{
+ int nparams;
+ int paramid;
+ ParamExecData *prm;
+
+ nparams = bms_num_members(params);
+ memcpy(*start_address, &nparams, sizeof(int));
+ *start_address += sizeof(int);
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ Oid typeOid;
+ int16 typLen;
+ bool typByVal;
+
+ prm = &(paramExecVals[paramid]);
+ typeOid = prm->ptype;
+
+ /* Write paramid. */
+ memcpy(*start_address, ¶mid, sizeof(int));
+ *start_address += sizeof(int);
+
+ /* Write OID. */
+ memcpy(*start_address, &typeOid, sizeof(Oid));
+ *start_address += sizeof(Oid);
+
+ /* space for datum/isnull */
+ if (OidIsValid(typeOid))
+ get_typlenbyval(typeOid, &typLen, &typByVal);
+ else
+ {
+ /* If no type OID, assume by-value, like copyParamList does. */
+ typLen = sizeof(Datum);
+ typByVal = true;
+ }
+ datumSerialize(prm->value, prm->isnull, typByVal, typLen,
+ start_address);
+ }
+}
+
+/*
+ * Restore ParamExecData params corresponding to initplans.
+ */
+void
+RestoreInitPlanParams(char **start_address, ParamExecData *params)
+{
+ int nparams;
+ int i;
+ int paramid;
+
+ memcpy(&nparams, *start_address, sizeof(int));
+ *start_address += sizeof(int);
+
+ for (i = 0; i < nparams; i++)
+ {
+ ParamExecData *prm;
+
+ /* Read paramid */
+ memcpy(¶mid, *start_address, sizeof(int));
+ *start_address += sizeof(int);
+ prm = ¶ms[paramid];
+
+ /* Read type OID. */
+ memcpy(&prm->ptype, *start_address, sizeof(Oid));
+ *start_address += sizeof(Oid);
+
+ /* Read datum/isnull. */
+ prm->value = datumRestore(start_address, &prm->isnull);
+ prm->execPlan = NULL;
+ }
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index f9ddf4e..d8f9ea5 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -364,6 +364,7 @@ _copyGather(const Gather *from)
COPY_SCALAR_FIELD(rescan_param);
COPY_SCALAR_FIELD(single_copy);
COPY_SCALAR_FIELD(invisible);
+ COPY_BITMAPSET_FIELD(initParam);
return newnode;
}
@@ -391,6 +392,7 @@ _copyGatherMerge(const GatherMerge *from)
COPY_POINTER_FIELD(sortOperators, from->numCols * sizeof(Oid));
COPY_POINTER_FIELD(collations, from->numCols * sizeof(Oid));
COPY_POINTER_FIELD(nullsFirst, from->numCols * sizeof(bool));
+ COPY_BITMAPSET_FIELD(initParam);
return newnode;
}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 9ee3e23..22962a7 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -482,6 +482,7 @@ _outGather(StringInfo str, const Gather *node)
WRITE_INT_FIELD(rescan_param);
WRITE_BOOL_FIELD(single_copy);
WRITE_BOOL_FIELD(invisible);
+ WRITE_BITMAPSET_FIELD(initParam);
}
static void
@@ -512,6 +513,8 @@ _outGatherMerge(StringInfo str, const GatherMerge *node)
appendStringInfoString(str, " :nullsFirst");
for (i = 0; i < node->numCols; i++)
appendStringInfo(str, " %s", booltostr(node->nullsFirst[i]));
+
+ WRITE_BITMAPSET_FIELD(initParam);
}
static void
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 67b9e19..93336af 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2166,6 +2166,7 @@ _readGather(void)
READ_INT_FIELD(rescan_param);
READ_BOOL_FIELD(single_copy);
READ_BOOL_FIELD(invisible);
+ READ_BITMAPSET_FIELD(initParam);
READ_DONE();
}
@@ -2187,6 +2188,7 @@ _readGatherMerge(void)
READ_OID_ARRAY(sortOperators, local_node->numCols);
READ_OID_ARRAY(collations, local_node->numCols);
READ_BOOL_ARRAY(nullsFirst, local_node->numCols);
+ READ_BITMAPSET_FIELD(initParam);
READ_DONE();
}
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 2d7e1d8..b1cabd9 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -38,6 +38,7 @@
#include "optimizer/planner.h"
#include "optimizer/prep.h"
#include "optimizer/restrictinfo.h"
+#include "optimizer/subselect.h"
#include "optimizer/tlist.h"
#include "optimizer/var.h"
#include "parser/parse_clause.h"
@@ -2202,6 +2203,14 @@ generate_gather_paths(PlannerInfo *root, RelOptInfo *rel)
Path *simple_gather_path;
ListCell *lc;
+ /*
+ * We don't want to generate gather or gather merge node if there are
+ * initplans at some query level below the current query level as those
+ * plans could be parallel-unsafe or undirect correlated plans.
+ */
+ if (contains_parallel_unsafe_param(root, rel))
+ return;
+
/* If there are no partial paths, there's nothing to do here. */
if (rel->partial_pathlist == NIL)
return;
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 2821662..0363d68 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -6257,6 +6257,7 @@ make_gather(List *qptlist,
node->rescan_param = rescan_param;
node->single_copy = single_copy;
node->invisible = false;
+ node->initParam = NULL;
return node;
}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 9662302..5d8751d 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -366,6 +366,14 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
{
Gather *gather = makeNode(Gather);
+ /*
+ * If there are any initPlans attached to the formerly-top plan node,
+ * move them up to the Gather node; same as we do for Material node in
+ * materialize_finished_plan.
+ */
+ gather->plan.initPlan = top_plan->initPlan;
+ top_plan->initPlan = NIL;
+
gather->plan.targetlist = top_plan->targetlist;
gather->plan.qual = NIL;
gather->plan.lefttree = top_plan;
@@ -3538,6 +3546,12 @@ create_grouping_paths(PlannerInfo *root,
ListCell *lc;
+ /*
+ * Don't parallelize the plan if there is an initplan below current query
+ * level. See generate_gather_paths() for detailed reason.
+ */
+ (void) contains_parallel_unsafe_param(root, input_rel);
+
/* For now, do all work in the (GROUP_AGG, NULL) upperrel */
grouped_rel = fetch_upper_rel(root, UPPERREL_GROUP_AGG, NULL);
@@ -4900,6 +4914,12 @@ create_ordered_paths(PlannerInfo *root,
RelOptInfo *ordered_rel;
ListCell *lc;
+ /*
+ * Don't parallelize the plan if there is an initplan below current query
+ * level. See generate_gather_paths() for detailed reason.
+ */
+ (void) contains_parallel_unsafe_param(root, input_rel);
+
/* For now, do all work in the (ORDERED, NULL) upperrel */
ordered_rel = fetch_upper_rel(root, UPPERREL_ORDERED, NULL);
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index b0c9e94..5fa2bb1 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -104,6 +104,7 @@ static Node *fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context);
static bool fix_scan_expr_walker(Node *node, fix_scan_expr_context *context);
static void set_join_references(PlannerInfo *root, Join *join, int rtoffset);
static void set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset);
+static void set_param_references(PlannerInfo *root, Plan *plan);
static Node *convert_combining_aggrefs(Node *node, void *context);
static void set_dummy_tlist_references(Plan *plan, int rtoffset);
static indexed_tlist *build_tlist_index(List *tlist);
@@ -628,7 +629,10 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
case T_Gather:
case T_GatherMerge:
- set_upper_references(root, plan, rtoffset);
+ {
+ set_upper_references(root, plan, rtoffset);
+ set_param_references(root, plan);
+ }
break;
case T_Hash:
@@ -1784,6 +1788,49 @@ set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset)
}
/*
+ * set_param_references
+ * Initialize the initParam list in gather or gather merge node such that
+ * it contains reference of all the params that needs to be evaluated
+ * before execution of the node. It contains the initplan params that are
+ * being passed to the plan nodes below it.
+ */
+static void
+set_param_references(PlannerInfo *root, Plan *plan)
+{
+ Assert(IsA(plan, Gather) || IsA(plan, GatherMerge));
+
+ if (plan->lefttree->extParam)
+ {
+ PlannerInfo *proot;
+ Bitmapset *initSetParam = NULL;
+ ListCell *l;
+
+ for (proot = root; proot != NULL; proot = proot->parent_root)
+ {
+ foreach(l, proot->init_plans)
+ {
+ SubPlan *initsubplan = (SubPlan *) lfirst(l);
+ ListCell *l2;
+
+ foreach(l2, initsubplan->setParam)
+ {
+ initSetParam = bms_add_member(initSetParam, lfirst_int(l2));
+ }
+ }
+ }
+
+ /*
+ * Remember the list of all external initplan params that are used by
+ * the children of gather or gather merge node.
+ */
+ if (IsA(plan, Gather))
+ ((Gather *) plan)->initParam = bms_intersect(plan->lefttree->extParam, initSetParam);
+ else
+ ((GatherMerge *) plan)->initParam = bms_intersect(plan->lefttree->extParam, initSetParam);
+ }
+}
+
+/*
* Recursively scan an expression tree and convert Aggrefs to the proper
* intermediate form for combining aggregates. This means (1) replacing each
* one's argument list with a single argument that is the original Aggref
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 1103984..8c99f48 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -84,6 +84,7 @@ static Bitmapset *finalize_plan(PlannerInfo *root,
Bitmapset *scan_params);
static bool finalize_primnode(Node *node, finalize_primnode_context *context);
static bool finalize_agg_primnode(Node *node, finalize_primnode_context *context);
+static bool is_initplan_is_below_current_query_level(PlannerInfo *root);
/*
@@ -2136,13 +2137,11 @@ SS_identify_outer_params(PlannerInfo *root)
}
/*
- * SS_charge_for_initplans - account for initplans in Path costs & parallelism
+ * SS_charge_for_initplans - account for initplans in Path costs
*
* If any initPlans have been created in the current query level, they will
* get attached to the Plan tree created from whichever Path we select from
- * the given rel. Increment all that rel's Paths' costs to account for them,
- * and make sure the paths get marked as parallel-unsafe, since we can't
- * currently transmit initPlans to parallel workers.
+ * the given rel. Increment all that rel's Paths' costs to account for them.
*
* This is separate from SS_attach_initplans because we might conditionally
* create more initPlans during create_plan(), depending on which Path we
@@ -2174,7 +2173,7 @@ SS_charge_for_initplans(PlannerInfo *root, RelOptInfo *final_rel)
}
/*
- * Now adjust the costs and parallel_safe flags.
+ * Now adjust the costs.
*/
foreach(lc, final_rel->pathlist)
{
@@ -2182,7 +2181,6 @@ SS_charge_for_initplans(PlannerInfo *root, RelOptInfo *final_rel)
path->startup_cost += initplan_cost;
path->total_cost += initplan_cost;
- path->parallel_safe = false;
}
/* We needn't do set_cheapest() here, caller will do it */
@@ -3000,3 +2998,61 @@ SS_make_initplan_from_plan(PlannerInfo *root,
/* Set costs of SubPlan using info from the plan tree */
cost_subplan(subroot, node, plan);
}
+
+/*
+ * is_initplan_below_current_query_level - is there any initplan present below
+ * current query level.
+ */
+static bool
+is_initplan_is_below_current_query_level(PlannerInfo *root)
+{
+ ListCell *lc;
+
+ /*
+ * If the subplan corresponding to the subroot is an initPlan, it'll be
+ * attached to its parent root. Hence, we check the query level of its
+ * parent root and if any init_plans are attached there.
+ */
+ foreach(lc, root->glob->subroots)
+ {
+ PlannerInfo *subroot = (PlannerInfo *) lfirst(lc);
+ PlannerInfo *proot = subroot->parent_root;
+
+ if (proot->query_level > root->query_level && proot->init_plans)
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * contains_parallel_unsafe_param - Check if there is any initplan present below
+ * current query level, mark all the partial and non-partial paths for a relation
+ * at this level as parallel-unsafe.
+ */
+bool
+contains_parallel_unsafe_param(PlannerInfo *root, RelOptInfo *rel)
+{
+ ListCell *lc;
+
+ if (is_initplan_is_below_current_query_level(root))
+ {
+ foreach(lc, rel->partial_pathlist)
+ {
+ Path *subpath = (Path *) lfirst(lc);
+
+ subpath->parallel_safe = false;
+ }
+ foreach(lc, rel->pathlist)
+ {
+ Path *subpath = (Path *) lfirst(lc);
+
+ subpath->parallel_safe = false;
+ }
+ rel->consider_parallel = false;
+
+ return true;
+ }
+
+ return false;
+}
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 93add27..48e502c 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -91,6 +91,7 @@ typedef struct
typedef struct
{
+ PlannerInfo *root;
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 */
@@ -1069,6 +1070,7 @@ max_parallel_hazard(Query *parse)
{
max_parallel_hazard_context context;
+ context.root = NULL;
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_UNSAFE;
context.safe_param_ids = NIL;
@@ -1098,6 +1100,7 @@ is_parallel_safe(PlannerInfo *root, Node *node)
root->glob->nParamExec == 0)
return true;
/* Else use max_parallel_hazard's search logic, but stop on RESTRICTED */
+ context.root = root;
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_RESTRICTED;
context.safe_param_ids = NIL;
@@ -1222,21 +1225,47 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
}
/*
- * We can't pass Params to workers at the moment either, so they are also
- * parallel-restricted, unless they are PARAM_EXEC Params listed in
+ * As of now, we can only pass Params that refer to the same or parent
+ * query level (see generate_gather_paths) or they are listed in
* safe_param_ids, meaning they could be generated within the worker.
*/
else if (IsA(node, Param))
{
+ int paramid;
+ PlannerInfo *root;
Param *param = (Param *) node;
- if (param->paramkind != PARAM_EXEC ||
- !list_member_int(context->safe_param_ids, param->paramid))
+ if (list_member_int(context->safe_param_ids, param->paramid))
+ return false;
+
+ root = context->root;
+ paramid = ((Param *) node)->paramid;
+
+ if (root)
{
- if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
- return true;
+ PlannerInfo *proot;
+ ListCell *l;
+
+ for (proot = root; proot != NULL; proot = proot->parent_root)
+ {
+ foreach(l, proot->init_plans)
+ {
+ SubPlan *initsubplan = (SubPlan *) lfirst(l);
+ ListCell *l2;
+
+ foreach(l2, initsubplan->setParam)
+ {
+ int initparam = lfirst_int(l2);
+
+ if (paramid == initparam)
+ return false;
+ }
+ }
+ }
}
- return false; /* nothing to recurse to */
+
+ if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
+ return true;
}
/*
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 8ee0496..c44ad88 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -611,6 +611,7 @@ extern ExprEvalOp ExecEvalStepOp(ExprState *state, ExprEvalStep *op);
*/
extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
+extern void ExecEvalParamExecParams(Bitmapset *params, EState *estate);
extern void ExecEvalParamExtern(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
extern void ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op);
diff --git a/src/include/executor/execParallel.h b/src/include/executor/execParallel.h
index 1cb895d..27bf00e 100644
--- a/src/include/executor/execParallel.h
+++ b/src/include/executor/execParallel.h
@@ -33,7 +33,8 @@ typedef struct ParallelExecutorInfo
} ParallelExecutorInfo;
extern ParallelExecutorInfo *ExecInitParallelPlan(PlanState *planstate,
- EState *estate, int nworkers, int64 tuples_needed);
+ EState *estate, Bitmapset *initParam, int nworkers,
+ int64 tuples_needed);
extern void ExecParallelFinish(ParallelExecutorInfo *pei);
extern void ExecParallelCleanup(ParallelExecutorInfo *pei);
extern void ExecParallelReinitialize(PlanState *planstate,
diff --git a/src/include/executor/nodeSubplan.h b/src/include/executor/nodeSubplan.h
index 5dbaeeb..40b1a34 100644
--- a/src/include/executor/nodeSubplan.h
+++ b/src/include/executor/nodeSubplan.h
@@ -28,4 +28,10 @@ extern void ExecReScanSetParamPlan(SubPlanState *node, PlanState *parent);
extern void ExecSetParamPlan(SubPlanState *node, ExprContext *econtext);
+extern Size EstimateInitPlanParamsSpace(ParamExecData *paramExecVals, Bitmapset *params);
+
+extern void SerializeInitPlanParams(ParamExecData *paramExecVals, Bitmapset *params, char **start_address);
+
+extern void RestoreInitPlanParams(char **start_address, ParamExecData *params);
+
#endif /* NODESUBPLAN_H */
diff --git a/src/include/nodes/params.h b/src/include/nodes/params.h
index 55219da..e96a1ea 100644
--- a/src/include/nodes/params.h
+++ b/src/include/nodes/params.h
@@ -98,6 +98,16 @@ typedef struct ParamExecData
{
void *execPlan; /* should be "SubPlanState *" */
Datum value;
+
+ /*
+ * parameter's datatype, or 0. This is required so that datum value can
+ * be read and used for other purposes like passing it to worker backend
+ * via shared memory. This is required only for initPlan's evaluation,
+ * however for consistency we set this for Subplan as well. We left it
+ * for other cases like CTE or RecursiveUnion cases where this structure
+ * is not used for evaluation of subplans.
+ */
+ Oid ptype;
bool isnull;
} ParamExecData;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index a382331..a16909c 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -841,6 +841,8 @@ typedef struct Gather
int rescan_param; /* ID of Param that signals a rescan, or -1 */
bool single_copy; /* don't execute plan more than once */
bool invisible; /* suppress EXPLAIN display (for testing)? */
+ Bitmapset *initParam; /* param id's of initplans which are referred
+ * at gather or one of it's child node */
} Gather;
/* ------------
@@ -858,6 +860,8 @@ typedef struct GatherMerge
Oid *sortOperators; /* OIDs of operators to sort them by */
Oid *collations; /* OIDs of collations */
bool *nullsFirst; /* NULLS FIRST/LAST directions */
+ Bitmapset *initParam; /* param id's of initplans which are referred
+ * at gather merge or one of it's child node */
} GatherMerge;
/* ----------------
diff --git a/src/include/optimizer/subselect.h b/src/include/optimizer/subselect.h
index ecd2011..a35df55 100644
--- a/src/include/optimizer/subselect.h
+++ b/src/include/optimizer/subselect.h
@@ -35,6 +35,8 @@ extern Param *SS_make_initplan_output_param(PlannerInfo *root,
extern void SS_make_initplan_from_plan(PlannerInfo *root,
PlannerInfo *subroot, Plan *plan,
Param *prm);
+extern bool initplan_is_below_current_query_level(PlannerInfo *root);
+extern bool contains_parallel_unsafe_param(PlannerInfo *root, RelOptInfo *rel);
extern Param *assign_nestloop_param_var(PlannerInfo *root, Var *var);
extern Param *assign_nestloop_param_placeholdervar(PlannerInfo *root,
PlaceHolderVar *phv);
diff --git a/src/test/regress/expected/select_parallel.out b/src/test/regress/expected/select_parallel.out
index 888da5a..1ce5ff5 100644
--- a/src/test/regress/expected/select_parallel.out
+++ b/src/test/regress/expected/select_parallel.out
@@ -139,6 +139,39 @@ explain (costs off)
(4 rows)
alter table tenk2 reset (parallel_workers);
+--test parallel plan for a query containing initplan.
+set enable_indexscan = off;
+set enable_indexonlyscan = off;
+set enable_bitmapscan = off;
+alter table tenk2 set (parallel_workers = 2);
+explain (costs off)
+ select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+ QUERY PLAN
+------------------------------------------------------
+ Aggregate
+ InitPlan 1 (returns $2)
+ -> Finalize Aggregate
+ -> Gather
+ Workers Planned: 2
+ -> Partial Aggregate
+ -> Parallel Seq Scan on tenk2
+ -> Gather
+ Workers Planned: 4
+ Params Evaluated: $2
+ -> Parallel Seq Scan on tenk1
+ Filter: (unique1 = $2)
+(12 rows)
+
+select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+ count
+-------
+ 1
+(1 row)
+
+reset enable_indexscan;
+reset enable_indexonlyscan;
+reset enable_bitmapscan;
+alter table tenk2 reset (parallel_workers);
-- test parallel index scans.
set enable_seqscan to off;
set enable_bitmapscan to off;
diff --git a/src/test/regress/sql/select_parallel.sql b/src/test/regress/sql/select_parallel.sql
index cefb5a2..358d02d 100644
--- a/src/test/regress/sql/select_parallel.sql
+++ b/src/test/regress/sql/select_parallel.sql
@@ -52,6 +52,21 @@ explain (costs off)
(select ten from tenk2);
alter table tenk2 reset (parallel_workers);
+--test parallel plan for a query containing initplan.
+set enable_indexscan = off;
+set enable_indexonlyscan = off;
+set enable_bitmapscan = off;
+alter table tenk2 set (parallel_workers = 2);
+
+explain (costs off)
+ select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+
+reset enable_indexscan;
+reset enable_indexonlyscan;
+reset enable_bitmapscan;
+alter table tenk2 reset (parallel_workers);
+
-- test parallel index scans.
set enable_seqscan to off;
set enable_bitmapscan to off;
On Thu, Aug 31, 2017 at 11:23 AM, Amit Kapila <amit.kapila16@gmail.com> wrote:
On Mon, Aug 21, 2017 at 2:40 PM, Amit Kapila <amit.kapila16@gmail.com> wrote:
On Mon, Aug 21, 2017 at 1:44 PM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:Thanks for adding more details. It is easy to understand.
I marked the patch as ready for committer in the commitfest.
Rebased the patch. The output of test case added by the patch is also
slightly changed because of the recent commit
7df2c1f8daeb361133ac8bdeaf59ceb0484e315a. I have verified that the
new test result is as expected.
The latest patch again needs to be rebased. Find rebased patch
attached with this email.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
pq_pushdown_initplan_v9.patchapplication/octet-stream; name=pq_pushdown_initplan_v9.patchDownload
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 4cee357..9e02a46 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -107,6 +107,7 @@ static void show_tidbitmap_info(BitmapHeapScanState *planstate,
static void show_instrumentation_count(const char *qlabel, int which,
PlanState *planstate, ExplainState *es);
static void show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es);
+static void show_eval_params(Bitmapset *bms_params, ExplainState *es);
static const char *explain_get_index_name(Oid indexId);
static void show_buffer_usage(ExplainState *es, const BufferUsage *usage);
static void ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir,
@@ -637,7 +638,17 @@ ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc)
*/
ps = queryDesc->planstate;
if (IsA(ps, GatherState) &&((Gather *) ps->plan)->invisible)
+ {
+ List *initPlanState = NULL;
+ PlanState *save_ps;
+
+ /* initplans are always attached to the top node (cf standard_planner) */
+ save_ps = ps;
+ initPlanState = ps->initPlan;
ps = outerPlanState(ps);
+ ps->initPlan = initPlanState;
+ save_ps->initPlan = NIL;
+ }
ExplainNode(ps, NIL, NULL, NULL, es);
}
@@ -1447,6 +1458,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
planstate, es);
ExplainPropertyInteger("Workers Planned",
gather->num_workers, es);
+
+ /* Show params evaluated at gather node */
+ if (gather->initParam)
+ show_eval_params(gather->initParam, es);
+
if (es->analyze)
{
int nworkers;
@@ -1469,6 +1485,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
planstate, es);
ExplainPropertyInteger("Workers Planned",
gm->num_workers, es);
+
+ /* Show params evaluated at gather-merge node */
+ if (gm->initParam)
+ show_eval_params(gm->initParam, es);
+
if (es->analyze)
{
int nworkers;
@@ -2494,6 +2515,29 @@ show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es)
}
/*
+ * Show initplan params evaluated at gather or gather merge node.
+ */
+static void
+show_eval_params(Bitmapset *bms_params, ExplainState *es)
+{
+ int paramid = -1;
+ List *params = NIL;
+
+ Assert(bms_params);
+
+ while ((paramid = bms_next_member(bms_params, paramid)) >= 0)
+ {
+ char param[32];
+
+ snprintf(param, sizeof(param), "$%d", paramid);
+ params = lappend(params, pstrdup(param));
+ }
+
+ if (params)
+ ExplainPropertyList("Params Evaluated", params, es);
+}
+
+/*
* Fetch the name of an index in an EXPLAIN
*
* We allow plugins to get control here so that plans involving hypothetical
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index bd8a15d..5eff791 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -1861,6 +1861,33 @@ ExecEvalParamExec(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
}
/*
+ * ExecEvalParamExecParams
+ *
+ * Execute the subplan stored in PARAM_EXEC initplans params, if not executed
+ * till now.
+ */
+void
+ExecEvalParamExecParams(Bitmapset *params, EState *estate)
+{
+ ParamExecData *prm;
+ int paramid;
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ prm = &(estate->es_param_exec_vals[paramid]);
+
+ if (prm->execPlan != NULL)
+ {
+ /* Parameter not evaluated yet, so go do it */
+ ExecSetParamPlan(prm->execPlan, GetPerTupleExprContext(estate));
+ /* ExecSetParamPlan should have processed this param... */
+ Assert(prm->execPlan == NULL);
+ }
+ }
+}
+
+/*
* Evaluate a PARAM_EXTERN parameter.
*
* PARAM_EXTERN parameters must be sought in ecxt_param_list_info.
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index 8737cc1..e758521 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -23,6 +23,7 @@
#include "postgres.h"
+#include "executor/execExpr.h"
#include "executor/execParallel.h"
#include "executor/executor.h"
#include "executor/nodeBitmapHeapscan.h"
@@ -31,6 +32,7 @@
#include "executor/nodeIndexscan.h"
#include "executor/nodeIndexonlyscan.h"
#include "executor/nodeSeqscan.h"
+#include "executor/nodeSubplan.h"
#include "executor/nodeSort.h"
#include "executor/tqueue.h"
#include "nodes/nodeFuncs.h"
@@ -56,6 +58,7 @@
#define PARALLEL_KEY_INSTRUMENTATION UINT64CONST(0xE000000000000006)
#define PARALLEL_KEY_DSA UINT64CONST(0xE000000000000007)
#define PARALLEL_KEY_QUERY_TEXT UINT64CONST(0xE000000000000008)
+#define PARALLEL_KEY_INITPLAN_PARAMS UINT64CONST(0xE000000000000009)
#define PARALLEL_TUPLE_QUEUE_SIZE 65536
@@ -395,7 +398,8 @@ ExecParallelSetupTupleQueues(ParallelContext *pcxt, bool reinitialize)
* execution and return results to the main backend.
*/
ParallelExecutorInfo *
-ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
+ExecInitParallelPlan(PlanState *planstate, EState *estate,
+ Bitmapset *initParam, int nworkers,
int64 tuples_needed)
{
ParallelExecutorInfo *pei;
@@ -406,10 +410,12 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
char *pstmt_data;
char *pstmt_space;
char *param_space;
+ char *initplan_param_space;
BufferUsage *bufusage_space;
SharedExecutorInstrumentation *instrumentation = NULL;
int pstmt_len;
int param_len;
+ int initplan_param_len;
int instrumentation_len = 0;
int instrument_offset = 0;
Size dsa_minsize = dsa_minimum_size();
@@ -421,6 +427,8 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
pei->finished = false;
pei->planstate = planstate;
+ ExecEvalParamExecParams(initParam, estate);
+
/* Fix up and serialize plan to be sent to workers. */
pstmt_data = ExecSerializePlan(planstate->plan, estate);
@@ -454,6 +462,11 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
shm_toc_estimate_chunk(&pcxt->estimator, param_len);
shm_toc_estimate_keys(&pcxt->estimator, 1);
+ /* Estimate space for initplan params. */
+ initplan_param_len = EstimateInitPlanParamsSpace(estate->es_param_exec_vals, initParam);
+ shm_toc_estimate_chunk(&pcxt->estimator, initplan_param_len);
+ shm_toc_estimate_keys(&pcxt->estimator, 1);
+
/*
* Estimate space for BufferUsage.
*
@@ -528,6 +541,11 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
shm_toc_insert(pcxt->toc, PARALLEL_KEY_PARAMS, param_space);
SerializeParamList(estate->es_param_list_info, ¶m_space);
+ /* Store serialized initplan params. */
+ initplan_param_space = shm_toc_allocate(pcxt->toc, initplan_param_len);
+ shm_toc_insert(pcxt->toc, PARALLEL_KEY_INITPLAN_PARAMS, initplan_param_space);
+ SerializeInitPlanParams(estate->es_param_exec_vals, initParam, &initplan_param_space);
+
/* Allocate space for each worker's BufferUsage; no need to initialize. */
bufusage_space = shm_toc_allocate(pcxt->toc,
mul_size(sizeof(BufferUsage), pcxt->nworkers));
@@ -865,6 +883,21 @@ ExecParallelGetReceiver(dsm_segment *seg, shm_toc *toc)
}
/*
+ * Copy the ParamExecData params corresponding to initplans from dynamic
+ * shared memory. This has to be done once the params are allocated by
+ * executor; that is after ExecutorStart().
+ */
+static void
+ExecParallelInitializeInitPlanParams(shm_toc *toc, ParamExecData *params)
+{
+ char *paramspace;
+
+ /* Reconstruct initplan params. */
+ paramspace = shm_toc_lookup(toc, PARALLEL_KEY_INITPLAN_PARAMS, false);
+ RestoreInitPlanParams(¶mspace, params);
+}
+
+/*
* Create a QueryDesc for the PlannedStmt we are to execute, and return it.
*/
static QueryDesc *
@@ -1049,6 +1082,7 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
/* Special executor initialization steps for parallel workers */
queryDesc->planstate->state->es_query_dsa = area;
+ ExecParallelInitializeInitPlanParams(toc, queryDesc->estate->es_param_exec_vals);
ExecParallelInitializeWorker(queryDesc->planstate, toc);
/* Pass down any tuple bound */
diff --git a/src/backend/executor/nodeGather.c b/src/backend/executor/nodeGather.c
index 022d75b..4e35c51 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -158,6 +158,7 @@ ExecGather(PlanState *pstate)
if (!node->pei)
node->pei = ExecInitParallelPlan(node->ps.lefttree,
estate,
+ gather->initParam,
gather->num_workers,
node->tuples_needed);
else
diff --git a/src/backend/executor/nodeGatherMerge.c b/src/backend/executor/nodeGatherMerge.c
index d20d466..ffff1a7 100644
--- a/src/backend/executor/nodeGatherMerge.c
+++ b/src/backend/executor/nodeGatherMerge.c
@@ -202,6 +202,7 @@ ExecGatherMerge(PlanState *pstate)
if (!node->pei)
node->pei = ExecInitParallelPlan(node->ps.lefttree,
estate,
+ gm->initParam,
gm->num_workers,
node->tuples_needed);
else
diff --git a/src/backend/executor/nodeNestloop.c b/src/backend/executor/nodeNestloop.c
index 4447b7c..52514e4 100644
--- a/src/backend/executor/nodeNestloop.c
+++ b/src/backend/executor/nodeNestloop.c
@@ -130,6 +130,7 @@ ExecNestLoop(PlanState *pstate)
{
NestLoopParam *nlp = (NestLoopParam *) lfirst(lc);
int paramno = nlp->paramno;
+ TupleDesc tdesc = outerTupleSlot->tts_tupleDescriptor;
ParamExecData *prm;
prm = &(econtext->ecxt_param_exec_vals[paramno]);
@@ -140,6 +141,7 @@ ExecNestLoop(PlanState *pstate)
prm->value = slot_getattr(outerTupleSlot,
nlp->paramval->varattno,
&(prm->isnull));
+ prm->ptype = TupleDescAttr(tdesc, nlp->paramval->varattno - 1)->atttypid;
/* Flag parameter value as changed */
innerPlan->chgParam = bms_add_member(innerPlan->chgParam,
paramno);
diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c
index 77ef6f3..06409ed 100644
--- a/src/backend/executor/nodeSubplan.c
+++ b/src/backend/executor/nodeSubplan.c
@@ -30,12 +30,16 @@
#include <math.h>
#include "access/htup_details.h"
+#include "catalog/pg_type.h"
#include "executor/executor.h"
#include "executor/nodeSubplan.h"
#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
#include "miscadmin.h"
#include "optimizer/clauses.h"
+#include "storage/shmem.h"
#include "utils/array.h"
+#include "utils/datum.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
@@ -273,11 +277,13 @@ ExecScanSubPlan(SubPlanState *node,
forboth(l, subplan->parParam, pvar, node->args)
{
int paramid = lfirst_int(l);
+ ExprState *exprstate = (ExprState *) lfirst(pvar);
ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]);
- prm->value = ExecEvalExprSwitchContext((ExprState *) lfirst(pvar),
+ prm->value = ExecEvalExprSwitchContext(exprstate,
econtext,
&(prm->isnull));
+ prm->ptype = exprType((Node *) exprstate->expr);
planstate->chgParam = bms_add_member(planstate->chgParam, paramid);
}
@@ -390,6 +396,7 @@ ExecScanSubPlan(SubPlanState *node,
prmdata = &(econtext->ecxt_param_exec_vals[paramid]);
Assert(prmdata->execPlan == NULL);
prmdata->value = slot_getattr(slot, col, &(prmdata->isnull));
+ prmdata->ptype = TupleDescAttr(tdesc, col - 1)->atttypid;
col++;
}
@@ -556,11 +563,13 @@ buildSubPlanHash(SubPlanState *node, ExprContext *econtext)
{
int paramid = lfirst_int(plst);
ParamExecData *prmdata;
+ TupleDesc tdesc = slot->tts_tupleDescriptor;
prmdata = &(innerecontext->ecxt_param_exec_vals[paramid]);
Assert(prmdata->execPlan == NULL);
prmdata->value = slot_getattr(slot, col,
&(prmdata->isnull));
+ prmdata->ptype = TupleDescAttr(tdesc, col - 1)->atttypid;
col++;
}
slot = ExecProject(node->projRight);
@@ -924,6 +933,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
ListCell *l;
bool found = false;
ArrayBuildStateAny *astate = NULL;
+ Oid ptype;
if (subLinkType == ANY_SUBLINK ||
subLinkType == ALL_SUBLINK)
@@ -931,6 +941,8 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
if (subLinkType == CTE_SUBLINK)
elog(ERROR, "CTE subplans should not be executed via ExecSetParamPlan");
+ ptype = exprType((Node *) node->subplan);
+
/* Initialize ArrayBuildStateAny in caller's context, if needed */
if (subLinkType == ARRAY_SUBLINK)
astate = initArrayResultAny(subplan->firstColType,
@@ -953,11 +965,13 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
forboth(l, subplan->parParam, pvar, node->args)
{
int paramid = lfirst_int(l);
+ ExprState *exprstate = (ExprState *) lfirst(pvar);
ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]);
- prm->value = ExecEvalExprSwitchContext((ExprState *) lfirst(pvar),
+ prm->value = ExecEvalExprSwitchContext(exprstate,
econtext,
&(prm->isnull));
+ prm->ptype = exprType((Node *) exprstate->expr);
planstate->chgParam = bms_add_member(planstate->chgParam, paramid);
}
@@ -980,6 +994,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = BoolGetDatum(true);
+ prm->ptype = ptype;
prm->isnull = false;
found = true;
break;
@@ -1031,6 +1046,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = heap_getattr(node->curTuple, i, tdesc,
&(prm->isnull));
+ prm->ptype = TupleDescAttr(tdesc, i - 1)->atttypid;
i++;
}
}
@@ -1053,6 +1069,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
true);
prm->execPlan = NULL;
prm->value = node->curArray;
+ prm->ptype = ptype;
prm->isnull = false;
}
else if (!found)
@@ -1065,6 +1082,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = BoolGetDatum(false);
+ prm->ptype = ptype;
prm->isnull = false;
}
else
@@ -1077,6 +1095,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = (Datum) 0;
+ prm->ptype = VOIDOID;
prm->isnull = true;
}
}
@@ -1207,3 +1226,133 @@ ExecAlternativeSubPlan(AlternativeSubPlanState *node,
return ExecSubPlan(activesp, econtext, isNull);
}
+
+/*
+ * Estimate the amount of space required to serialize the InitPlan params.
+ */
+Size
+EstimateInitPlanParamsSpace(ParamExecData *paramExecVals, Bitmapset *params)
+{
+ int paramid;
+ Size sz = sizeof(int);
+ ParamExecData *prm;
+
+ if (params == NULL)
+ return sz;
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ Oid typeOid;
+ int16 typLen;
+ bool typByVal;
+
+ prm = &(paramExecVals[paramid]);
+ typeOid = prm->ptype;
+
+ sz = add_size(sz, sizeof(int)); /* space for paramid */
+ sz = add_size(sz, sizeof(Oid)); /* space for type OID */
+
+ /* space for datum/isnull */
+ if (OidIsValid(typeOid))
+ get_typlenbyval(typeOid, &typLen, &typByVal);
+ else
+ {
+ /* If no type OID, assume by-value, like copyParamList does. */
+ typLen = sizeof(Datum);
+ typByVal = true;
+ }
+ sz = add_size(sz,
+ datumEstimateSpace(prm->value, prm->isnull, typByVal, typLen));
+ }
+ return sz;
+}
+
+/*
+ * Serialize ParamExecData params corresponding to initplans.
+ *
+ * We write the number of parameters first, as a 4-byte integer, and then
+ * write details for each parameter in turn. The details for each parameter
+ * consist of a 4-byte paramid (location of param in execution time internal
+ * parameter array), 4-byte type OID, and then the datum as serialized by
+ * datumSerialize().
+ *
+ * The above format is quite similar to the format used to serialize
+ * paramListInfo structure, so if we change either format, then consider to
+ * change at both the places.
+ */
+void
+SerializeInitPlanParams(ParamExecData *paramExecVals, Bitmapset *params,
+ char **start_address)
+{
+ int nparams;
+ int paramid;
+ ParamExecData *prm;
+
+ nparams = bms_num_members(params);
+ memcpy(*start_address, &nparams, sizeof(int));
+ *start_address += sizeof(int);
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ Oid typeOid;
+ int16 typLen;
+ bool typByVal;
+
+ prm = &(paramExecVals[paramid]);
+ typeOid = prm->ptype;
+
+ /* Write paramid. */
+ memcpy(*start_address, ¶mid, sizeof(int));
+ *start_address += sizeof(int);
+
+ /* Write OID. */
+ memcpy(*start_address, &typeOid, sizeof(Oid));
+ *start_address += sizeof(Oid);
+
+ /* space for datum/isnull */
+ if (OidIsValid(typeOid))
+ get_typlenbyval(typeOid, &typLen, &typByVal);
+ else
+ {
+ /* If no type OID, assume by-value, like copyParamList does. */
+ typLen = sizeof(Datum);
+ typByVal = true;
+ }
+ datumSerialize(prm->value, prm->isnull, typByVal, typLen,
+ start_address);
+ }
+}
+
+/*
+ * Restore ParamExecData params corresponding to initplans.
+ */
+void
+RestoreInitPlanParams(char **start_address, ParamExecData *params)
+{
+ int nparams;
+ int i;
+ int paramid;
+
+ memcpy(&nparams, *start_address, sizeof(int));
+ *start_address += sizeof(int);
+
+ for (i = 0; i < nparams; i++)
+ {
+ ParamExecData *prm;
+
+ /* Read paramid */
+ memcpy(¶mid, *start_address, sizeof(int));
+ *start_address += sizeof(int);
+ prm = ¶ms[paramid];
+
+ /* Read type OID. */
+ memcpy(&prm->ptype, *start_address, sizeof(Oid));
+ *start_address += sizeof(Oid);
+
+ /* Read datum/isnull. */
+ prm->value = datumRestore(start_address, &prm->isnull);
+ prm->execPlan = NULL;
+ }
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index f1bed14..dbac5fb 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -364,6 +364,7 @@ _copyGather(const Gather *from)
COPY_SCALAR_FIELD(rescan_param);
COPY_SCALAR_FIELD(single_copy);
COPY_SCALAR_FIELD(invisible);
+ COPY_BITMAPSET_FIELD(initParam);
return newnode;
}
@@ -391,6 +392,7 @@ _copyGatherMerge(const GatherMerge *from)
COPY_POINTER_FIELD(sortOperators, from->numCols * sizeof(Oid));
COPY_POINTER_FIELD(collations, from->numCols * sizeof(Oid));
COPY_POINTER_FIELD(nullsFirst, from->numCols * sizeof(bool));
+ COPY_BITMAPSET_FIELD(initParam);
return newnode;
}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index b83d919..9ead312 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -482,6 +482,7 @@ _outGather(StringInfo str, const Gather *node)
WRITE_INT_FIELD(rescan_param);
WRITE_BOOL_FIELD(single_copy);
WRITE_BOOL_FIELD(invisible);
+ WRITE_BITMAPSET_FIELD(initParam);
}
static void
@@ -512,6 +513,8 @@ _outGatherMerge(StringInfo str, const GatherMerge *node)
appendStringInfoString(str, " :nullsFirst");
for (i = 0; i < node->numCols; i++)
appendStringInfo(str, " %s", booltostr(node->nullsFirst[i]));
+
+ WRITE_BITMAPSET_FIELD(initParam);
}
static void
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index fbf8330..baee0cd 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2166,6 +2166,7 @@ _readGather(void)
READ_INT_FIELD(rescan_param);
READ_BOOL_FIELD(single_copy);
READ_BOOL_FIELD(invisible);
+ READ_BITMAPSET_FIELD(initParam);
READ_DONE();
}
@@ -2187,6 +2188,7 @@ _readGatherMerge(void)
READ_OID_ARRAY(sortOperators, local_node->numCols);
READ_OID_ARRAY(collations, local_node->numCols);
READ_BOOL_ARRAY(nullsFirst, local_node->numCols);
+ READ_BITMAPSET_FIELD(initParam);
READ_DONE();
}
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 5b746a9..1044b0e 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -39,6 +39,7 @@
#include "optimizer/planner.h"
#include "optimizer/prep.h"
#include "optimizer/restrictinfo.h"
+#include "optimizer/subselect.h"
#include "optimizer/tlist.h"
#include "optimizer/var.h"
#include "parser/parse_clause.h"
@@ -2238,6 +2239,14 @@ generate_gather_paths(PlannerInfo *root, RelOptInfo *rel)
Path *simple_gather_path;
ListCell *lc;
+ /*
+ * We don't want to generate gather or gather merge node if there are
+ * initplans at some query level below the current query level as those
+ * plans could be parallel-unsafe or undirect correlated plans.
+ */
+ if (contains_parallel_unsafe_param(root, rel))
+ return;
+
/* If there are no partial paths, there's nothing to do here. */
if (rel->partial_pathlist == NIL)
return;
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 2821662..0363d68 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -6257,6 +6257,7 @@ make_gather(List *qptlist,
node->rescan_param = rescan_param;
node->single_copy = single_copy;
node->invisible = false;
+ node->initParam = NULL;
return node;
}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 7f146d6..47a6b0d 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -366,6 +366,14 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
{
Gather *gather = makeNode(Gather);
+ /*
+ * If there are any initPlans attached to the formerly-top plan node,
+ * move them up to the Gather node; same as we do for Material node in
+ * materialize_finished_plan.
+ */
+ gather->plan.initPlan = top_plan->initPlan;
+ top_plan->initPlan = NIL;
+
gather->plan.targetlist = top_plan->targetlist;
gather->plan.qual = NIL;
gather->plan.lefttree = top_plan;
@@ -3580,6 +3588,12 @@ create_grouping_paths(PlannerInfo *root,
ListCell *lc;
+ /*
+ * Don't parallelize the plan if there is an initplan below current query
+ * level. See generate_gather_paths() for detailed reason.
+ */
+ (void) contains_parallel_unsafe_param(root, input_rel);
+
/* For now, do all work in the (GROUP_AGG, NULL) upperrel */
grouped_rel = fetch_upper_rel(root, UPPERREL_GROUP_AGG, NULL);
@@ -4942,6 +4956,12 @@ create_ordered_paths(PlannerInfo *root,
RelOptInfo *ordered_rel;
ListCell *lc;
+ /*
+ * Don't parallelize the plan if there is an initplan below current query
+ * level. See generate_gather_paths() for detailed reason.
+ */
+ (void) contains_parallel_unsafe_param(root, input_rel);
+
/* For now, do all work in the (ORDERED, NULL) upperrel */
ordered_rel = fetch_upper_rel(root, UPPERREL_ORDERED, NULL);
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index b0c9e94..5fa2bb1 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -104,6 +104,7 @@ static Node *fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context);
static bool fix_scan_expr_walker(Node *node, fix_scan_expr_context *context);
static void set_join_references(PlannerInfo *root, Join *join, int rtoffset);
static void set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset);
+static void set_param_references(PlannerInfo *root, Plan *plan);
static Node *convert_combining_aggrefs(Node *node, void *context);
static void set_dummy_tlist_references(Plan *plan, int rtoffset);
static indexed_tlist *build_tlist_index(List *tlist);
@@ -628,7 +629,10 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
case T_Gather:
case T_GatherMerge:
- set_upper_references(root, plan, rtoffset);
+ {
+ set_upper_references(root, plan, rtoffset);
+ set_param_references(root, plan);
+ }
break;
case T_Hash:
@@ -1784,6 +1788,49 @@ set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset)
}
/*
+ * set_param_references
+ * Initialize the initParam list in gather or gather merge node such that
+ * it contains reference of all the params that needs to be evaluated
+ * before execution of the node. It contains the initplan params that are
+ * being passed to the plan nodes below it.
+ */
+static void
+set_param_references(PlannerInfo *root, Plan *plan)
+{
+ Assert(IsA(plan, Gather) || IsA(plan, GatherMerge));
+
+ if (plan->lefttree->extParam)
+ {
+ PlannerInfo *proot;
+ Bitmapset *initSetParam = NULL;
+ ListCell *l;
+
+ for (proot = root; proot != NULL; proot = proot->parent_root)
+ {
+ foreach(l, proot->init_plans)
+ {
+ SubPlan *initsubplan = (SubPlan *) lfirst(l);
+ ListCell *l2;
+
+ foreach(l2, initsubplan->setParam)
+ {
+ initSetParam = bms_add_member(initSetParam, lfirst_int(l2));
+ }
+ }
+ }
+
+ /*
+ * Remember the list of all external initplan params that are used by
+ * the children of gather or gather merge node.
+ */
+ if (IsA(plan, Gather))
+ ((Gather *) plan)->initParam = bms_intersect(plan->lefttree->extParam, initSetParam);
+ else
+ ((GatherMerge *) plan)->initParam = bms_intersect(plan->lefttree->extParam, initSetParam);
+ }
+}
+
+/*
* Recursively scan an expression tree and convert Aggrefs to the proper
* intermediate form for combining aggregates. This means (1) replacing each
* one's argument list with a single argument that is the original Aggref
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 1103984..8c99f48 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -84,6 +84,7 @@ static Bitmapset *finalize_plan(PlannerInfo *root,
Bitmapset *scan_params);
static bool finalize_primnode(Node *node, finalize_primnode_context *context);
static bool finalize_agg_primnode(Node *node, finalize_primnode_context *context);
+static bool is_initplan_is_below_current_query_level(PlannerInfo *root);
/*
@@ -2136,13 +2137,11 @@ SS_identify_outer_params(PlannerInfo *root)
}
/*
- * SS_charge_for_initplans - account for initplans in Path costs & parallelism
+ * SS_charge_for_initplans - account for initplans in Path costs
*
* If any initPlans have been created in the current query level, they will
* get attached to the Plan tree created from whichever Path we select from
- * the given rel. Increment all that rel's Paths' costs to account for them,
- * and make sure the paths get marked as parallel-unsafe, since we can't
- * currently transmit initPlans to parallel workers.
+ * the given rel. Increment all that rel's Paths' costs to account for them.
*
* This is separate from SS_attach_initplans because we might conditionally
* create more initPlans during create_plan(), depending on which Path we
@@ -2174,7 +2173,7 @@ SS_charge_for_initplans(PlannerInfo *root, RelOptInfo *final_rel)
}
/*
- * Now adjust the costs and parallel_safe flags.
+ * Now adjust the costs.
*/
foreach(lc, final_rel->pathlist)
{
@@ -2182,7 +2181,6 @@ SS_charge_for_initplans(PlannerInfo *root, RelOptInfo *final_rel)
path->startup_cost += initplan_cost;
path->total_cost += initplan_cost;
- path->parallel_safe = false;
}
/* We needn't do set_cheapest() here, caller will do it */
@@ -3000,3 +2998,61 @@ SS_make_initplan_from_plan(PlannerInfo *root,
/* Set costs of SubPlan using info from the plan tree */
cost_subplan(subroot, node, plan);
}
+
+/*
+ * is_initplan_below_current_query_level - is there any initplan present below
+ * current query level.
+ */
+static bool
+is_initplan_is_below_current_query_level(PlannerInfo *root)
+{
+ ListCell *lc;
+
+ /*
+ * If the subplan corresponding to the subroot is an initPlan, it'll be
+ * attached to its parent root. Hence, we check the query level of its
+ * parent root and if any init_plans are attached there.
+ */
+ foreach(lc, root->glob->subroots)
+ {
+ PlannerInfo *subroot = (PlannerInfo *) lfirst(lc);
+ PlannerInfo *proot = subroot->parent_root;
+
+ if (proot->query_level > root->query_level && proot->init_plans)
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * contains_parallel_unsafe_param - Check if there is any initplan present below
+ * current query level, mark all the partial and non-partial paths for a relation
+ * at this level as parallel-unsafe.
+ */
+bool
+contains_parallel_unsafe_param(PlannerInfo *root, RelOptInfo *rel)
+{
+ ListCell *lc;
+
+ if (is_initplan_is_below_current_query_level(root))
+ {
+ foreach(lc, rel->partial_pathlist)
+ {
+ Path *subpath = (Path *) lfirst(lc);
+
+ subpath->parallel_safe = false;
+ }
+ foreach(lc, rel->pathlist)
+ {
+ Path *subpath = (Path *) lfirst(lc);
+
+ subpath->parallel_safe = false;
+ }
+ rel->consider_parallel = false;
+
+ return true;
+ }
+
+ return false;
+}
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 93add27..48e502c 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -91,6 +91,7 @@ typedef struct
typedef struct
{
+ PlannerInfo *root;
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 */
@@ -1069,6 +1070,7 @@ max_parallel_hazard(Query *parse)
{
max_parallel_hazard_context context;
+ context.root = NULL;
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_UNSAFE;
context.safe_param_ids = NIL;
@@ -1098,6 +1100,7 @@ is_parallel_safe(PlannerInfo *root, Node *node)
root->glob->nParamExec == 0)
return true;
/* Else use max_parallel_hazard's search logic, but stop on RESTRICTED */
+ context.root = root;
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_RESTRICTED;
context.safe_param_ids = NIL;
@@ -1222,21 +1225,47 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
}
/*
- * We can't pass Params to workers at the moment either, so they are also
- * parallel-restricted, unless they are PARAM_EXEC Params listed in
+ * As of now, we can only pass Params that refer to the same or parent
+ * query level (see generate_gather_paths) or they are listed in
* safe_param_ids, meaning they could be generated within the worker.
*/
else if (IsA(node, Param))
{
+ int paramid;
+ PlannerInfo *root;
Param *param = (Param *) node;
- if (param->paramkind != PARAM_EXEC ||
- !list_member_int(context->safe_param_ids, param->paramid))
+ if (list_member_int(context->safe_param_ids, param->paramid))
+ return false;
+
+ root = context->root;
+ paramid = ((Param *) node)->paramid;
+
+ if (root)
{
- if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
- return true;
+ PlannerInfo *proot;
+ ListCell *l;
+
+ for (proot = root; proot != NULL; proot = proot->parent_root)
+ {
+ foreach(l, proot->init_plans)
+ {
+ SubPlan *initsubplan = (SubPlan *) lfirst(l);
+ ListCell *l2;
+
+ foreach(l2, initsubplan->setParam)
+ {
+ int initparam = lfirst_int(l2);
+
+ if (paramid == initparam)
+ return false;
+ }
+ }
+ }
}
- return false; /* nothing to recurse to */
+
+ if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
+ return true;
}
/*
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 8ee0496..c44ad88 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -611,6 +611,7 @@ extern ExprEvalOp ExecEvalStepOp(ExprState *state, ExprEvalStep *op);
*/
extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
+extern void ExecEvalParamExecParams(Bitmapset *params, EState *estate);
extern void ExecEvalParamExtern(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
extern void ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op);
diff --git a/src/include/executor/execParallel.h b/src/include/executor/execParallel.h
index ed231f2..e62c596 100644
--- a/src/include/executor/execParallel.h
+++ b/src/include/executor/execParallel.h
@@ -35,7 +35,8 @@ typedef struct ParallelExecutorInfo
} ParallelExecutorInfo;
extern ParallelExecutorInfo *ExecInitParallelPlan(PlanState *planstate,
- EState *estate, int nworkers, int64 tuples_needed);
+ EState *estate, Bitmapset *initParam, int nworkers,
+ int64 tuples_needed);
extern void ExecParallelCreateReaders(ParallelExecutorInfo *pei,
TupleDesc tupDesc);
extern void ExecParallelFinish(ParallelExecutorInfo *pei);
diff --git a/src/include/executor/nodeSubplan.h b/src/include/executor/nodeSubplan.h
index 5dbaeeb..40b1a34 100644
--- a/src/include/executor/nodeSubplan.h
+++ b/src/include/executor/nodeSubplan.h
@@ -28,4 +28,10 @@ extern void ExecReScanSetParamPlan(SubPlanState *node, PlanState *parent);
extern void ExecSetParamPlan(SubPlanState *node, ExprContext *econtext);
+extern Size EstimateInitPlanParamsSpace(ParamExecData *paramExecVals, Bitmapset *params);
+
+extern void SerializeInitPlanParams(ParamExecData *paramExecVals, Bitmapset *params, char **start_address);
+
+extern void RestoreInitPlanParams(char **start_address, ParamExecData *params);
+
#endif /* NODESUBPLAN_H */
diff --git a/src/include/nodes/params.h b/src/include/nodes/params.h
index 55219da..e96a1ea 100644
--- a/src/include/nodes/params.h
+++ b/src/include/nodes/params.h
@@ -98,6 +98,16 @@ typedef struct ParamExecData
{
void *execPlan; /* should be "SubPlanState *" */
Datum value;
+
+ /*
+ * parameter's datatype, or 0. This is required so that datum value can
+ * be read and used for other purposes like passing it to worker backend
+ * via shared memory. This is required only for initPlan's evaluation,
+ * however for consistency we set this for Subplan as well. We left it
+ * for other cases like CTE or RecursiveUnion cases where this structure
+ * is not used for evaluation of subplans.
+ */
+ Oid ptype;
bool isnull;
} ParamExecData;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index a382331..a16909c 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -841,6 +841,8 @@ typedef struct Gather
int rescan_param; /* ID of Param that signals a rescan, or -1 */
bool single_copy; /* don't execute plan more than once */
bool invisible; /* suppress EXPLAIN display (for testing)? */
+ Bitmapset *initParam; /* param id's of initplans which are referred
+ * at gather or one of it's child node */
} Gather;
/* ------------
@@ -858,6 +860,8 @@ typedef struct GatherMerge
Oid *sortOperators; /* OIDs of operators to sort them by */
Oid *collations; /* OIDs of collations */
bool *nullsFirst; /* NULLS FIRST/LAST directions */
+ Bitmapset *initParam; /* param id's of initplans which are referred
+ * at gather merge or one of it's child node */
} GatherMerge;
/* ----------------
diff --git a/src/include/optimizer/subselect.h b/src/include/optimizer/subselect.h
index ecd2011..a35df55 100644
--- a/src/include/optimizer/subselect.h
+++ b/src/include/optimizer/subselect.h
@@ -35,6 +35,8 @@ extern Param *SS_make_initplan_output_param(PlannerInfo *root,
extern void SS_make_initplan_from_plan(PlannerInfo *root,
PlannerInfo *subroot, Plan *plan,
Param *prm);
+extern bool initplan_is_below_current_query_level(PlannerInfo *root);
+extern bool contains_parallel_unsafe_param(PlannerInfo *root, RelOptInfo *rel);
extern Param *assign_nestloop_param_var(PlannerInfo *root, Var *var);
extern Param *assign_nestloop_param_placeholdervar(PlannerInfo *root,
PlaceHolderVar *phv);
diff --git a/src/test/regress/expected/select_parallel.out b/src/test/regress/expected/select_parallel.out
index 2ae600f..22af824 100644
--- a/src/test/regress/expected/select_parallel.out
+++ b/src/test/regress/expected/select_parallel.out
@@ -139,6 +139,39 @@ explain (costs off)
(4 rows)
alter table tenk2 reset (parallel_workers);
+--test parallel plan for a query containing initplan.
+set enable_indexscan = off;
+set enable_indexonlyscan = off;
+set enable_bitmapscan = off;
+alter table tenk2 set (parallel_workers = 2);
+explain (costs off)
+ select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+ QUERY PLAN
+------------------------------------------------------
+ Aggregate
+ InitPlan 1 (returns $2)
+ -> Finalize Aggregate
+ -> Gather
+ Workers Planned: 2
+ -> Partial Aggregate
+ -> Parallel Seq Scan on tenk2
+ -> Gather
+ Workers Planned: 4
+ Params Evaluated: $2
+ -> Parallel Seq Scan on tenk1
+ Filter: (unique1 = $2)
+(12 rows)
+
+select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+ count
+-------
+ 1
+(1 row)
+
+reset enable_indexscan;
+reset enable_indexonlyscan;
+reset enable_bitmapscan;
+alter table tenk2 reset (parallel_workers);
-- test parallel index scans.
set enable_seqscan to off;
set enable_bitmapscan to off;
diff --git a/src/test/regress/sql/select_parallel.sql b/src/test/regress/sql/select_parallel.sql
index 89fe80a..ae144ca 100644
--- a/src/test/regress/sql/select_parallel.sql
+++ b/src/test/regress/sql/select_parallel.sql
@@ -52,6 +52,21 @@ explain (costs off)
(select ten from tenk2);
alter table tenk2 reset (parallel_workers);
+--test parallel plan for a query containing initplan.
+set enable_indexscan = off;
+set enable_indexonlyscan = off;
+set enable_bitmapscan = off;
+alter table tenk2 set (parallel_workers = 2);
+
+explain (costs off)
+ select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+
+reset enable_indexscan;
+reset enable_indexonlyscan;
+reset enable_bitmapscan;
+alter table tenk2 reset (parallel_workers);
+
-- test parallel index scans.
set enable_seqscan to off;
set enable_bitmapscan to off;
On 15 Sep 2017, at 04:45, Amit Kapila <amit.kapila16@gmail.com> wrote:
On Thu, Aug 31, 2017 at 11:23 AM, Amit Kapila <amit.kapila16@gmail.com> wrote:
On Mon, Aug 21, 2017 at 2:40 PM, Amit Kapila <amit.kapila16@gmail.com> wrote:
On Mon, Aug 21, 2017 at 1:44 PM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:Thanks for adding more details. It is easy to understand.
I marked the patch as ready for committer in the commitfest.
Rebased the patch. The output of test case added by the patch is also
slightly changed because of the recent commit
7df2c1f8daeb361133ac8bdeaf59ceb0484e315a. I have verified that the
new test result is as expected.The latest patch again needs to be rebased. Find rebased patch
attached with this email.
Moved to next commitfest, but changed to Waiting for author since it no longer
applies cleanly.
cheers ./daniel
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Sep 14, 2017 at 10:45 PM, Amit Kapila <amit.kapila16@gmail.com> wrote:
The latest patch again needs to be rebased. Find rebased patch
attached with this email.
I read through this patch this morning. Copying Tom in the hopes
that he might chime in on the following two issues in particular:
1. Is there any superior alternative to adding ptype to ParamExecData?
I think the reason why we don't have this already is because the plan
tree that populates the Param must output the right type and the plan
tree that reads the Param must expect the right type, and there's no
need for anything else. But for serialization and deserialization
this seems to be necessary. I wonder whether it would be better to
try to capture this at the time the Param is generated (e.g.
var->vartype in assign_param_for_var) rather than derived at execution
time by applying exprType(), but I'm not sure how that would work
exactly, or whether it's really any better.
2. Do max_parallel_hazard_walker and set_param_references() have the
right idea about which parameters are acceptable candidates for this
optimization? The idea seems to be to collect the setParam sets for
all initplans between the current query level and the root. That
looks correct to me but I'm not an expert on this parameter stuff.
Some other comments:
- I think parallel_hazard_walker should likely work by setting
safe_param_ids to the right set of parameter IDs rather than testing
whether the parameter is safe by checking either whether it is in
safe_param_ids or some other condition is met.
- contains_parallel_unsafe_param() sounds like a function that merely
returns true or false, but it actually has major side effects. Those
side effects also look unsafe; mutating previously-generated paths can
corrupt the rel's pathlist, because it will no longer have the sort
order and other characteristics that add_path() creates and upon which
other code relies.
- Can't is_initplan_is_below_current_query_level() be confused when
there are multiple subqueries in the tree? For example if the
toplevel query has subqueries a and b and a has a sub-subquery aa
which has an initplan, won't this function think that b has an
initplan below the current query level? If not, maybe a comment is in
order explaining why not?
- Why do we even need contains_parallel_unsafe_param() and
is_initplan_is_below_current_query_level() in the first place, anyway?
I think there's been some discussion of that on this thread, but I'm
not sure I completely understand it, and the comments in the patch
don't help me understand why we now need this restriction.
- The new code in ExplainPrintPlan() needs a comment.
- I have typically referred in comments to "Gather or Gather Merge"
rather than "gather or gather merge".
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Oct 2, 2017 at 8:13 PM, Robert Haas <robertmhaas@gmail.com> wrote:
On Thu, Sep 14, 2017 at 10:45 PM, Amit Kapila <amit.kapila16@gmail.com> wrote:
The latest patch again needs to be rebased. Find rebased patch
attached with this email.I read through this patch this morning. Copying Tom in the hopes
that he might chime in on the following two issues in particular:
It seems you forgot to include Tom, included now.
1. Is there any superior alternative to adding ptype to ParamExecData?
I think the reason why we don't have this already is because the plan
tree that populates the Param must output the right type and the plan
tree that reads the Param must expect the right type, and there's no
need for anything else. But for serialization and deserialization
this seems to be necessary. I wonder whether it would be better to
try to capture this at the time the Param is generated (e.g.
var->vartype in assign_param_for_var) rather than derived at execution
time by applying exprType(), but I'm not sure how that would work
exactly, or whether it's really any better.2. Do max_parallel_hazard_walker and set_param_references() have the
right idea about which parameters are acceptable candidates for this
optimization? The idea seems to be to collect the setParam sets for
all initplans between the current query level and the root. That
looks correct to me but I'm not an expert on this parameter stuff.Some other comments:
- I think parallel_hazard_walker should likely work by setting
safe_param_ids to the right set of parameter IDs rather than testing
whether the parameter is safe by checking either whether it is in
safe_param_ids or some other condition is met.
Okay, I think it should work the way you are suggesting. I will give it a try.
- contains_parallel_unsafe_param() sounds like a function that merely
returns true or false, but it actually has major side effects. Those
side effects also look unsafe; mutating previously-generated paths can
corrupt the rel's pathlist, because it will no longer have the sort
order and other characteristics that add_path() creates and upon which
other code relies.- Can't is_initplan_is_below_current_query_level() be confused when
there are multiple subqueries in the tree? For example if the
toplevel query has subqueries a and b and a has a sub-subquery aa
which has an initplan, won't this function think that b has an
initplan below the current query level? If not, maybe a comment is in
order explaining why not?
We can discuss above two points once you are convinced about whether
we need any such functions, so let's first discuss that.
- Why do we even need contains_parallel_unsafe_param() and
is_initplan_is_below_current_query_level() in the first place, anyway?
I think there's been some discussion of that on this thread, but I'm
not sure I completely understand it, and the comments in the patch
don't help me understand why we now need this restriction.
This is to ensure that initplans are never below Gather node. If we
allow parallelism when initplan is below current query level, (a) then
we need a way to pass the result of initplan from worker to other
workers and to master backend (b) if initplan contains reference to
some parameter above the current query level (undirect correlated
plans), then we need a mechanism to pass such parameters (basically
allow correlated initplans work).
Now, one might think (a) and or (b) is desirable so that it can be
used in more number of cases, but based on TPC-H analysis we have
found that it is quite useful even without those and in fact after we
support those cases the benefits might not be significant.
The patch contains few comments in generate_gather_paths and at the
top of functions contains_parallel_unsafe_param and
is_initplan_is_below_current_query_level, if you feel it should be
explained in further detail, then let me know.
- The new code in ExplainPrintPlan() needs a comment.
There was a comment, but I have added a somewhat detailed comment now,
check if that makes it clear.
- I have typically referred in comments to "Gather or Gather Merge"
rather than "gather or gather merge".
Changed as per suggestion.
Changed funcation name is_initplan_is_below_current_query_level to
is_initplan_below_current_query_level as well.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
pq_pushdown_initplan_v10.patchapplication/octet-stream; name=pq_pushdown_initplan_v10.patchDownload
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index c1602c5..6d644f1 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -107,6 +107,7 @@ static void show_tidbitmap_info(BitmapHeapScanState *planstate,
static void show_instrumentation_count(const char *qlabel, int which,
PlanState *planstate, ExplainState *es);
static void show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es);
+static void show_eval_params(Bitmapset *bms_params, ExplainState *es);
static const char *explain_get_index_name(Oid indexId);
static void show_buffer_usage(ExplainState *es, const BufferUsage *usage);
static void ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir,
@@ -633,7 +634,21 @@ ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc)
*/
ps = queryDesc->planstate;
if (IsA(ps, GatherState) &&((Gather *) ps->plan)->invisible)
+ {
+ List *initPlanState = NULL;
+ PlanState *save_ps;
+
+ /*
+ * The Gather node will be omitted in this case, so we need to move
+ * initplans to the outer node to ensure that they remain attached to
+ * the top node (cf standard_planner).
+ */
+ save_ps = ps;
+ initPlanState = ps->initPlan;
ps = outerPlanState(ps);
+ ps->initPlan = initPlanState;
+ save_ps->initPlan = NIL;
+ }
ExplainNode(ps, NIL, NULL, NULL, es);
}
@@ -1443,6 +1458,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
planstate, es);
ExplainPropertyInteger("Workers Planned",
gather->num_workers, es);
+
+ /* Show params evaluated at gather node */
+ if (gather->initParam)
+ show_eval_params(gather->initParam, es);
+
if (es->analyze)
{
int nworkers;
@@ -1465,6 +1485,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
planstate, es);
ExplainPropertyInteger("Workers Planned",
gm->num_workers, es);
+
+ /* Show params evaluated at gather-merge node */
+ if (gm->initParam)
+ show_eval_params(gm->initParam, es);
+
if (es->analyze)
{
int nworkers;
@@ -2490,6 +2515,29 @@ show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es)
}
/*
+ * Show initplan params evaluated at Gather or Gather Merge node.
+ */
+static void
+show_eval_params(Bitmapset *bms_params, ExplainState *es)
+{
+ int paramid = -1;
+ List *params = NIL;
+
+ Assert(bms_params);
+
+ while ((paramid = bms_next_member(bms_params, paramid)) >= 0)
+ {
+ char param[32];
+
+ snprintf(param, sizeof(param), "$%d", paramid);
+ params = lappend(params, pstrdup(param));
+ }
+
+ if (params)
+ ExplainPropertyList("Params Evaluated", params, es);
+}
+
+/*
* Fetch the name of an index in an EXPLAIN
*
* We allow plugins to get control here so that plans involving hypothetical
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index c5e97ef..d170f305 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -1927,6 +1927,33 @@ ExecEvalParamExec(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
}
/*
+ * ExecEvalParamExecParams
+ *
+ * Execute the subplan stored in PARAM_EXEC initplans params, if not executed
+ * till now.
+ */
+void
+ExecEvalParamExecParams(Bitmapset *params, EState *estate)
+{
+ ParamExecData *prm;
+ int paramid;
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ prm = &(estate->es_param_exec_vals[paramid]);
+
+ if (prm->execPlan != NULL)
+ {
+ /* Parameter not evaluated yet, so go do it */
+ ExecSetParamPlan(prm->execPlan, GetPerTupleExprContext(estate));
+ /* ExecSetParamPlan should have processed this param... */
+ Assert(prm->execPlan == NULL);
+ }
+ }
+}
+
+/*
* Evaluate a PARAM_EXTERN parameter.
*
* PARAM_EXTERN parameters must be sought in ecxt_param_list_info.
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index 5dc26ed..57a8fe5 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -23,6 +23,7 @@
#include "postgres.h"
+#include "executor/execExpr.h"
#include "executor/execParallel.h"
#include "executor/executor.h"
#include "executor/nodeBitmapHeapscan.h"
@@ -31,6 +32,7 @@
#include "executor/nodeIndexscan.h"
#include "executor/nodeIndexonlyscan.h"
#include "executor/nodeSeqscan.h"
+#include "executor/nodeSubplan.h"
#include "executor/nodeSort.h"
#include "executor/tqueue.h"
#include "nodes/nodeFuncs.h"
@@ -56,6 +58,7 @@
#define PARALLEL_KEY_INSTRUMENTATION UINT64CONST(0xE000000000000006)
#define PARALLEL_KEY_DSA UINT64CONST(0xE000000000000007)
#define PARALLEL_KEY_QUERY_TEXT UINT64CONST(0xE000000000000008)
+#define PARALLEL_KEY_INITPLAN_PARAMS UINT64CONST(0xE000000000000009)
#define PARALLEL_TUPLE_QUEUE_SIZE 65536
@@ -395,7 +398,8 @@ ExecParallelSetupTupleQueues(ParallelContext *pcxt, bool reinitialize)
* execution and return results to the main backend.
*/
ParallelExecutorInfo *
-ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
+ExecInitParallelPlan(PlanState *planstate, EState *estate,
+ Bitmapset *initParam, int nworkers,
int64 tuples_needed)
{
ParallelExecutorInfo *pei;
@@ -406,10 +410,12 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
char *pstmt_data;
char *pstmt_space;
char *param_space;
+ char *initplan_param_space;
BufferUsage *bufusage_space;
SharedExecutorInstrumentation *instrumentation = NULL;
int pstmt_len;
int param_len;
+ int initplan_param_len;
int instrumentation_len = 0;
int instrument_offset = 0;
Size dsa_minsize = dsa_minimum_size();
@@ -421,6 +427,8 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
pei->finished = false;
pei->planstate = planstate;
+ ExecEvalParamExecParams(initParam, estate);
+
/* Fix up and serialize plan to be sent to workers. */
pstmt_data = ExecSerializePlan(planstate->plan, estate);
@@ -454,6 +462,11 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
shm_toc_estimate_chunk(&pcxt->estimator, param_len);
shm_toc_estimate_keys(&pcxt->estimator, 1);
+ /* Estimate space for initplan params. */
+ initplan_param_len = EstimateInitPlanParamsSpace(estate->es_param_exec_vals, initParam);
+ shm_toc_estimate_chunk(&pcxt->estimator, initplan_param_len);
+ shm_toc_estimate_keys(&pcxt->estimator, 1);
+
/*
* Estimate space for BufferUsage.
*
@@ -528,6 +541,11 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
shm_toc_insert(pcxt->toc, PARALLEL_KEY_PARAMS, param_space);
SerializeParamList(estate->es_param_list_info, ¶m_space);
+ /* Store serialized initplan params. */
+ initplan_param_space = shm_toc_allocate(pcxt->toc, initplan_param_len);
+ shm_toc_insert(pcxt->toc, PARALLEL_KEY_INITPLAN_PARAMS, initplan_param_space);
+ SerializeInitPlanParams(estate->es_param_exec_vals, initParam, &initplan_param_space);
+
/* Allocate space for each worker's BufferUsage; no need to initialize. */
bufusage_space = shm_toc_allocate(pcxt->toc,
mul_size(sizeof(BufferUsage), pcxt->nworkers));
@@ -862,6 +880,21 @@ ExecParallelGetReceiver(dsm_segment *seg, shm_toc *toc)
}
/*
+ * Copy the ParamExecData params corresponding to initplans from dynamic
+ * shared memory. This has to be done once the params are allocated by
+ * executor; that is after ExecutorStart().
+ */
+static void
+ExecParallelInitializeInitPlanParams(shm_toc *toc, ParamExecData *params)
+{
+ char *paramspace;
+
+ /* Reconstruct initplan params. */
+ paramspace = shm_toc_lookup(toc, PARALLEL_KEY_INITPLAN_PARAMS, false);
+ RestoreInitPlanParams(¶mspace, params);
+}
+
+/*
* Create a QueryDesc for the PlannedStmt we are to execute, and return it.
*/
static QueryDesc *
@@ -1046,6 +1079,7 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
/* Special executor initialization steps for parallel workers */
queryDesc->planstate->state->es_query_dsa = area;
+ ExecParallelInitializeInitPlanParams(toc, queryDesc->estate->es_param_exec_vals);
ExecParallelInitializeWorker(queryDesc->planstate, toc);
/* Pass down any tuple bound */
diff --git a/src/backend/executor/nodeGather.c b/src/backend/executor/nodeGather.c
index 8370037..4104dc2 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -158,6 +158,7 @@ ExecGather(PlanState *pstate)
if (!node->pei)
node->pei = ExecInitParallelPlan(node->ps.lefttree,
estate,
+ gather->initParam,
gather->num_workers,
node->tuples_needed);
else
diff --git a/src/backend/executor/nodeGatherMerge.c b/src/backend/executor/nodeGatherMerge.c
index 70f33a9..f75430e 100644
--- a/src/backend/executor/nodeGatherMerge.c
+++ b/src/backend/executor/nodeGatherMerge.c
@@ -202,6 +202,7 @@ ExecGatherMerge(PlanState *pstate)
if (!node->pei)
node->pei = ExecInitParallelPlan(node->ps.lefttree,
estate,
+ gm->initParam,
gm->num_workers,
node->tuples_needed);
else
diff --git a/src/backend/executor/nodeNestloop.c b/src/backend/executor/nodeNestloop.c
index 4447b7c..52514e4 100644
--- a/src/backend/executor/nodeNestloop.c
+++ b/src/backend/executor/nodeNestloop.c
@@ -130,6 +130,7 @@ ExecNestLoop(PlanState *pstate)
{
NestLoopParam *nlp = (NestLoopParam *) lfirst(lc);
int paramno = nlp->paramno;
+ TupleDesc tdesc = outerTupleSlot->tts_tupleDescriptor;
ParamExecData *prm;
prm = &(econtext->ecxt_param_exec_vals[paramno]);
@@ -140,6 +141,7 @@ ExecNestLoop(PlanState *pstate)
prm->value = slot_getattr(outerTupleSlot,
nlp->paramval->varattno,
&(prm->isnull));
+ prm->ptype = TupleDescAttr(tdesc, nlp->paramval->varattno - 1)->atttypid;
/* Flag parameter value as changed */
innerPlan->chgParam = bms_add_member(innerPlan->chgParam,
paramno);
diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c
index 77ef6f3..06409ed 100644
--- a/src/backend/executor/nodeSubplan.c
+++ b/src/backend/executor/nodeSubplan.c
@@ -30,12 +30,16 @@
#include <math.h>
#include "access/htup_details.h"
+#include "catalog/pg_type.h"
#include "executor/executor.h"
#include "executor/nodeSubplan.h"
#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
#include "miscadmin.h"
#include "optimizer/clauses.h"
+#include "storage/shmem.h"
#include "utils/array.h"
+#include "utils/datum.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
@@ -273,11 +277,13 @@ ExecScanSubPlan(SubPlanState *node,
forboth(l, subplan->parParam, pvar, node->args)
{
int paramid = lfirst_int(l);
+ ExprState *exprstate = (ExprState *) lfirst(pvar);
ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]);
- prm->value = ExecEvalExprSwitchContext((ExprState *) lfirst(pvar),
+ prm->value = ExecEvalExprSwitchContext(exprstate,
econtext,
&(prm->isnull));
+ prm->ptype = exprType((Node *) exprstate->expr);
planstate->chgParam = bms_add_member(planstate->chgParam, paramid);
}
@@ -390,6 +396,7 @@ ExecScanSubPlan(SubPlanState *node,
prmdata = &(econtext->ecxt_param_exec_vals[paramid]);
Assert(prmdata->execPlan == NULL);
prmdata->value = slot_getattr(slot, col, &(prmdata->isnull));
+ prmdata->ptype = TupleDescAttr(tdesc, col - 1)->atttypid;
col++;
}
@@ -556,11 +563,13 @@ buildSubPlanHash(SubPlanState *node, ExprContext *econtext)
{
int paramid = lfirst_int(plst);
ParamExecData *prmdata;
+ TupleDesc tdesc = slot->tts_tupleDescriptor;
prmdata = &(innerecontext->ecxt_param_exec_vals[paramid]);
Assert(prmdata->execPlan == NULL);
prmdata->value = slot_getattr(slot, col,
&(prmdata->isnull));
+ prmdata->ptype = TupleDescAttr(tdesc, col - 1)->atttypid;
col++;
}
slot = ExecProject(node->projRight);
@@ -924,6 +933,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
ListCell *l;
bool found = false;
ArrayBuildStateAny *astate = NULL;
+ Oid ptype;
if (subLinkType == ANY_SUBLINK ||
subLinkType == ALL_SUBLINK)
@@ -931,6 +941,8 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
if (subLinkType == CTE_SUBLINK)
elog(ERROR, "CTE subplans should not be executed via ExecSetParamPlan");
+ ptype = exprType((Node *) node->subplan);
+
/* Initialize ArrayBuildStateAny in caller's context, if needed */
if (subLinkType == ARRAY_SUBLINK)
astate = initArrayResultAny(subplan->firstColType,
@@ -953,11 +965,13 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
forboth(l, subplan->parParam, pvar, node->args)
{
int paramid = lfirst_int(l);
+ ExprState *exprstate = (ExprState *) lfirst(pvar);
ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]);
- prm->value = ExecEvalExprSwitchContext((ExprState *) lfirst(pvar),
+ prm->value = ExecEvalExprSwitchContext(exprstate,
econtext,
&(prm->isnull));
+ prm->ptype = exprType((Node *) exprstate->expr);
planstate->chgParam = bms_add_member(planstate->chgParam, paramid);
}
@@ -980,6 +994,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = BoolGetDatum(true);
+ prm->ptype = ptype;
prm->isnull = false;
found = true;
break;
@@ -1031,6 +1046,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = heap_getattr(node->curTuple, i, tdesc,
&(prm->isnull));
+ prm->ptype = TupleDescAttr(tdesc, i - 1)->atttypid;
i++;
}
}
@@ -1053,6 +1069,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
true);
prm->execPlan = NULL;
prm->value = node->curArray;
+ prm->ptype = ptype;
prm->isnull = false;
}
else if (!found)
@@ -1065,6 +1082,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = BoolGetDatum(false);
+ prm->ptype = ptype;
prm->isnull = false;
}
else
@@ -1077,6 +1095,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = (Datum) 0;
+ prm->ptype = VOIDOID;
prm->isnull = true;
}
}
@@ -1207,3 +1226,133 @@ ExecAlternativeSubPlan(AlternativeSubPlanState *node,
return ExecSubPlan(activesp, econtext, isNull);
}
+
+/*
+ * Estimate the amount of space required to serialize the InitPlan params.
+ */
+Size
+EstimateInitPlanParamsSpace(ParamExecData *paramExecVals, Bitmapset *params)
+{
+ int paramid;
+ Size sz = sizeof(int);
+ ParamExecData *prm;
+
+ if (params == NULL)
+ return sz;
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ Oid typeOid;
+ int16 typLen;
+ bool typByVal;
+
+ prm = &(paramExecVals[paramid]);
+ typeOid = prm->ptype;
+
+ sz = add_size(sz, sizeof(int)); /* space for paramid */
+ sz = add_size(sz, sizeof(Oid)); /* space for type OID */
+
+ /* space for datum/isnull */
+ if (OidIsValid(typeOid))
+ get_typlenbyval(typeOid, &typLen, &typByVal);
+ else
+ {
+ /* If no type OID, assume by-value, like copyParamList does. */
+ typLen = sizeof(Datum);
+ typByVal = true;
+ }
+ sz = add_size(sz,
+ datumEstimateSpace(prm->value, prm->isnull, typByVal, typLen));
+ }
+ return sz;
+}
+
+/*
+ * Serialize ParamExecData params corresponding to initplans.
+ *
+ * We write the number of parameters first, as a 4-byte integer, and then
+ * write details for each parameter in turn. The details for each parameter
+ * consist of a 4-byte paramid (location of param in execution time internal
+ * parameter array), 4-byte type OID, and then the datum as serialized by
+ * datumSerialize().
+ *
+ * The above format is quite similar to the format used to serialize
+ * paramListInfo structure, so if we change either format, then consider to
+ * change at both the places.
+ */
+void
+SerializeInitPlanParams(ParamExecData *paramExecVals, Bitmapset *params,
+ char **start_address)
+{
+ int nparams;
+ int paramid;
+ ParamExecData *prm;
+
+ nparams = bms_num_members(params);
+ memcpy(*start_address, &nparams, sizeof(int));
+ *start_address += sizeof(int);
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ Oid typeOid;
+ int16 typLen;
+ bool typByVal;
+
+ prm = &(paramExecVals[paramid]);
+ typeOid = prm->ptype;
+
+ /* Write paramid. */
+ memcpy(*start_address, ¶mid, sizeof(int));
+ *start_address += sizeof(int);
+
+ /* Write OID. */
+ memcpy(*start_address, &typeOid, sizeof(Oid));
+ *start_address += sizeof(Oid);
+
+ /* space for datum/isnull */
+ if (OidIsValid(typeOid))
+ get_typlenbyval(typeOid, &typLen, &typByVal);
+ else
+ {
+ /* If no type OID, assume by-value, like copyParamList does. */
+ typLen = sizeof(Datum);
+ typByVal = true;
+ }
+ datumSerialize(prm->value, prm->isnull, typByVal, typLen,
+ start_address);
+ }
+}
+
+/*
+ * Restore ParamExecData params corresponding to initplans.
+ */
+void
+RestoreInitPlanParams(char **start_address, ParamExecData *params)
+{
+ int nparams;
+ int i;
+ int paramid;
+
+ memcpy(&nparams, *start_address, sizeof(int));
+ *start_address += sizeof(int);
+
+ for (i = 0; i < nparams; i++)
+ {
+ ParamExecData *prm;
+
+ /* Read paramid */
+ memcpy(¶mid, *start_address, sizeof(int));
+ *start_address += sizeof(int);
+ prm = ¶ms[paramid];
+
+ /* Read type OID. */
+ memcpy(&prm->ptype, *start_address, sizeof(Oid));
+ *start_address += sizeof(Oid);
+
+ /* Read datum/isnull. */
+ prm->value = datumRestore(start_address, &prm->isnull);
+ prm->execPlan = NULL;
+ }
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index b274af2..fac75f4 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -364,6 +364,7 @@ _copyGather(const Gather *from)
COPY_SCALAR_FIELD(rescan_param);
COPY_SCALAR_FIELD(single_copy);
COPY_SCALAR_FIELD(invisible);
+ COPY_BITMAPSET_FIELD(initParam);
return newnode;
}
@@ -391,6 +392,7 @@ _copyGatherMerge(const GatherMerge *from)
COPY_POINTER_FIELD(sortOperators, from->numCols * sizeof(Oid));
COPY_POINTER_FIELD(collations, from->numCols * sizeof(Oid));
COPY_POINTER_FIELD(nullsFirst, from->numCols * sizeof(bool));
+ COPY_BITMAPSET_FIELD(initParam);
return newnode;
}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 2532edc..556d533 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -482,6 +482,7 @@ _outGather(StringInfo str, const Gather *node)
WRITE_INT_FIELD(rescan_param);
WRITE_BOOL_FIELD(single_copy);
WRITE_BOOL_FIELD(invisible);
+ WRITE_BITMAPSET_FIELD(initParam);
}
static void
@@ -512,6 +513,8 @@ _outGatherMerge(StringInfo str, const GatherMerge *node)
appendStringInfoString(str, " :nullsFirst");
for (i = 0; i < node->numCols; i++)
appendStringInfo(str, " %s", booltostr(node->nullsFirst[i]));
+
+ WRITE_BITMAPSET_FIELD(initParam);
}
static void
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 07ba691..c3bcf47 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2165,6 +2165,7 @@ _readGather(void)
READ_INT_FIELD(rescan_param);
READ_BOOL_FIELD(single_copy);
READ_BOOL_FIELD(invisible);
+ READ_BITMAPSET_FIELD(initParam);
READ_DONE();
}
@@ -2186,6 +2187,7 @@ _readGatherMerge(void)
READ_OID_ARRAY(sortOperators, local_node->numCols);
READ_OID_ARRAY(collations, local_node->numCols);
READ_BOOL_ARRAY(nullsFirst, local_node->numCols);
+ READ_BITMAPSET_FIELD(initParam);
READ_DONE();
}
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index a7866a9..a131558 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -39,6 +39,7 @@
#include "optimizer/planner.h"
#include "optimizer/prep.h"
#include "optimizer/restrictinfo.h"
+#include "optimizer/subselect.h"
#include "optimizer/tlist.h"
#include "optimizer/var.h"
#include "parser/parse_clause.h"
@@ -2238,6 +2239,14 @@ generate_gather_paths(PlannerInfo *root, RelOptInfo *rel)
Path *simple_gather_path;
ListCell *lc;
+ /*
+ * We don't want to generate Gather or Gather Merge node if there are
+ * initplans at some query level below the current query level as those
+ * plans could be parallel-unsafe or undirect correlated plans.
+ */
+ if (contains_parallel_unsafe_param(root, rel))
+ return;
+
/* If there are no partial paths, there's nothing to do here. */
if (rel->partial_pathlist == NIL)
return;
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 2821662..0363d68 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -6257,6 +6257,7 @@ make_gather(List *qptlist,
node->rescan_param = rescan_param;
node->single_copy = single_copy;
node->invisible = false;
+ node->initParam = NULL;
return node;
}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 7f146d6..47a6b0d 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -366,6 +366,14 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
{
Gather *gather = makeNode(Gather);
+ /*
+ * If there are any initPlans attached to the formerly-top plan node,
+ * move them up to the Gather node; same as we do for Material node in
+ * materialize_finished_plan.
+ */
+ gather->plan.initPlan = top_plan->initPlan;
+ top_plan->initPlan = NIL;
+
gather->plan.targetlist = top_plan->targetlist;
gather->plan.qual = NIL;
gather->plan.lefttree = top_plan;
@@ -3580,6 +3588,12 @@ create_grouping_paths(PlannerInfo *root,
ListCell *lc;
+ /*
+ * Don't parallelize the plan if there is an initplan below current query
+ * level. See generate_gather_paths() for detailed reason.
+ */
+ (void) contains_parallel_unsafe_param(root, input_rel);
+
/* For now, do all work in the (GROUP_AGG, NULL) upperrel */
grouped_rel = fetch_upper_rel(root, UPPERREL_GROUP_AGG, NULL);
@@ -4942,6 +4956,12 @@ create_ordered_paths(PlannerInfo *root,
RelOptInfo *ordered_rel;
ListCell *lc;
+ /*
+ * Don't parallelize the plan if there is an initplan below current query
+ * level. See generate_gather_paths() for detailed reason.
+ */
+ (void) contains_parallel_unsafe_param(root, input_rel);
+
/* For now, do all work in the (ORDERED, NULL) upperrel */
ordered_rel = fetch_upper_rel(root, UPPERREL_ORDERED, NULL);
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index dee4414..97baa72 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -104,6 +104,7 @@ static Node *fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context);
static bool fix_scan_expr_walker(Node *node, fix_scan_expr_context *context);
static void set_join_references(PlannerInfo *root, Join *join, int rtoffset);
static void set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset);
+static void set_param_references(PlannerInfo *root, Plan *plan);
static Node *convert_combining_aggrefs(Node *node, void *context);
static void set_dummy_tlist_references(Plan *plan, int rtoffset);
static indexed_tlist *build_tlist_index(List *tlist);
@@ -628,7 +629,10 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
case T_Gather:
case T_GatherMerge:
- set_upper_references(root, plan, rtoffset);
+ {
+ set_upper_references(root, plan, rtoffset);
+ set_param_references(root, plan);
+ }
break;
case T_Hash:
@@ -1778,6 +1782,49 @@ set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset)
}
/*
+ * set_param_references
+ * Initialize the initParam list in Gather or Gather merge node such that
+ * it contains reference of all the params that needs to be evaluated
+ * before execution of the node. It contains the initplan params that are
+ * being passed to the plan nodes below it.
+ */
+static void
+set_param_references(PlannerInfo *root, Plan *plan)
+{
+ Assert(IsA(plan, Gather) || IsA(plan, GatherMerge));
+
+ if (plan->lefttree->extParam)
+ {
+ PlannerInfo *proot;
+ Bitmapset *initSetParam = NULL;
+ ListCell *l;
+
+ for (proot = root; proot != NULL; proot = proot->parent_root)
+ {
+ foreach(l, proot->init_plans)
+ {
+ SubPlan *initsubplan = (SubPlan *) lfirst(l);
+ ListCell *l2;
+
+ foreach(l2, initsubplan->setParam)
+ {
+ initSetParam = bms_add_member(initSetParam, lfirst_int(l2));
+ }
+ }
+ }
+
+ /*
+ * Remember the list of all external initplan params that are used by
+ * the children of Gather or Gather merge node.
+ */
+ if (IsA(plan, Gather))
+ ((Gather *) plan)->initParam = bms_intersect(plan->lefttree->extParam, initSetParam);
+ else
+ ((GatherMerge *) plan)->initParam = bms_intersect(plan->lefttree->extParam, initSetParam);
+ }
+}
+
+/*
* Recursively scan an expression tree and convert Aggrefs to the proper
* intermediate form for combining aggregates. This means (1) replacing each
* one's argument list with a single argument that is the original Aggref
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 1103984..2fb6219 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -84,6 +84,7 @@ static Bitmapset *finalize_plan(PlannerInfo *root,
Bitmapset *scan_params);
static bool finalize_primnode(Node *node, finalize_primnode_context *context);
static bool finalize_agg_primnode(Node *node, finalize_primnode_context *context);
+static bool is_initplan_below_current_query_level(PlannerInfo *root);
/*
@@ -2136,13 +2137,11 @@ SS_identify_outer_params(PlannerInfo *root)
}
/*
- * SS_charge_for_initplans - account for initplans in Path costs & parallelism
+ * SS_charge_for_initplans - account for initplans in Path costs
*
* If any initPlans have been created in the current query level, they will
* get attached to the Plan tree created from whichever Path we select from
- * the given rel. Increment all that rel's Paths' costs to account for them,
- * and make sure the paths get marked as parallel-unsafe, since we can't
- * currently transmit initPlans to parallel workers.
+ * the given rel. Increment all that rel's Paths' costs to account for them.
*
* This is separate from SS_attach_initplans because we might conditionally
* create more initPlans during create_plan(), depending on which Path we
@@ -2174,7 +2173,7 @@ SS_charge_for_initplans(PlannerInfo *root, RelOptInfo *final_rel)
}
/*
- * Now adjust the costs and parallel_safe flags.
+ * Now adjust the costs.
*/
foreach(lc, final_rel->pathlist)
{
@@ -2182,7 +2181,6 @@ SS_charge_for_initplans(PlannerInfo *root, RelOptInfo *final_rel)
path->startup_cost += initplan_cost;
path->total_cost += initplan_cost;
- path->parallel_safe = false;
}
/* We needn't do set_cheapest() here, caller will do it */
@@ -3000,3 +2998,61 @@ SS_make_initplan_from_plan(PlannerInfo *root,
/* Set costs of SubPlan using info from the plan tree */
cost_subplan(subroot, node, plan);
}
+
+/*
+ * is_initplan_below_current_query_level - is there any initplan present below
+ * current query level.
+ */
+static bool
+is_initplan_below_current_query_level(PlannerInfo *root)
+{
+ ListCell *lc;
+
+ /*
+ * If the subplan corresponding to the subroot is an initPlan, it'll be
+ * attached to its parent root. Hence, we check the query level of its
+ * parent root and if any init_plans are attached there.
+ */
+ foreach(lc, root->glob->subroots)
+ {
+ PlannerInfo *subroot = (PlannerInfo *) lfirst(lc);
+ PlannerInfo *proot = subroot->parent_root;
+
+ if (proot->query_level > root->query_level && proot->init_plans)
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * contains_parallel_unsafe_param - Check if there is any initplan present below
+ * current query level, mark all the partial and non-partial paths for a relation
+ * at this level as parallel-unsafe.
+ */
+bool
+contains_parallel_unsafe_param(PlannerInfo *root, RelOptInfo *rel)
+{
+ ListCell *lc;
+
+ if (is_initplan_below_current_query_level(root))
+ {
+ foreach(lc, rel->partial_pathlist)
+ {
+ Path *subpath = (Path *) lfirst(lc);
+
+ subpath->parallel_safe = false;
+ }
+ foreach(lc, rel->pathlist)
+ {
+ Path *subpath = (Path *) lfirst(lc);
+
+ subpath->parallel_safe = false;
+ }
+ rel->consider_parallel = false;
+
+ return true;
+ }
+
+ return false;
+}
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 7961362..3272b4e 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -91,6 +91,7 @@ typedef struct
typedef struct
{
+ PlannerInfo *root;
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 */
@@ -1069,6 +1070,7 @@ max_parallel_hazard(Query *parse)
{
max_parallel_hazard_context context;
+ context.root = NULL;
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_UNSAFE;
context.safe_param_ids = NIL;
@@ -1098,6 +1100,7 @@ is_parallel_safe(PlannerInfo *root, Node *node)
root->glob->nParamExec == 0)
return true;
/* Else use max_parallel_hazard's search logic, but stop on RESTRICTED */
+ context.root = root;
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_RESTRICTED;
context.safe_param_ids = NIL;
@@ -1222,21 +1225,47 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
}
/*
- * We can't pass Params to workers at the moment either, so they are also
- * parallel-restricted, unless they are PARAM_EXEC Params listed in
+ * As of now, we can only pass Params that refer to the same or parent
+ * query level (see generate_gather_paths) or they are listed in
* safe_param_ids, meaning they could be generated within the worker.
*/
else if (IsA(node, Param))
{
+ int paramid;
+ PlannerInfo *root;
Param *param = (Param *) node;
- if (param->paramkind != PARAM_EXEC ||
- !list_member_int(context->safe_param_ids, param->paramid))
+ if (list_member_int(context->safe_param_ids, param->paramid))
+ return false;
+
+ root = context->root;
+ paramid = ((Param *) node)->paramid;
+
+ if (root)
{
- if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
- return true;
+ PlannerInfo *proot;
+ ListCell *l;
+
+ for (proot = root; proot != NULL; proot = proot->parent_root)
+ {
+ foreach(l, proot->init_plans)
+ {
+ SubPlan *initsubplan = (SubPlan *) lfirst(l);
+ ListCell *l2;
+
+ foreach(l2, initsubplan->setParam)
+ {
+ int initparam = lfirst_int(l2);
+
+ if (paramid == initparam)
+ return false;
+ }
+ }
+ }
}
- return false; /* nothing to recurse to */
+
+ if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
+ return true;
}
/*
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 78d2247..5bbb63a 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -609,6 +609,7 @@ extern ExprEvalOp ExecEvalStepOp(ExprState *state, ExprEvalStep *op);
*/
extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
+extern void ExecEvalParamExecParams(Bitmapset *params, EState *estate);
extern void ExecEvalParamExtern(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
extern void ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op);
diff --git a/src/include/executor/execParallel.h b/src/include/executor/execParallel.h
index e1b3e7a..88e2757 100644
--- a/src/include/executor/execParallel.h
+++ b/src/include/executor/execParallel.h
@@ -35,7 +35,8 @@ typedef struct ParallelExecutorInfo
} ParallelExecutorInfo;
extern ParallelExecutorInfo *ExecInitParallelPlan(PlanState *planstate,
- EState *estate, int nworkers, int64 tuples_needed);
+ EState *estate, Bitmapset *initParam, int nworkers,
+ int64 tuples_needed);
extern void ExecParallelCreateReaders(ParallelExecutorInfo *pei);
extern void ExecParallelFinish(ParallelExecutorInfo *pei);
extern void ExecParallelCleanup(ParallelExecutorInfo *pei);
diff --git a/src/include/executor/nodeSubplan.h b/src/include/executor/nodeSubplan.h
index 5dbaeeb..40b1a34 100644
--- a/src/include/executor/nodeSubplan.h
+++ b/src/include/executor/nodeSubplan.h
@@ -28,4 +28,10 @@ extern void ExecReScanSetParamPlan(SubPlanState *node, PlanState *parent);
extern void ExecSetParamPlan(SubPlanState *node, ExprContext *econtext);
+extern Size EstimateInitPlanParamsSpace(ParamExecData *paramExecVals, Bitmapset *params);
+
+extern void SerializeInitPlanParams(ParamExecData *paramExecVals, Bitmapset *params, char **start_address);
+
+extern void RestoreInitPlanParams(char **start_address, ParamExecData *params);
+
#endif /* NODESUBPLAN_H */
diff --git a/src/include/nodes/params.h b/src/include/nodes/params.h
index 55219da..e96a1ea 100644
--- a/src/include/nodes/params.h
+++ b/src/include/nodes/params.h
@@ -98,6 +98,16 @@ typedef struct ParamExecData
{
void *execPlan; /* should be "SubPlanState *" */
Datum value;
+
+ /*
+ * parameter's datatype, or 0. This is required so that datum value can
+ * be read and used for other purposes like passing it to worker backend
+ * via shared memory. This is required only for initPlan's evaluation,
+ * however for consistency we set this for Subplan as well. We left it
+ * for other cases like CTE or RecursiveUnion cases where this structure
+ * is not used for evaluation of subplans.
+ */
+ Oid ptype;
bool isnull;
} ParamExecData;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index a382331..a16909c 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -841,6 +841,8 @@ typedef struct Gather
int rescan_param; /* ID of Param that signals a rescan, or -1 */
bool single_copy; /* don't execute plan more than once */
bool invisible; /* suppress EXPLAIN display (for testing)? */
+ Bitmapset *initParam; /* param id's of initplans which are referred
+ * at gather or one of it's child node */
} Gather;
/* ------------
@@ -858,6 +860,8 @@ typedef struct GatherMerge
Oid *sortOperators; /* OIDs of operators to sort them by */
Oid *collations; /* OIDs of collations */
bool *nullsFirst; /* NULLS FIRST/LAST directions */
+ Bitmapset *initParam; /* param id's of initplans which are referred
+ * at gather merge or one of it's child node */
} GatherMerge;
/* ----------------
diff --git a/src/include/optimizer/subselect.h b/src/include/optimizer/subselect.h
index ecd2011..a35df55 100644
--- a/src/include/optimizer/subselect.h
+++ b/src/include/optimizer/subselect.h
@@ -35,6 +35,8 @@ extern Param *SS_make_initplan_output_param(PlannerInfo *root,
extern void SS_make_initplan_from_plan(PlannerInfo *root,
PlannerInfo *subroot, Plan *plan,
Param *prm);
+extern bool initplan_is_below_current_query_level(PlannerInfo *root);
+extern bool contains_parallel_unsafe_param(PlannerInfo *root, RelOptInfo *rel);
extern Param *assign_nestloop_param_var(PlannerInfo *root, Var *var);
extern Param *assign_nestloop_param_placeholdervar(PlannerInfo *root,
PlaceHolderVar *phv);
diff --git a/src/test/regress/expected/select_parallel.out b/src/test/regress/expected/select_parallel.out
index 2ae600f..22af824 100644
--- a/src/test/regress/expected/select_parallel.out
+++ b/src/test/regress/expected/select_parallel.out
@@ -139,6 +139,39 @@ explain (costs off)
(4 rows)
alter table tenk2 reset (parallel_workers);
+--test parallel plan for a query containing initplan.
+set enable_indexscan = off;
+set enable_indexonlyscan = off;
+set enable_bitmapscan = off;
+alter table tenk2 set (parallel_workers = 2);
+explain (costs off)
+ select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+ QUERY PLAN
+------------------------------------------------------
+ Aggregate
+ InitPlan 1 (returns $2)
+ -> Finalize Aggregate
+ -> Gather
+ Workers Planned: 2
+ -> Partial Aggregate
+ -> Parallel Seq Scan on tenk2
+ -> Gather
+ Workers Planned: 4
+ Params Evaluated: $2
+ -> Parallel Seq Scan on tenk1
+ Filter: (unique1 = $2)
+(12 rows)
+
+select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+ count
+-------
+ 1
+(1 row)
+
+reset enable_indexscan;
+reset enable_indexonlyscan;
+reset enable_bitmapscan;
+alter table tenk2 reset (parallel_workers);
-- test parallel index scans.
set enable_seqscan to off;
set enable_bitmapscan to off;
diff --git a/src/test/regress/sql/select_parallel.sql b/src/test/regress/sql/select_parallel.sql
index 89fe80a..ae144ca 100644
--- a/src/test/regress/sql/select_parallel.sql
+++ b/src/test/regress/sql/select_parallel.sql
@@ -52,6 +52,21 @@ explain (costs off)
(select ten from tenk2);
alter table tenk2 reset (parallel_workers);
+--test parallel plan for a query containing initplan.
+set enable_indexscan = off;
+set enable_indexonlyscan = off;
+set enable_bitmapscan = off;
+alter table tenk2 set (parallel_workers = 2);
+
+explain (costs off)
+ select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+
+reset enable_indexscan;
+reset enable_indexonlyscan;
+reset enable_bitmapscan;
+alter table tenk2 reset (parallel_workers);
+
-- test parallel index scans.
set enable_seqscan to off;
set enable_bitmapscan to off;
On Tue, Oct 3, 2017 at 7:33 AM, Amit Kapila <amit.kapila16@gmail.com> wrote:
- Why do we even need contains_parallel_unsafe_param() and
is_initplan_is_below_current_query_level() in the first place, anyway?
I think there's been some discussion of that on this thread, but I'm
not sure I completely understand it, and the comments in the patch
don't help me understand why we now need this restriction.This is to ensure that initplans are never below Gather node. If we
allow parallelism when initplan is below current query level, (a) then
we need a way to pass the result of initplan from worker to other
workers and to master backend (b) if initplan contains reference to
some parameter above the current query level (undirect correlated
plans), then we need a mechanism to pass such parameters (basically
allow correlated initplans work).
Man, this stuff makes my hurt. Leaving aside the details of what this
particular patch tries to handle, and with due regard for the
possibility that I don't know what I'm talking about, I feel like
there are four cases: (1) the InitPlan refers to one or more
parameters whose value is computed below the Gather node, (2) the
InitPlan refers to one or more parameters whose value is computed
above the Gather node, (3) the InitPlan refers to both parameters
computed above the Gather and parameters computed below the Gather,
and (4) the InitPlan refers to no parameters at all.
In cases (1) and (3), we cannot compute the value at the Gather node
because some of the necessary information doesn't exist yet. it
wouldn't make any sense to try to compute it at that point because
each the value on which the InitPlan output depends can be different
in each worker and can change at different times in different workers.
However, in case (1), it's definitely OK to compute the value in the
worker itself, and in case (3), it's OK provided that the necessary
parameter values from above the Gather are passed down to the workers.
In cases (2) and (4), we can compute the value at the Gather node. If
we tried to compute it within individual workers, then we'd run into a
few problems: (a) the plan might not be parallel-safe, (b) it would be
inefficient to recompute the same value in every worker, and (c) if
the expression is volatile, the workers might not all arrive at the
same answer. Now, if the plan is parallel-safe, then we could also
choose NOT to compute the value at the Gather node and instead let the
first participant that needs the value compute it; while it's being
computed, it could fence off other participants from embarking on the
same computation, and once the computation is done, all participants
get the same computed value. But AFAICS all of that is strictly
optional: in any case where the InitPlan doesn't depend a value
produced below the Gather, it is legal (implementation difficulties
aside) to compute it at the Gather node.
Having said all that, I think that this patch only wants to handle the
subset of cases (2) and (4) where the relevant InitPlan is attached
ABOVE the Gather node -- which seems very reasonable, because
evaluating an InitPlan at a level of the plan tree above the level at
which it is defined sounds like it might be complex. But I still
don't quite see why we need these tests. I mean, if we only allow
Param references from a set of safe parameter IDs, and the safe
parameter IDs include only those IDs that can be generated in a
worker, then won't other InitPlans in the workers anyway be ruled out?
I assume there won't be an InitPlan whose output Param is never
referenced anywhere -- and if there were, it wouldn't really matter
whether a worker thought it could compute it, because there'd be
nothing to trigger that computation to actually happen.
If I am all mixed up, please help straighten me out.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Oct 4, 2017 at 3:40 AM, Robert Haas <robertmhaas@gmail.com> wrote:
On Tue, Oct 3, 2017 at 7:33 AM, Amit Kapila <amit.kapila16@gmail.com> wrote:
Having said all that, I think that this patch only wants to handle the
subset of cases (2) and (4) where the relevant InitPlan is attached
ABOVE the Gather node -- which seems very reasonable, because
evaluating an InitPlan at a level of the plan tree above the level at
which it is defined sounds like it might be complex. But I still
don't quite see why we need these tests. I mean, if we only allow
Param references from a set of safe parameter IDs, and the safe
parameter IDs include only those IDs that can be generated in a
worker, then won't other InitPlans in the workers anyway be ruled out?
It doesn't happen always. There are cases when the part of required
conditions are pushed one query level below, so when we check during
max_parallel_hazard_walker, they look safe, but actually, they are
not. I will try to explain by example:
postgres=# explain (costs off, verbose) select * from t1 where t1.i in
( select 1 + (select max(j) from t3));
QUERY PLAN
----------------------------------------------------------------------
Hash Semi Join
Output: t1.i, t1.j, t1.k
Hash Cond: (t1.i = ((1 + $1)))
-> Seq Scan on public.t1
Output: t1.i, t1.j, t1.k
-> Hash
Output: ((1 + $1))
-> Result
Output: (1 + $1)
InitPlan 1 (returns $1)
-> Finalize Aggregate
Output: max(t3.j)
-> Gather
Output: (PARTIAL max(t3.j))
Workers Planned: 2
-> Partial Aggregate
Output: PARTIAL max(t3.j)
-> Parallel Seq Scan on public.t3
Output: t3.j
(19 rows)
In the above example, you can see that the condition referring to
initplan (1 + $1) is pushed one level below. So when it tries to
check parallel safety for the join condition, it won't see Param node.
Now, consider if we don't check contains_parallel_unsafe_param during
generate_gather_paths, then it will lead to below plan.
postgres=# explain (costs off, verbose) select * from t1 where t1.i in
( select 1 + (select max(j) from t3));
QUERY PLAN
----------------------------------------------------------------------------
Gather
Output: t1.i, t1.j, t1.k
Workers Planned: 2
-> Hash Semi Join
Output: t1.i, t1.j, t1.k
Hash Cond: (t1.i = ((1 + $1)))
-> Parallel Seq Scan on public.t1
Output: t1.i, t1.j, t1.k
-> Hash
Output: ((1 + $1))
-> Result
Output: (1 + $1)
InitPlan 1 (returns $1)
-> Finalize Aggregate
Output: max(t3.j)
-> Gather
Output: (PARTIAL max(t3.j))
Workers Planned: 2
-> Partial Aggregate
Output: PARTIAL max(t3.j)
-> Parallel Seq Scan on public.t3
Output: t3.j
(22 rows)
This is wrong because when we will try to evaluate params that are
required at gather node, we won't get the required param as there is
no initplan at that level.
If I am all mixed up, please help straighten me out.
I think whatever you said is right and very clear.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Oct 4, 2017 at 12:55 PM, Amit Kapila <amit.kapila16@gmail.com> wrote:
On Wed, Oct 4, 2017 at 3:40 AM, Robert Haas <robertmhaas@gmail.com> wrote:
On Tue, Oct 3, 2017 at 7:33 AM, Amit Kapila <amit.kapila16@gmail.com> wrote:
Having said all that, I think that this patch only wants to handle the
subset of cases (2) and (4) where the relevant InitPlan is attached
ABOVE the Gather node -- which seems very reasonable, because
evaluating an InitPlan at a level of the plan tree above the level at
which it is defined sounds like it might be complex. But I still
don't quite see why we need these tests. I mean, if we only allow
Param references from a set of safe parameter IDs, and the safe
parameter IDs include only those IDs that can be generated in a
worker, then won't other InitPlans in the workers anyway be ruled out?It doesn't happen always. There are cases when the part of required
conditions are pushed one query level below, so when we check during
max_parallel_hazard_walker, they look safe, but actually, they are
not. I will try to explain by example:postgres=# explain (costs off, verbose) select * from t1 where t1.i in
( select 1 + (select max(j) from t3));
If you want to reproduce this case, then you can use the script posted
by Kuntal up thread [1]/messages/by-id/CAGz5QC+uHOq78GCika3fbgRyN5zgiDR9Dd1Th5kENF+UpnPomQ@mail.gmail.com.
[1]: /messages/by-id/CAGz5QC+uHOq78GCika3fbgRyN5zgiDR9Dd1Th5kENF+UpnPomQ@mail.gmail.com
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Oct 4, 2017 at 12:55 PM, Amit Kapila <amit.kapila16@gmail.com> wrote:
On Wed, Oct 4, 2017 at 3:40 AM, Robert Haas <robertmhaas@gmail.com> wrote:
On Tue, Oct 3, 2017 at 7:33 AM, Amit Kapila <amit.kapila16@gmail.com> wrote:
Having said all that, I think that this patch only wants to handle the
subset of cases (2) and (4) where the relevant InitPlan is attached
ABOVE the Gather node -- which seems very reasonable, because
evaluating an InitPlan at a level of the plan tree above the level at
which it is defined sounds like it might be complex. But I still
don't quite see why we need these tests. I mean, if we only allow
Param references from a set of safe parameter IDs, and the safe
parameter IDs include only those IDs that can be generated in a
worker, then won't other InitPlans in the workers anyway be ruled out?It doesn't happen always. There are cases when the part of required
conditions are pushed one query level below, so when we check during
max_parallel_hazard_walker, they look safe, but actually, they are
not. I will try to explain by example:postgres=# explain (costs off, verbose) select * from t1 where t1.i in
( select 1 + (select max(j) from t3));
QUERY PLAN
----------------------------------------------------------------------
Hash Semi Join
Output: t1.i, t1.j, t1.k
Hash Cond: (t1.i = ((1 + $1)))
-> Seq Scan on public.t1
Output: t1.i, t1.j, t1.k
-> Hash
Output: ((1 + $1))
-> Result
Output: (1 + $1)
InitPlan 1 (returns $1)
-> Finalize Aggregate
Output: max(t3.j)
-> Gather
Output: (PARTIAL max(t3.j))
Workers Planned: 2
-> Partial Aggregate
Output: PARTIAL max(t3.j)
-> Parallel Seq Scan on public.t3
Output: t3.j
(19 rows)In the above example, you can see that the condition referring to
initplan (1 + $1) is pushed one level below. So when it tries to
check parallel safety for the join condition, it won't see Param node.
To add more detail, the param node in join qual is replaced with Var
during pullup of sublink. Basically, it constructs vars from target
list of subquery and then replaces it in join quals. Refer code:
convert_ANY_sublink_to_join()
{
..
/*
* Build a list of Vars representing the subselect outputs.
*/
subquery_vars = generate_subquery_vars(root,
subselect->targetList,
rtindex);
/*
* Build the new join's qual expression, replacing Params with these Vars.
*/
quals = convert_testexpr(root, sublink->testexpr, subquery_vars);
..
}
Now, unless, I am missing something here, it won't be possible to
detect params in such cases during forming of join rels and hence we
need the tests in generate_gather_paths. Let me know if I am missing
something in this context or if you have any better ideas to make it
work?
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Oct 5, 2017 at 5:52 AM, Amit Kapila <amit.kapila16@gmail.com> wrote:
Now, unless, I am missing something here, it won't be possible to
detect params in such cases during forming of join rels and hence we
need the tests in generate_gather_paths. Let me know if I am missing
something in this context or if you have any better ideas to make it
work?
Hmm, in a case like this, I think we shouldn't need to detect it. The
Var is perfectly parallel-safe, the problem is that we can't use a
not-safe plan for the inner rel. I wonder why that's happening
here... is it a problem with your patch or an existing bug?
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Oct 5, 2017 at 6:08 PM, Robert Haas <robertmhaas@gmail.com> wrote:
On Thu, Oct 5, 2017 at 5:52 AM, Amit Kapila <amit.kapila16@gmail.com> wrote:
Now, unless, I am missing something here, it won't be possible to
detect params in such cases during forming of join rels and hence we
need the tests in generate_gather_paths. Let me know if I am missing
something in this context or if you have any better ideas to make it
work?Hmm, in a case like this, I think we shouldn't need to detect it. The
Var is perfectly parallel-safe, the problem is that we can't use a
not-safe plan for the inner rel. I wonder why that's happening
here...
It is because the inner rel (Result Path) contains a reference to a
param which appears to be at the same query level. Basically due to
changes in max_parallel_hazard_walker().
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Oct 5, 2017 at 1:16 PM, Amit Kapila <amit.kapila16@gmail.com> wrote:
On Thu, Oct 5, 2017 at 6:08 PM, Robert Haas <robertmhaas@gmail.com> wrote:
On Thu, Oct 5, 2017 at 5:52 AM, Amit Kapila <amit.kapila16@gmail.com> wrote:
Now, unless, I am missing something here, it won't be possible to
detect params in such cases during forming of join rels and hence we
need the tests in generate_gather_paths. Let me know if I am missing
something in this context or if you have any better ideas to make it
work?Hmm, in a case like this, I think we shouldn't need to detect it. The
Var is perfectly parallel-safe, the problem is that we can't use a
not-safe plan for the inner rel. I wonder why that's happening
here...It is because the inner rel (Result Path) contains a reference to a
param which appears to be at the same query level. Basically due to
changes in max_parallel_hazard_walker().
I spent several hours debugging this issue this afternoon. I think
you've misdiagnosed the problem. I think that the Param reference in
the result path is parallel-safe; that doesn't seem to me to be wrong.
If we see a Param reference for our own query level, then either we're
below the Gather and the new logic added by this patch will pass down
the value or we're above the Gather and we can access the value
directly. Either way, no problem.
However, I think that if you attach an InitPlan to a parallel-safe
plan, it becomes parallel-restricted. This is obvious in the case
where the InitPlan's plan isn't itself parallel-safe, but it's also
arguably true even when the InitPlan's plan *is* parallel-safe,
because pushing that below a Gather introduces a multiple-evaluation
hazard. I think we should fix that problem someday by providing a
DSA-based parameter store, but that's a job for another day.
And it turns out that we actually have such logic already, but this
patch removes it:
@@ -2182,7 +2181,6 @@ SS_charge_for_initplans(PlannerInfo *root,
RelOptInfo *final_rel)
path->startup_cost += initplan_cost;
path->total_cost += initplan_cost;
- path->parallel_safe = false;
}
/* We needn't do set_cheapest() here, caller will do it */
Now, the header comment for SS_charge_for_initplans() is wrong; it
claims we can't transmit initPlans to workers, but I think that's
already wrong even before this patch. On the other hand, I think that
the actual code is right even after this patch. If I put that line
back but make contains_parallel_unsafe_param always return false, then
I can still get plans like this (I modified EXPLAIN to show Parallel
Safe markings)...
rhaas=# explain select * from pgbench_accounts where bid = (select
max(g) from generate_series(1,1000)g);
QUERY PLAN
-----------------------------------------------------------------------------------------
Gather (cost=12.51..648066.51 rows=100000 width=97)
Parallel Safe: false
Workers Planned: 2
Params Evaluated: $0
InitPlan 1 (returns $0)
-> Aggregate (cost=12.50..12.51 rows=1 width=4)
Parallel Safe: true
-> Function Scan on generate_series g (cost=0.00..10.00
rows=1000 width=4)
Parallel Safe: true
-> Parallel Seq Scan on pgbench_accounts (cost=0.00..648054.00
rows=41667 width=97)
Parallel Safe: true
Filter: (bid = $0)
(12 rows)
...but Kuntal's example no longer misbehaves:
QUERY PLAN
----------------------------------------------------------------------
Hash Semi Join
Parallel Safe: false
Output: t1.i, t1.j, t1.k
Hash Cond: (t1.i = ((1 + $1)))
-> Gather
Parallel Safe: false
Output: t1.i, t1.j, t1.k
Workers Planned: 2
-> Parallel Seq Scan on public.t1
Parallel Safe: true
Output: t1.i, t1.j, t1.k
-> Hash
Parallel Safe: false
Output: ((1 + $1))
-> Result
Parallel Safe: false
Output: (1 + $1)
InitPlan 1 (returns $1)
-> Finalize Aggregate
Parallel Safe: false
Output: max(t3.j)
-> Gather
Parallel Safe: false
Output: (PARTIAL max(t3.j))
Workers Planned: 2
-> Partial Aggregate
Parallel Safe: true
Output: PARTIAL max(t3.j)
-> Parallel Seq Scan on public.t3
Parallel Safe: true
Output: t3.j
(31 rows)
With your original path, the Result was getting marked parallel-safe,
but now it doesn't, which is correct, and after that everything seems
to just work.
Notice that in my original example the topmost plan node doubly fails
to be parallel-safe: it fails because it's a Gather, and it fails
because it has an InitPlan attached. But that's all OK -- the
InitPlan doesn't make anything *under* the Gather unsafe, so we can
still use parallelism *at* the level where the InitPlan is attached,
just not *above* that level.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Oct 6, 2017 at 2:32 AM, Robert Haas <robertmhaas@gmail.com> wrote:
On Thu, Oct 5, 2017 at 1:16 PM, Amit Kapila <amit.kapila16@gmail.com> wrote:
On Thu, Oct 5, 2017 at 6:08 PM, Robert Haas <robertmhaas@gmail.com> wrote:
On Thu, Oct 5, 2017 at 5:52 AM, Amit Kapila <amit.kapila16@gmail.com> wrote:
Now, unless, I am missing something here, it won't be possible to
detect params in such cases during forming of join rels and hence we
need the tests in generate_gather_paths. Let me know if I am missing
something in this context or if you have any better ideas to make it
work?Hmm, in a case like this, I think we shouldn't need to detect it. The
Var is perfectly parallel-safe, the problem is that we can't use a
not-safe plan for the inner rel. I wonder why that's happening
here...It is because the inner rel (Result Path) contains a reference to a
param which appears to be at the same query level. Basically due to
changes in max_parallel_hazard_walker().I spent several hours debugging this issue this afternoon.
Thanks a lot for spending time.
I think
you've misdiagnosed the problem. I think that the Param reference in
the result path is parallel-safe; that doesn't seem to me to be wrong.
If we see a Param reference for our own query level, then either we're
below the Gather and the new logic added by this patch will pass down
the value or we're above the Gather and we can access the value
directly. Either way, no problem.However, I think that if you attach an InitPlan to a parallel-safe
plan, it becomes parallel-restricted. This is obvious in the case
where the InitPlan's plan isn't itself parallel-safe, but it's also
arguably true even when the InitPlan's plan *is* parallel-safe,
because pushing that below a Gather introduces a multiple-evaluation
hazard. I think we should fix that problem someday by providing a
DSA-based parameter store, but that's a job for another day.And it turns out that we actually have such logic already, but this
patch removes it:@@ -2182,7 +2181,6 @@ SS_charge_for_initplans(PlannerInfo *root,
RelOptInfo *final_rel)path->startup_cost += initplan_cost;
path->total_cost += initplan_cost;
- path->parallel_safe = false;
}/* We needn't do set_cheapest() here, caller will do it */
Yeah, it is a mistake from my side. I thought that we don't need it
now as we pass the computed value of initplan params for valid cases
and for other cases we can prohibit them as was done in the patch.
Now, the header comment for SS_charge_for_initplans() is wrong; it
claims we can't transmit initPlans to workers, but I think that's
already wrong even before this patch.
What exactly you want to convey as part of that comment?
I have fixed the other review comment related to using safe_param_list
in the attached patch. I think I have fixed all comments given by
you, but let me know if I have missed anything or you have any other
comment.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
pq_pushdown_initplan_v11.patchapplication/octet-stream; name=pq_pushdown_initplan_v11.patchDownload
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 8f7062c..561b85c 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -107,6 +107,7 @@ static void show_tidbitmap_info(BitmapHeapScanState *planstate,
static void show_instrumentation_count(const char *qlabel, int which,
PlanState *planstate, ExplainState *es);
static void show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es);
+static void show_eval_params(Bitmapset *bms_params, ExplainState *es);
static const char *explain_get_index_name(Oid indexId);
static void show_buffer_usage(ExplainState *es, const BufferUsage *usage);
static void ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir,
@@ -631,7 +632,21 @@ ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc)
*/
ps = queryDesc->planstate;
if (IsA(ps, GatherState) &&((Gather *) ps->plan)->invisible)
+ {
+ List *initPlanState = NULL;
+ PlanState *save_ps;
+
+ /*
+ * The Gather node will be omitted in this case, so we need to move
+ * initplans to the outer node to ensure that they remain attached to
+ * the top node (cf standard_planner).
+ */
+ save_ps = ps;
+ initPlanState = ps->initPlan;
ps = outerPlanState(ps);
+ ps->initPlan = initPlanState;
+ save_ps->initPlan = NIL;
+ }
ExplainNode(ps, NIL, NULL, NULL, es);
}
@@ -1441,6 +1456,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
planstate, es);
ExplainPropertyInteger("Workers Planned",
gather->num_workers, es);
+
+ /* Show params evaluated at gather node */
+ if (gather->initParam)
+ show_eval_params(gather->initParam, es);
+
if (es->analyze)
{
int nworkers;
@@ -1463,6 +1483,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
planstate, es);
ExplainPropertyInteger("Workers Planned",
gm->num_workers, es);
+
+ /* Show params evaluated at gather-merge node */
+ if (gm->initParam)
+ show_eval_params(gm->initParam, es);
+
if (es->analyze)
{
int nworkers;
@@ -2488,6 +2513,29 @@ show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es)
}
/*
+ * Show initplan params evaluated at Gather or Gather Merge node.
+ */
+static void
+show_eval_params(Bitmapset *bms_params, ExplainState *es)
+{
+ int paramid = -1;
+ List *params = NIL;
+
+ Assert(bms_params);
+
+ while ((paramid = bms_next_member(bms_params, paramid)) >= 0)
+ {
+ char param[32];
+
+ snprintf(param, sizeof(param), "$%d", paramid);
+ params = lappend(params, pstrdup(param));
+ }
+
+ if (params)
+ ExplainPropertyList("Params Evaluated", params, es);
+}
+
+/*
* Fetch the name of an index in an EXPLAIN
*
* We allow plugins to get control here so that plans involving hypothetical
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index c5e97ef..d170f305 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -1927,6 +1927,33 @@ ExecEvalParamExec(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
}
/*
+ * ExecEvalParamExecParams
+ *
+ * Execute the subplan stored in PARAM_EXEC initplans params, if not executed
+ * till now.
+ */
+void
+ExecEvalParamExecParams(Bitmapset *params, EState *estate)
+{
+ ParamExecData *prm;
+ int paramid;
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ prm = &(estate->es_param_exec_vals[paramid]);
+
+ if (prm->execPlan != NULL)
+ {
+ /* Parameter not evaluated yet, so go do it */
+ ExecSetParamPlan(prm->execPlan, GetPerTupleExprContext(estate));
+ /* ExecSetParamPlan should have processed this param... */
+ Assert(prm->execPlan == NULL);
+ }
+ }
+}
+
+/*
* Evaluate a PARAM_EXTERN parameter.
*
* PARAM_EXTERN parameters must be sought in ecxt_param_list_info.
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index 5dc26ed..57a8fe5 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -23,6 +23,7 @@
#include "postgres.h"
+#include "executor/execExpr.h"
#include "executor/execParallel.h"
#include "executor/executor.h"
#include "executor/nodeBitmapHeapscan.h"
@@ -31,6 +32,7 @@
#include "executor/nodeIndexscan.h"
#include "executor/nodeIndexonlyscan.h"
#include "executor/nodeSeqscan.h"
+#include "executor/nodeSubplan.h"
#include "executor/nodeSort.h"
#include "executor/tqueue.h"
#include "nodes/nodeFuncs.h"
@@ -56,6 +58,7 @@
#define PARALLEL_KEY_INSTRUMENTATION UINT64CONST(0xE000000000000006)
#define PARALLEL_KEY_DSA UINT64CONST(0xE000000000000007)
#define PARALLEL_KEY_QUERY_TEXT UINT64CONST(0xE000000000000008)
+#define PARALLEL_KEY_INITPLAN_PARAMS UINT64CONST(0xE000000000000009)
#define PARALLEL_TUPLE_QUEUE_SIZE 65536
@@ -395,7 +398,8 @@ ExecParallelSetupTupleQueues(ParallelContext *pcxt, bool reinitialize)
* execution and return results to the main backend.
*/
ParallelExecutorInfo *
-ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
+ExecInitParallelPlan(PlanState *planstate, EState *estate,
+ Bitmapset *initParam, int nworkers,
int64 tuples_needed)
{
ParallelExecutorInfo *pei;
@@ -406,10 +410,12 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
char *pstmt_data;
char *pstmt_space;
char *param_space;
+ char *initplan_param_space;
BufferUsage *bufusage_space;
SharedExecutorInstrumentation *instrumentation = NULL;
int pstmt_len;
int param_len;
+ int initplan_param_len;
int instrumentation_len = 0;
int instrument_offset = 0;
Size dsa_minsize = dsa_minimum_size();
@@ -421,6 +427,8 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
pei->finished = false;
pei->planstate = planstate;
+ ExecEvalParamExecParams(initParam, estate);
+
/* Fix up and serialize plan to be sent to workers. */
pstmt_data = ExecSerializePlan(planstate->plan, estate);
@@ -454,6 +462,11 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
shm_toc_estimate_chunk(&pcxt->estimator, param_len);
shm_toc_estimate_keys(&pcxt->estimator, 1);
+ /* Estimate space for initplan params. */
+ initplan_param_len = EstimateInitPlanParamsSpace(estate->es_param_exec_vals, initParam);
+ shm_toc_estimate_chunk(&pcxt->estimator, initplan_param_len);
+ shm_toc_estimate_keys(&pcxt->estimator, 1);
+
/*
* Estimate space for BufferUsage.
*
@@ -528,6 +541,11 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
shm_toc_insert(pcxt->toc, PARALLEL_KEY_PARAMS, param_space);
SerializeParamList(estate->es_param_list_info, ¶m_space);
+ /* Store serialized initplan params. */
+ initplan_param_space = shm_toc_allocate(pcxt->toc, initplan_param_len);
+ shm_toc_insert(pcxt->toc, PARALLEL_KEY_INITPLAN_PARAMS, initplan_param_space);
+ SerializeInitPlanParams(estate->es_param_exec_vals, initParam, &initplan_param_space);
+
/* Allocate space for each worker's BufferUsage; no need to initialize. */
bufusage_space = shm_toc_allocate(pcxt->toc,
mul_size(sizeof(BufferUsage), pcxt->nworkers));
@@ -862,6 +880,21 @@ ExecParallelGetReceiver(dsm_segment *seg, shm_toc *toc)
}
/*
+ * Copy the ParamExecData params corresponding to initplans from dynamic
+ * shared memory. This has to be done once the params are allocated by
+ * executor; that is after ExecutorStart().
+ */
+static void
+ExecParallelInitializeInitPlanParams(shm_toc *toc, ParamExecData *params)
+{
+ char *paramspace;
+
+ /* Reconstruct initplan params. */
+ paramspace = shm_toc_lookup(toc, PARALLEL_KEY_INITPLAN_PARAMS, false);
+ RestoreInitPlanParams(¶mspace, params);
+}
+
+/*
* Create a QueryDesc for the PlannedStmt we are to execute, and return it.
*/
static QueryDesc *
@@ -1046,6 +1079,7 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
/* Special executor initialization steps for parallel workers */
queryDesc->planstate->state->es_query_dsa = area;
+ ExecParallelInitializeInitPlanParams(toc, queryDesc->estate->es_param_exec_vals);
ExecParallelInitializeWorker(queryDesc->planstate, toc);
/* Pass down any tuple bound */
diff --git a/src/backend/executor/nodeGather.c b/src/backend/executor/nodeGather.c
index 8370037..4104dc2 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -158,6 +158,7 @@ ExecGather(PlanState *pstate)
if (!node->pei)
node->pei = ExecInitParallelPlan(node->ps.lefttree,
estate,
+ gather->initParam,
gather->num_workers,
node->tuples_needed);
else
diff --git a/src/backend/executor/nodeGatherMerge.c b/src/backend/executor/nodeGatherMerge.c
index 70f33a9..f75430e 100644
--- a/src/backend/executor/nodeGatherMerge.c
+++ b/src/backend/executor/nodeGatherMerge.c
@@ -202,6 +202,7 @@ ExecGatherMerge(PlanState *pstate)
if (!node->pei)
node->pei = ExecInitParallelPlan(node->ps.lefttree,
estate,
+ gm->initParam,
gm->num_workers,
node->tuples_needed);
else
diff --git a/src/backend/executor/nodeNestloop.c b/src/backend/executor/nodeNestloop.c
index 4447b7c..52514e4 100644
--- a/src/backend/executor/nodeNestloop.c
+++ b/src/backend/executor/nodeNestloop.c
@@ -130,6 +130,7 @@ ExecNestLoop(PlanState *pstate)
{
NestLoopParam *nlp = (NestLoopParam *) lfirst(lc);
int paramno = nlp->paramno;
+ TupleDesc tdesc = outerTupleSlot->tts_tupleDescriptor;
ParamExecData *prm;
prm = &(econtext->ecxt_param_exec_vals[paramno]);
@@ -140,6 +141,7 @@ ExecNestLoop(PlanState *pstate)
prm->value = slot_getattr(outerTupleSlot,
nlp->paramval->varattno,
&(prm->isnull));
+ prm->ptype = TupleDescAttr(tdesc, nlp->paramval->varattno - 1)->atttypid;
/* Flag parameter value as changed */
innerPlan->chgParam = bms_add_member(innerPlan->chgParam,
paramno);
diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c
index 77ef6f3..06409ed 100644
--- a/src/backend/executor/nodeSubplan.c
+++ b/src/backend/executor/nodeSubplan.c
@@ -30,12 +30,16 @@
#include <math.h>
#include "access/htup_details.h"
+#include "catalog/pg_type.h"
#include "executor/executor.h"
#include "executor/nodeSubplan.h"
#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
#include "miscadmin.h"
#include "optimizer/clauses.h"
+#include "storage/shmem.h"
#include "utils/array.h"
+#include "utils/datum.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
@@ -273,11 +277,13 @@ ExecScanSubPlan(SubPlanState *node,
forboth(l, subplan->parParam, pvar, node->args)
{
int paramid = lfirst_int(l);
+ ExprState *exprstate = (ExprState *) lfirst(pvar);
ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]);
- prm->value = ExecEvalExprSwitchContext((ExprState *) lfirst(pvar),
+ prm->value = ExecEvalExprSwitchContext(exprstate,
econtext,
&(prm->isnull));
+ prm->ptype = exprType((Node *) exprstate->expr);
planstate->chgParam = bms_add_member(planstate->chgParam, paramid);
}
@@ -390,6 +396,7 @@ ExecScanSubPlan(SubPlanState *node,
prmdata = &(econtext->ecxt_param_exec_vals[paramid]);
Assert(prmdata->execPlan == NULL);
prmdata->value = slot_getattr(slot, col, &(prmdata->isnull));
+ prmdata->ptype = TupleDescAttr(tdesc, col - 1)->atttypid;
col++;
}
@@ -556,11 +563,13 @@ buildSubPlanHash(SubPlanState *node, ExprContext *econtext)
{
int paramid = lfirst_int(plst);
ParamExecData *prmdata;
+ TupleDesc tdesc = slot->tts_tupleDescriptor;
prmdata = &(innerecontext->ecxt_param_exec_vals[paramid]);
Assert(prmdata->execPlan == NULL);
prmdata->value = slot_getattr(slot, col,
&(prmdata->isnull));
+ prmdata->ptype = TupleDescAttr(tdesc, col - 1)->atttypid;
col++;
}
slot = ExecProject(node->projRight);
@@ -924,6 +933,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
ListCell *l;
bool found = false;
ArrayBuildStateAny *astate = NULL;
+ Oid ptype;
if (subLinkType == ANY_SUBLINK ||
subLinkType == ALL_SUBLINK)
@@ -931,6 +941,8 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
if (subLinkType == CTE_SUBLINK)
elog(ERROR, "CTE subplans should not be executed via ExecSetParamPlan");
+ ptype = exprType((Node *) node->subplan);
+
/* Initialize ArrayBuildStateAny in caller's context, if needed */
if (subLinkType == ARRAY_SUBLINK)
astate = initArrayResultAny(subplan->firstColType,
@@ -953,11 +965,13 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
forboth(l, subplan->parParam, pvar, node->args)
{
int paramid = lfirst_int(l);
+ ExprState *exprstate = (ExprState *) lfirst(pvar);
ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]);
- prm->value = ExecEvalExprSwitchContext((ExprState *) lfirst(pvar),
+ prm->value = ExecEvalExprSwitchContext(exprstate,
econtext,
&(prm->isnull));
+ prm->ptype = exprType((Node *) exprstate->expr);
planstate->chgParam = bms_add_member(planstate->chgParam, paramid);
}
@@ -980,6 +994,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = BoolGetDatum(true);
+ prm->ptype = ptype;
prm->isnull = false;
found = true;
break;
@@ -1031,6 +1046,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = heap_getattr(node->curTuple, i, tdesc,
&(prm->isnull));
+ prm->ptype = TupleDescAttr(tdesc, i - 1)->atttypid;
i++;
}
}
@@ -1053,6 +1069,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
true);
prm->execPlan = NULL;
prm->value = node->curArray;
+ prm->ptype = ptype;
prm->isnull = false;
}
else if (!found)
@@ -1065,6 +1082,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = BoolGetDatum(false);
+ prm->ptype = ptype;
prm->isnull = false;
}
else
@@ -1077,6 +1095,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = (Datum) 0;
+ prm->ptype = VOIDOID;
prm->isnull = true;
}
}
@@ -1207,3 +1226,133 @@ ExecAlternativeSubPlan(AlternativeSubPlanState *node,
return ExecSubPlan(activesp, econtext, isNull);
}
+
+/*
+ * Estimate the amount of space required to serialize the InitPlan params.
+ */
+Size
+EstimateInitPlanParamsSpace(ParamExecData *paramExecVals, Bitmapset *params)
+{
+ int paramid;
+ Size sz = sizeof(int);
+ ParamExecData *prm;
+
+ if (params == NULL)
+ return sz;
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ Oid typeOid;
+ int16 typLen;
+ bool typByVal;
+
+ prm = &(paramExecVals[paramid]);
+ typeOid = prm->ptype;
+
+ sz = add_size(sz, sizeof(int)); /* space for paramid */
+ sz = add_size(sz, sizeof(Oid)); /* space for type OID */
+
+ /* space for datum/isnull */
+ if (OidIsValid(typeOid))
+ get_typlenbyval(typeOid, &typLen, &typByVal);
+ else
+ {
+ /* If no type OID, assume by-value, like copyParamList does. */
+ typLen = sizeof(Datum);
+ typByVal = true;
+ }
+ sz = add_size(sz,
+ datumEstimateSpace(prm->value, prm->isnull, typByVal, typLen));
+ }
+ return sz;
+}
+
+/*
+ * Serialize ParamExecData params corresponding to initplans.
+ *
+ * We write the number of parameters first, as a 4-byte integer, and then
+ * write details for each parameter in turn. The details for each parameter
+ * consist of a 4-byte paramid (location of param in execution time internal
+ * parameter array), 4-byte type OID, and then the datum as serialized by
+ * datumSerialize().
+ *
+ * The above format is quite similar to the format used to serialize
+ * paramListInfo structure, so if we change either format, then consider to
+ * change at both the places.
+ */
+void
+SerializeInitPlanParams(ParamExecData *paramExecVals, Bitmapset *params,
+ char **start_address)
+{
+ int nparams;
+ int paramid;
+ ParamExecData *prm;
+
+ nparams = bms_num_members(params);
+ memcpy(*start_address, &nparams, sizeof(int));
+ *start_address += sizeof(int);
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ Oid typeOid;
+ int16 typLen;
+ bool typByVal;
+
+ prm = &(paramExecVals[paramid]);
+ typeOid = prm->ptype;
+
+ /* Write paramid. */
+ memcpy(*start_address, ¶mid, sizeof(int));
+ *start_address += sizeof(int);
+
+ /* Write OID. */
+ memcpy(*start_address, &typeOid, sizeof(Oid));
+ *start_address += sizeof(Oid);
+
+ /* space for datum/isnull */
+ if (OidIsValid(typeOid))
+ get_typlenbyval(typeOid, &typLen, &typByVal);
+ else
+ {
+ /* If no type OID, assume by-value, like copyParamList does. */
+ typLen = sizeof(Datum);
+ typByVal = true;
+ }
+ datumSerialize(prm->value, prm->isnull, typByVal, typLen,
+ start_address);
+ }
+}
+
+/*
+ * Restore ParamExecData params corresponding to initplans.
+ */
+void
+RestoreInitPlanParams(char **start_address, ParamExecData *params)
+{
+ int nparams;
+ int i;
+ int paramid;
+
+ memcpy(&nparams, *start_address, sizeof(int));
+ *start_address += sizeof(int);
+
+ for (i = 0; i < nparams; i++)
+ {
+ ParamExecData *prm;
+
+ /* Read paramid */
+ memcpy(¶mid, *start_address, sizeof(int));
+ *start_address += sizeof(int);
+ prm = ¶ms[paramid];
+
+ /* Read type OID. */
+ memcpy(&prm->ptype, *start_address, sizeof(Oid));
+ *start_address += sizeof(Oid);
+
+ /* Read datum/isnull. */
+ prm->value = datumRestore(start_address, &prm->isnull);
+ prm->execPlan = NULL;
+ }
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index c1a83ca..61fe226 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -364,6 +364,7 @@ _copyGather(const Gather *from)
COPY_SCALAR_FIELD(rescan_param);
COPY_SCALAR_FIELD(single_copy);
COPY_SCALAR_FIELD(invisible);
+ COPY_BITMAPSET_FIELD(initParam);
return newnode;
}
@@ -391,6 +392,7 @@ _copyGatherMerge(const GatherMerge *from)
COPY_POINTER_FIELD(sortOperators, from->numCols * sizeof(Oid));
COPY_POINTER_FIELD(collations, from->numCols * sizeof(Oid));
COPY_POINTER_FIELD(nullsFirst, from->numCols * sizeof(bool));
+ COPY_BITMAPSET_FIELD(initParam);
return newnode;
}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 2532edc..556d533 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -482,6 +482,7 @@ _outGather(StringInfo str, const Gather *node)
WRITE_INT_FIELD(rescan_param);
WRITE_BOOL_FIELD(single_copy);
WRITE_BOOL_FIELD(invisible);
+ WRITE_BITMAPSET_FIELD(initParam);
}
static void
@@ -512,6 +513,8 @@ _outGatherMerge(StringInfo str, const GatherMerge *node)
appendStringInfoString(str, " :nullsFirst");
for (i = 0; i < node->numCols; i++)
appendStringInfo(str, " %s", booltostr(node->nullsFirst[i]));
+
+ WRITE_BITMAPSET_FIELD(initParam);
}
static void
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 07ba691..c3bcf47 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2165,6 +2165,7 @@ _readGather(void)
READ_INT_FIELD(rescan_param);
READ_BOOL_FIELD(single_copy);
READ_BOOL_FIELD(invisible);
+ READ_BITMAPSET_FIELD(initParam);
READ_DONE();
}
@@ -2186,6 +2187,7 @@ _readGatherMerge(void)
READ_OID_ARRAY(sortOperators, local_node->numCols);
READ_OID_ARRAY(collations, local_node->numCols);
READ_BOOL_ARRAY(nullsFirst, local_node->numCols);
+ READ_BITMAPSET_FIELD(initParam);
READ_DONE();
}
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index a7866a9..a2053e0 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -39,6 +39,7 @@
#include "optimizer/planner.h"
#include "optimizer/prep.h"
#include "optimizer/restrictinfo.h"
+#include "optimizer/subselect.h"
#include "optimizer/tlist.h"
#include "optimizer/var.h"
#include "parser/parse_clause.h"
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 2821662..0363d68 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -6257,6 +6257,7 @@ make_gather(List *qptlist,
node->rescan_param = rescan_param;
node->single_copy = single_copy;
node->invisible = false;
+ node->initParam = NULL;
return node;
}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index e7ac11e..02aa044 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -376,6 +376,14 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
{
Gather *gather = makeNode(Gather);
+ /*
+ * If there are any initPlans attached to the formerly-top plan node,
+ * move them up to the Gather node; same as we do for Material node in
+ * materialize_finished_plan.
+ */
+ gather->plan.initPlan = top_plan->initPlan;
+ top_plan->initPlan = NIL;
+
gather->plan.targetlist = top_plan->targetlist;
gather->plan.qual = NIL;
gather->plan.lefttree = top_plan;
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index dee4414..97baa72 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -104,6 +104,7 @@ static Node *fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context);
static bool fix_scan_expr_walker(Node *node, fix_scan_expr_context *context);
static void set_join_references(PlannerInfo *root, Join *join, int rtoffset);
static void set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset);
+static void set_param_references(PlannerInfo *root, Plan *plan);
static Node *convert_combining_aggrefs(Node *node, void *context);
static void set_dummy_tlist_references(Plan *plan, int rtoffset);
static indexed_tlist *build_tlist_index(List *tlist);
@@ -628,7 +629,10 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
case T_Gather:
case T_GatherMerge:
- set_upper_references(root, plan, rtoffset);
+ {
+ set_upper_references(root, plan, rtoffset);
+ set_param_references(root, plan);
+ }
break;
case T_Hash:
@@ -1778,6 +1782,49 @@ set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset)
}
/*
+ * set_param_references
+ * Initialize the initParam list in Gather or Gather merge node such that
+ * it contains reference of all the params that needs to be evaluated
+ * before execution of the node. It contains the initplan params that are
+ * being passed to the plan nodes below it.
+ */
+static void
+set_param_references(PlannerInfo *root, Plan *plan)
+{
+ Assert(IsA(plan, Gather) || IsA(plan, GatherMerge));
+
+ if (plan->lefttree->extParam)
+ {
+ PlannerInfo *proot;
+ Bitmapset *initSetParam = NULL;
+ ListCell *l;
+
+ for (proot = root; proot != NULL; proot = proot->parent_root)
+ {
+ foreach(l, proot->init_plans)
+ {
+ SubPlan *initsubplan = (SubPlan *) lfirst(l);
+ ListCell *l2;
+
+ foreach(l2, initsubplan->setParam)
+ {
+ initSetParam = bms_add_member(initSetParam, lfirst_int(l2));
+ }
+ }
+ }
+
+ /*
+ * Remember the list of all external initplan params that are used by
+ * the children of Gather or Gather merge node.
+ */
+ if (IsA(plan, Gather))
+ ((Gather *) plan)->initParam = bms_intersect(plan->lefttree->extParam, initSetParam);
+ else
+ ((GatherMerge *) plan)->initParam = bms_intersect(plan->lefttree->extParam, initSetParam);
+ }
+}
+
+/*
* Recursively scan an expression tree and convert Aggrefs to the proper
* intermediate form for combining aggregates. This means (1) replacing each
* one's argument list with a single argument that is the original Aggref
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 7961362..e1b139a 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -1087,6 +1087,8 @@ bool
is_parallel_safe(PlannerInfo *root, Node *node)
{
max_parallel_hazard_context context;
+ PlannerInfo *proot;
+ ListCell *l;
/*
* Even if the original querytree contained nothing unsafe, we need to
@@ -1101,6 +1103,25 @@ is_parallel_safe(PlannerInfo *root, Node *node)
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_RESTRICTED;
context.safe_param_ids = NIL;
+
+ /*
+ * The params that refer to the same or parent query level are considered
+ * parallel-safe. The idea is that we compute such params at Gather or
+ * Gather Merge node and pass their value to workers.
+ */
+ for (proot = root; proot != NULL; proot = proot->parent_root)
+ {
+ foreach(l, proot->init_plans)
+ {
+ SubPlan *initsubplan = (SubPlan *) lfirst(l);
+ ListCell *l2;
+
+ foreach(l2, initsubplan->setParam)
+ context.safe_param_ids = lcons_int(lfirst_int(l2),
+ context.safe_param_ids);
+ }
+ }
+
return !max_parallel_hazard_walker(node, &context);
}
@@ -1224,19 +1245,17 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
/*
* We can't pass Params to workers at the moment either, so they are also
* parallel-restricted, unless they are PARAM_EXEC Params listed in
- * safe_param_ids, meaning they could be generated within the worker.
+ * safe_param_ids, meaning they could be either generated within the
+ * worker or can be computed in master and then their value can be passed
+ * to the worker.
*/
else if (IsA(node, Param))
{
- Param *param = (Param *) node;
+ if (list_member_int(context->safe_param_ids, ((Param *) node)->paramid))
+ return false;
- 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 */
+ if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
+ return true;
}
/*
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 78d2247..5bbb63a 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -609,6 +609,7 @@ extern ExprEvalOp ExecEvalStepOp(ExprState *state, ExprEvalStep *op);
*/
extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
+extern void ExecEvalParamExecParams(Bitmapset *params, EState *estate);
extern void ExecEvalParamExtern(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
extern void ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op);
diff --git a/src/include/executor/execParallel.h b/src/include/executor/execParallel.h
index e1b3e7a..88e2757 100644
--- a/src/include/executor/execParallel.h
+++ b/src/include/executor/execParallel.h
@@ -35,7 +35,8 @@ typedef struct ParallelExecutorInfo
} ParallelExecutorInfo;
extern ParallelExecutorInfo *ExecInitParallelPlan(PlanState *planstate,
- EState *estate, int nworkers, int64 tuples_needed);
+ EState *estate, Bitmapset *initParam, int nworkers,
+ int64 tuples_needed);
extern void ExecParallelCreateReaders(ParallelExecutorInfo *pei);
extern void ExecParallelFinish(ParallelExecutorInfo *pei);
extern void ExecParallelCleanup(ParallelExecutorInfo *pei);
diff --git a/src/include/executor/nodeSubplan.h b/src/include/executor/nodeSubplan.h
index 5dbaeeb..40b1a34 100644
--- a/src/include/executor/nodeSubplan.h
+++ b/src/include/executor/nodeSubplan.h
@@ -28,4 +28,10 @@ extern void ExecReScanSetParamPlan(SubPlanState *node, PlanState *parent);
extern void ExecSetParamPlan(SubPlanState *node, ExprContext *econtext);
+extern Size EstimateInitPlanParamsSpace(ParamExecData *paramExecVals, Bitmapset *params);
+
+extern void SerializeInitPlanParams(ParamExecData *paramExecVals, Bitmapset *params, char **start_address);
+
+extern void RestoreInitPlanParams(char **start_address, ParamExecData *params);
+
#endif /* NODESUBPLAN_H */
diff --git a/src/include/nodes/params.h b/src/include/nodes/params.h
index 55219da..e96a1ea 100644
--- a/src/include/nodes/params.h
+++ b/src/include/nodes/params.h
@@ -98,6 +98,16 @@ typedef struct ParamExecData
{
void *execPlan; /* should be "SubPlanState *" */
Datum value;
+
+ /*
+ * parameter's datatype, or 0. This is required so that datum value can
+ * be read and used for other purposes like passing it to worker backend
+ * via shared memory. This is required only for initPlan's evaluation,
+ * however for consistency we set this for Subplan as well. We left it
+ * for other cases like CTE or RecursiveUnion cases where this structure
+ * is not used for evaluation of subplans.
+ */
+ Oid ptype;
bool isnull;
} ParamExecData;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index a382331..a16909c 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -841,6 +841,8 @@ typedef struct Gather
int rescan_param; /* ID of Param that signals a rescan, or -1 */
bool single_copy; /* don't execute plan more than once */
bool invisible; /* suppress EXPLAIN display (for testing)? */
+ Bitmapset *initParam; /* param id's of initplans which are referred
+ * at gather or one of it's child node */
} Gather;
/* ------------
@@ -858,6 +860,8 @@ typedef struct GatherMerge
Oid *sortOperators; /* OIDs of operators to sort them by */
Oid *collations; /* OIDs of collations */
bool *nullsFirst; /* NULLS FIRST/LAST directions */
+ Bitmapset *initParam; /* param id's of initplans which are referred
+ * at gather merge or one of it's child node */
} GatherMerge;
/* ----------------
diff --git a/src/test/regress/expected/select_parallel.out b/src/test/regress/expected/select_parallel.out
index 2ae600f..22af824 100644
--- a/src/test/regress/expected/select_parallel.out
+++ b/src/test/regress/expected/select_parallel.out
@@ -139,6 +139,39 @@ explain (costs off)
(4 rows)
alter table tenk2 reset (parallel_workers);
+--test parallel plan for a query containing initplan.
+set enable_indexscan = off;
+set enable_indexonlyscan = off;
+set enable_bitmapscan = off;
+alter table tenk2 set (parallel_workers = 2);
+explain (costs off)
+ select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+ QUERY PLAN
+------------------------------------------------------
+ Aggregate
+ InitPlan 1 (returns $2)
+ -> Finalize Aggregate
+ -> Gather
+ Workers Planned: 2
+ -> Partial Aggregate
+ -> Parallel Seq Scan on tenk2
+ -> Gather
+ Workers Planned: 4
+ Params Evaluated: $2
+ -> Parallel Seq Scan on tenk1
+ Filter: (unique1 = $2)
+(12 rows)
+
+select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+ count
+-------
+ 1
+(1 row)
+
+reset enable_indexscan;
+reset enable_indexonlyscan;
+reset enable_bitmapscan;
+alter table tenk2 reset (parallel_workers);
-- test parallel index scans.
set enable_seqscan to off;
set enable_bitmapscan to off;
diff --git a/src/test/regress/sql/select_parallel.sql b/src/test/regress/sql/select_parallel.sql
index 89fe80a..ae144ca 100644
--- a/src/test/regress/sql/select_parallel.sql
+++ b/src/test/regress/sql/select_parallel.sql
@@ -52,6 +52,21 @@ explain (costs off)
(select ten from tenk2);
alter table tenk2 reset (parallel_workers);
+--test parallel plan for a query containing initplan.
+set enable_indexscan = off;
+set enable_indexonlyscan = off;
+set enable_bitmapscan = off;
+alter table tenk2 set (parallel_workers = 2);
+
+explain (costs off)
+ select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+
+reset enable_indexscan;
+reset enable_indexonlyscan;
+reset enable_bitmapscan;
+alter table tenk2 reset (parallel_workers);
+
-- test parallel index scans.
set enable_seqscan to off;
set enable_bitmapscan to off;
On Fri, Oct 6, 2017 at 7:08 AM, Amit Kapila <amit.kapila16@gmail.com> wrote:
I have fixed the other review comment related to using safe_param_list
in the attached patch. I think I have fixed all comments given by
you, but let me know if I have missed anything or you have any other
comment.
- Param *param = (Param *) node;
+ if (list_member_int(context->safe_param_ids, ((Param *)
node)->paramid))
+ return false;
- 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 */
+ if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
+ return true;
I think your revised logic is wrong here because it drops the
paramkind test, and I don't see any justification for that. IIUC,
param IDs are tracked separately for extern and exec parameters, so
why would we ignore that distinction here?
But I'm also wondering if we're missing a trick here, because isn't a
PARAM_EXTERN parameter always safe, given SerializeParamList? If so,
this || ought to be &&, but if that adjustment is needed, it seems
like a separate patch.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Sat, Oct 7, 2017 at 7:22 PM, Robert Haas <robertmhaas@gmail.com> wrote:
On Fri, Oct 6, 2017 at 7:08 AM, Amit Kapila <amit.kapila16@gmail.com> wrote:
I have fixed the other review comment related to using safe_param_list
in the attached patch. I think I have fixed all comments given by
you, but let me know if I have missed anything or you have any other
comment.- Param *param = (Param *) node; + if (list_member_int(context->safe_param_ids, ((Param *) node)->paramid)) + return false;- 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 */ + if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context)) + return true;I think your revised logic is wrong here because it drops the
paramkind test, and I don't see any justification for that.
I have dropped the check thinking that Param extern params will be
always safe and for param exec params we now have a list of safe
params, so we don't need this check and now again thinking about it,
it seems I might not be right. OTOH, I am not sure if the check as
written is correct for all cases, however, I think for the purpose of
this patch, we can leave it as it is and discuss separately what
should be the check (as suggested by you). I have reverted the check
in the attached patch.
But I'm also wondering if we're missing a trick here, because isn't a
PARAM_EXTERN parameter always safe, given SerializeParamList?
Right.
If so,
this || ought to be &&, but if that adjustment is needed, it seems
like a separate patch.
How will it work if, during planning, we encounter param_extern param
in any list? Won't it return true in that case, because param extern
params will not be present in safe_param_ids, so this check will be
true and then max_parallel_hazard_test will also return true?
How about always returning false for PARAM_EXTERN?
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
pq_pushdown_initplan_v12.patchapplication/octet-stream; name=pq_pushdown_initplan_v12.patchDownload
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 8f7062c..561b85c 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -107,6 +107,7 @@ static void show_tidbitmap_info(BitmapHeapScanState *planstate,
static void show_instrumentation_count(const char *qlabel, int which,
PlanState *planstate, ExplainState *es);
static void show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es);
+static void show_eval_params(Bitmapset *bms_params, ExplainState *es);
static const char *explain_get_index_name(Oid indexId);
static void show_buffer_usage(ExplainState *es, const BufferUsage *usage);
static void ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir,
@@ -631,7 +632,21 @@ ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc)
*/
ps = queryDesc->planstate;
if (IsA(ps, GatherState) &&((Gather *) ps->plan)->invisible)
+ {
+ List *initPlanState = NULL;
+ PlanState *save_ps;
+
+ /*
+ * The Gather node will be omitted in this case, so we need to move
+ * initplans to the outer node to ensure that they remain attached to
+ * the top node (cf standard_planner).
+ */
+ save_ps = ps;
+ initPlanState = ps->initPlan;
ps = outerPlanState(ps);
+ ps->initPlan = initPlanState;
+ save_ps->initPlan = NIL;
+ }
ExplainNode(ps, NIL, NULL, NULL, es);
}
@@ -1441,6 +1456,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
planstate, es);
ExplainPropertyInteger("Workers Planned",
gather->num_workers, es);
+
+ /* Show params evaluated at gather node */
+ if (gather->initParam)
+ show_eval_params(gather->initParam, es);
+
if (es->analyze)
{
int nworkers;
@@ -1463,6 +1483,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
planstate, es);
ExplainPropertyInteger("Workers Planned",
gm->num_workers, es);
+
+ /* Show params evaluated at gather-merge node */
+ if (gm->initParam)
+ show_eval_params(gm->initParam, es);
+
if (es->analyze)
{
int nworkers;
@@ -2488,6 +2513,29 @@ show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es)
}
/*
+ * Show initplan params evaluated at Gather or Gather Merge node.
+ */
+static void
+show_eval_params(Bitmapset *bms_params, ExplainState *es)
+{
+ int paramid = -1;
+ List *params = NIL;
+
+ Assert(bms_params);
+
+ while ((paramid = bms_next_member(bms_params, paramid)) >= 0)
+ {
+ char param[32];
+
+ snprintf(param, sizeof(param), "$%d", paramid);
+ params = lappend(params, pstrdup(param));
+ }
+
+ if (params)
+ ExplainPropertyList("Params Evaluated", params, es);
+}
+
+/*
* Fetch the name of an index in an EXPLAIN
*
* We allow plugins to get control here so that plans involving hypothetical
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index c5e97ef..d170f305 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -1927,6 +1927,33 @@ ExecEvalParamExec(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
}
/*
+ * ExecEvalParamExecParams
+ *
+ * Execute the subplan stored in PARAM_EXEC initplans params, if not executed
+ * till now.
+ */
+void
+ExecEvalParamExecParams(Bitmapset *params, EState *estate)
+{
+ ParamExecData *prm;
+ int paramid;
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ prm = &(estate->es_param_exec_vals[paramid]);
+
+ if (prm->execPlan != NULL)
+ {
+ /* Parameter not evaluated yet, so go do it */
+ ExecSetParamPlan(prm->execPlan, GetPerTupleExprContext(estate));
+ /* ExecSetParamPlan should have processed this param... */
+ Assert(prm->execPlan == NULL);
+ }
+ }
+}
+
+/*
* Evaluate a PARAM_EXTERN parameter.
*
* PARAM_EXTERN parameters must be sought in ecxt_param_list_info.
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index 5dc26ed..57a8fe5 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -23,6 +23,7 @@
#include "postgres.h"
+#include "executor/execExpr.h"
#include "executor/execParallel.h"
#include "executor/executor.h"
#include "executor/nodeBitmapHeapscan.h"
@@ -31,6 +32,7 @@
#include "executor/nodeIndexscan.h"
#include "executor/nodeIndexonlyscan.h"
#include "executor/nodeSeqscan.h"
+#include "executor/nodeSubplan.h"
#include "executor/nodeSort.h"
#include "executor/tqueue.h"
#include "nodes/nodeFuncs.h"
@@ -56,6 +58,7 @@
#define PARALLEL_KEY_INSTRUMENTATION UINT64CONST(0xE000000000000006)
#define PARALLEL_KEY_DSA UINT64CONST(0xE000000000000007)
#define PARALLEL_KEY_QUERY_TEXT UINT64CONST(0xE000000000000008)
+#define PARALLEL_KEY_INITPLAN_PARAMS UINT64CONST(0xE000000000000009)
#define PARALLEL_TUPLE_QUEUE_SIZE 65536
@@ -395,7 +398,8 @@ ExecParallelSetupTupleQueues(ParallelContext *pcxt, bool reinitialize)
* execution and return results to the main backend.
*/
ParallelExecutorInfo *
-ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
+ExecInitParallelPlan(PlanState *planstate, EState *estate,
+ Bitmapset *initParam, int nworkers,
int64 tuples_needed)
{
ParallelExecutorInfo *pei;
@@ -406,10 +410,12 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
char *pstmt_data;
char *pstmt_space;
char *param_space;
+ char *initplan_param_space;
BufferUsage *bufusage_space;
SharedExecutorInstrumentation *instrumentation = NULL;
int pstmt_len;
int param_len;
+ int initplan_param_len;
int instrumentation_len = 0;
int instrument_offset = 0;
Size dsa_minsize = dsa_minimum_size();
@@ -421,6 +427,8 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
pei->finished = false;
pei->planstate = planstate;
+ ExecEvalParamExecParams(initParam, estate);
+
/* Fix up and serialize plan to be sent to workers. */
pstmt_data = ExecSerializePlan(planstate->plan, estate);
@@ -454,6 +462,11 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
shm_toc_estimate_chunk(&pcxt->estimator, param_len);
shm_toc_estimate_keys(&pcxt->estimator, 1);
+ /* Estimate space for initplan params. */
+ initplan_param_len = EstimateInitPlanParamsSpace(estate->es_param_exec_vals, initParam);
+ shm_toc_estimate_chunk(&pcxt->estimator, initplan_param_len);
+ shm_toc_estimate_keys(&pcxt->estimator, 1);
+
/*
* Estimate space for BufferUsage.
*
@@ -528,6 +541,11 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
shm_toc_insert(pcxt->toc, PARALLEL_KEY_PARAMS, param_space);
SerializeParamList(estate->es_param_list_info, ¶m_space);
+ /* Store serialized initplan params. */
+ initplan_param_space = shm_toc_allocate(pcxt->toc, initplan_param_len);
+ shm_toc_insert(pcxt->toc, PARALLEL_KEY_INITPLAN_PARAMS, initplan_param_space);
+ SerializeInitPlanParams(estate->es_param_exec_vals, initParam, &initplan_param_space);
+
/* Allocate space for each worker's BufferUsage; no need to initialize. */
bufusage_space = shm_toc_allocate(pcxt->toc,
mul_size(sizeof(BufferUsage), pcxt->nworkers));
@@ -862,6 +880,21 @@ ExecParallelGetReceiver(dsm_segment *seg, shm_toc *toc)
}
/*
+ * Copy the ParamExecData params corresponding to initplans from dynamic
+ * shared memory. This has to be done once the params are allocated by
+ * executor; that is after ExecutorStart().
+ */
+static void
+ExecParallelInitializeInitPlanParams(shm_toc *toc, ParamExecData *params)
+{
+ char *paramspace;
+
+ /* Reconstruct initplan params. */
+ paramspace = shm_toc_lookup(toc, PARALLEL_KEY_INITPLAN_PARAMS, false);
+ RestoreInitPlanParams(¶mspace, params);
+}
+
+/*
* Create a QueryDesc for the PlannedStmt we are to execute, and return it.
*/
static QueryDesc *
@@ -1046,6 +1079,7 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
/* Special executor initialization steps for parallel workers */
queryDesc->planstate->state->es_query_dsa = area;
+ ExecParallelInitializeInitPlanParams(toc, queryDesc->estate->es_param_exec_vals);
ExecParallelInitializeWorker(queryDesc->planstate, toc);
/* Pass down any tuple bound */
diff --git a/src/backend/executor/nodeGather.c b/src/backend/executor/nodeGather.c
index 8370037..4104dc2 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -158,6 +158,7 @@ ExecGather(PlanState *pstate)
if (!node->pei)
node->pei = ExecInitParallelPlan(node->ps.lefttree,
estate,
+ gather->initParam,
gather->num_workers,
node->tuples_needed);
else
diff --git a/src/backend/executor/nodeGatherMerge.c b/src/backend/executor/nodeGatherMerge.c
index 70f33a9..f75430e 100644
--- a/src/backend/executor/nodeGatherMerge.c
+++ b/src/backend/executor/nodeGatherMerge.c
@@ -202,6 +202,7 @@ ExecGatherMerge(PlanState *pstate)
if (!node->pei)
node->pei = ExecInitParallelPlan(node->ps.lefttree,
estate,
+ gm->initParam,
gm->num_workers,
node->tuples_needed);
else
diff --git a/src/backend/executor/nodeNestloop.c b/src/backend/executor/nodeNestloop.c
index 4447b7c..52514e4 100644
--- a/src/backend/executor/nodeNestloop.c
+++ b/src/backend/executor/nodeNestloop.c
@@ -130,6 +130,7 @@ ExecNestLoop(PlanState *pstate)
{
NestLoopParam *nlp = (NestLoopParam *) lfirst(lc);
int paramno = nlp->paramno;
+ TupleDesc tdesc = outerTupleSlot->tts_tupleDescriptor;
ParamExecData *prm;
prm = &(econtext->ecxt_param_exec_vals[paramno]);
@@ -140,6 +141,7 @@ ExecNestLoop(PlanState *pstate)
prm->value = slot_getattr(outerTupleSlot,
nlp->paramval->varattno,
&(prm->isnull));
+ prm->ptype = TupleDescAttr(tdesc, nlp->paramval->varattno - 1)->atttypid;
/* Flag parameter value as changed */
innerPlan->chgParam = bms_add_member(innerPlan->chgParam,
paramno);
diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c
index 77ef6f3..06409ed 100644
--- a/src/backend/executor/nodeSubplan.c
+++ b/src/backend/executor/nodeSubplan.c
@@ -30,12 +30,16 @@
#include <math.h>
#include "access/htup_details.h"
+#include "catalog/pg_type.h"
#include "executor/executor.h"
#include "executor/nodeSubplan.h"
#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
#include "miscadmin.h"
#include "optimizer/clauses.h"
+#include "storage/shmem.h"
#include "utils/array.h"
+#include "utils/datum.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
@@ -273,11 +277,13 @@ ExecScanSubPlan(SubPlanState *node,
forboth(l, subplan->parParam, pvar, node->args)
{
int paramid = lfirst_int(l);
+ ExprState *exprstate = (ExprState *) lfirst(pvar);
ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]);
- prm->value = ExecEvalExprSwitchContext((ExprState *) lfirst(pvar),
+ prm->value = ExecEvalExprSwitchContext(exprstate,
econtext,
&(prm->isnull));
+ prm->ptype = exprType((Node *) exprstate->expr);
planstate->chgParam = bms_add_member(planstate->chgParam, paramid);
}
@@ -390,6 +396,7 @@ ExecScanSubPlan(SubPlanState *node,
prmdata = &(econtext->ecxt_param_exec_vals[paramid]);
Assert(prmdata->execPlan == NULL);
prmdata->value = slot_getattr(slot, col, &(prmdata->isnull));
+ prmdata->ptype = TupleDescAttr(tdesc, col - 1)->atttypid;
col++;
}
@@ -556,11 +563,13 @@ buildSubPlanHash(SubPlanState *node, ExprContext *econtext)
{
int paramid = lfirst_int(plst);
ParamExecData *prmdata;
+ TupleDesc tdesc = slot->tts_tupleDescriptor;
prmdata = &(innerecontext->ecxt_param_exec_vals[paramid]);
Assert(prmdata->execPlan == NULL);
prmdata->value = slot_getattr(slot, col,
&(prmdata->isnull));
+ prmdata->ptype = TupleDescAttr(tdesc, col - 1)->atttypid;
col++;
}
slot = ExecProject(node->projRight);
@@ -924,6 +933,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
ListCell *l;
bool found = false;
ArrayBuildStateAny *astate = NULL;
+ Oid ptype;
if (subLinkType == ANY_SUBLINK ||
subLinkType == ALL_SUBLINK)
@@ -931,6 +941,8 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
if (subLinkType == CTE_SUBLINK)
elog(ERROR, "CTE subplans should not be executed via ExecSetParamPlan");
+ ptype = exprType((Node *) node->subplan);
+
/* Initialize ArrayBuildStateAny in caller's context, if needed */
if (subLinkType == ARRAY_SUBLINK)
astate = initArrayResultAny(subplan->firstColType,
@@ -953,11 +965,13 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
forboth(l, subplan->parParam, pvar, node->args)
{
int paramid = lfirst_int(l);
+ ExprState *exprstate = (ExprState *) lfirst(pvar);
ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]);
- prm->value = ExecEvalExprSwitchContext((ExprState *) lfirst(pvar),
+ prm->value = ExecEvalExprSwitchContext(exprstate,
econtext,
&(prm->isnull));
+ prm->ptype = exprType((Node *) exprstate->expr);
planstate->chgParam = bms_add_member(planstate->chgParam, paramid);
}
@@ -980,6 +994,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = BoolGetDatum(true);
+ prm->ptype = ptype;
prm->isnull = false;
found = true;
break;
@@ -1031,6 +1046,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = heap_getattr(node->curTuple, i, tdesc,
&(prm->isnull));
+ prm->ptype = TupleDescAttr(tdesc, i - 1)->atttypid;
i++;
}
}
@@ -1053,6 +1069,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
true);
prm->execPlan = NULL;
prm->value = node->curArray;
+ prm->ptype = ptype;
prm->isnull = false;
}
else if (!found)
@@ -1065,6 +1082,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = BoolGetDatum(false);
+ prm->ptype = ptype;
prm->isnull = false;
}
else
@@ -1077,6 +1095,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = (Datum) 0;
+ prm->ptype = VOIDOID;
prm->isnull = true;
}
}
@@ -1207,3 +1226,133 @@ ExecAlternativeSubPlan(AlternativeSubPlanState *node,
return ExecSubPlan(activesp, econtext, isNull);
}
+
+/*
+ * Estimate the amount of space required to serialize the InitPlan params.
+ */
+Size
+EstimateInitPlanParamsSpace(ParamExecData *paramExecVals, Bitmapset *params)
+{
+ int paramid;
+ Size sz = sizeof(int);
+ ParamExecData *prm;
+
+ if (params == NULL)
+ return sz;
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ Oid typeOid;
+ int16 typLen;
+ bool typByVal;
+
+ prm = &(paramExecVals[paramid]);
+ typeOid = prm->ptype;
+
+ sz = add_size(sz, sizeof(int)); /* space for paramid */
+ sz = add_size(sz, sizeof(Oid)); /* space for type OID */
+
+ /* space for datum/isnull */
+ if (OidIsValid(typeOid))
+ get_typlenbyval(typeOid, &typLen, &typByVal);
+ else
+ {
+ /* If no type OID, assume by-value, like copyParamList does. */
+ typLen = sizeof(Datum);
+ typByVal = true;
+ }
+ sz = add_size(sz,
+ datumEstimateSpace(prm->value, prm->isnull, typByVal, typLen));
+ }
+ return sz;
+}
+
+/*
+ * Serialize ParamExecData params corresponding to initplans.
+ *
+ * We write the number of parameters first, as a 4-byte integer, and then
+ * write details for each parameter in turn. The details for each parameter
+ * consist of a 4-byte paramid (location of param in execution time internal
+ * parameter array), 4-byte type OID, and then the datum as serialized by
+ * datumSerialize().
+ *
+ * The above format is quite similar to the format used to serialize
+ * paramListInfo structure, so if we change either format, then consider to
+ * change at both the places.
+ */
+void
+SerializeInitPlanParams(ParamExecData *paramExecVals, Bitmapset *params,
+ char **start_address)
+{
+ int nparams;
+ int paramid;
+ ParamExecData *prm;
+
+ nparams = bms_num_members(params);
+ memcpy(*start_address, &nparams, sizeof(int));
+ *start_address += sizeof(int);
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ Oid typeOid;
+ int16 typLen;
+ bool typByVal;
+
+ prm = &(paramExecVals[paramid]);
+ typeOid = prm->ptype;
+
+ /* Write paramid. */
+ memcpy(*start_address, ¶mid, sizeof(int));
+ *start_address += sizeof(int);
+
+ /* Write OID. */
+ memcpy(*start_address, &typeOid, sizeof(Oid));
+ *start_address += sizeof(Oid);
+
+ /* space for datum/isnull */
+ if (OidIsValid(typeOid))
+ get_typlenbyval(typeOid, &typLen, &typByVal);
+ else
+ {
+ /* If no type OID, assume by-value, like copyParamList does. */
+ typLen = sizeof(Datum);
+ typByVal = true;
+ }
+ datumSerialize(prm->value, prm->isnull, typByVal, typLen,
+ start_address);
+ }
+}
+
+/*
+ * Restore ParamExecData params corresponding to initplans.
+ */
+void
+RestoreInitPlanParams(char **start_address, ParamExecData *params)
+{
+ int nparams;
+ int i;
+ int paramid;
+
+ memcpy(&nparams, *start_address, sizeof(int));
+ *start_address += sizeof(int);
+
+ for (i = 0; i < nparams; i++)
+ {
+ ParamExecData *prm;
+
+ /* Read paramid */
+ memcpy(¶mid, *start_address, sizeof(int));
+ *start_address += sizeof(int);
+ prm = ¶ms[paramid];
+
+ /* Read type OID. */
+ memcpy(&prm->ptype, *start_address, sizeof(Oid));
+ *start_address += sizeof(Oid);
+
+ /* Read datum/isnull. */
+ prm->value = datumRestore(start_address, &prm->isnull);
+ prm->execPlan = NULL;
+ }
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index c1a83ca..61fe226 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -364,6 +364,7 @@ _copyGather(const Gather *from)
COPY_SCALAR_FIELD(rescan_param);
COPY_SCALAR_FIELD(single_copy);
COPY_SCALAR_FIELD(invisible);
+ COPY_BITMAPSET_FIELD(initParam);
return newnode;
}
@@ -391,6 +392,7 @@ _copyGatherMerge(const GatherMerge *from)
COPY_POINTER_FIELD(sortOperators, from->numCols * sizeof(Oid));
COPY_POINTER_FIELD(collations, from->numCols * sizeof(Oid));
COPY_POINTER_FIELD(nullsFirst, from->numCols * sizeof(bool));
+ COPY_BITMAPSET_FIELD(initParam);
return newnode;
}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 2532edc..556d533 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -482,6 +482,7 @@ _outGather(StringInfo str, const Gather *node)
WRITE_INT_FIELD(rescan_param);
WRITE_BOOL_FIELD(single_copy);
WRITE_BOOL_FIELD(invisible);
+ WRITE_BITMAPSET_FIELD(initParam);
}
static void
@@ -512,6 +513,8 @@ _outGatherMerge(StringInfo str, const GatherMerge *node)
appendStringInfoString(str, " :nullsFirst");
for (i = 0; i < node->numCols; i++)
appendStringInfo(str, " %s", booltostr(node->nullsFirst[i]));
+
+ WRITE_BITMAPSET_FIELD(initParam);
}
static void
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 07ba691..c3bcf47 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2165,6 +2165,7 @@ _readGather(void)
READ_INT_FIELD(rescan_param);
READ_BOOL_FIELD(single_copy);
READ_BOOL_FIELD(invisible);
+ READ_BITMAPSET_FIELD(initParam);
READ_DONE();
}
@@ -2186,6 +2187,7 @@ _readGatherMerge(void)
READ_OID_ARRAY(sortOperators, local_node->numCols);
READ_OID_ARRAY(collations, local_node->numCols);
READ_BOOL_ARRAY(nullsFirst, local_node->numCols);
+ READ_BITMAPSET_FIELD(initParam);
READ_DONE();
}
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 5535b63..ef843dc 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -39,6 +39,7 @@
#include "optimizer/planner.h"
#include "optimizer/prep.h"
#include "optimizer/restrictinfo.h"
+#include "optimizer/subselect.h"
#include "optimizer/tlist.h"
#include "optimizer/var.h"
#include "parser/parse_clause.h"
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 792ea84..7f9b0da 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -6266,6 +6266,7 @@ make_gather(List *qptlist,
node->rescan_param = rescan_param;
node->single_copy = single_copy;
node->invisible = false;
+ node->initParam = NULL;
return node;
}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index ecdd728..3378997 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -376,6 +376,14 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
{
Gather *gather = makeNode(Gather);
+ /*
+ * If there are any initPlans attached to the formerly-top plan node,
+ * move them up to the Gather node; same as we do for Material node in
+ * materialize_finished_plan.
+ */
+ gather->plan.initPlan = top_plan->initPlan;
+ top_plan->initPlan = NIL;
+
gather->plan.targetlist = top_plan->targetlist;
gather->plan.qual = NIL;
gather->plan.lefttree = top_plan;
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 1382b67..f4b7891 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -107,6 +107,7 @@ static Node *fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context);
static bool fix_scan_expr_walker(Node *node, fix_scan_expr_context *context);
static void set_join_references(PlannerInfo *root, Join *join, int rtoffset);
static void set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset);
+static void set_param_references(PlannerInfo *root, Plan *plan);
static Node *convert_combining_aggrefs(Node *node, void *context);
static void set_dummy_tlist_references(Plan *plan, int rtoffset);
static indexed_tlist *build_tlist_index(List *tlist);
@@ -632,7 +633,10 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
case T_Gather:
case T_GatherMerge:
- set_upper_references(root, plan, rtoffset);
+ {
+ set_upper_references(root, plan, rtoffset);
+ set_param_references(root, plan);
+ }
break;
case T_Hash:
@@ -1782,6 +1786,49 @@ set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset)
}
/*
+ * set_param_references
+ * Initialize the initParam list in Gather or Gather merge node such that
+ * it contains reference of all the params that needs to be evaluated
+ * before execution of the node. It contains the initplan params that are
+ * being passed to the plan nodes below it.
+ */
+static void
+set_param_references(PlannerInfo *root, Plan *plan)
+{
+ Assert(IsA(plan, Gather) || IsA(plan, GatherMerge));
+
+ if (plan->lefttree->extParam)
+ {
+ PlannerInfo *proot;
+ Bitmapset *initSetParam = NULL;
+ ListCell *l;
+
+ for (proot = root; proot != NULL; proot = proot->parent_root)
+ {
+ foreach(l, proot->init_plans)
+ {
+ SubPlan *initsubplan = (SubPlan *) lfirst(l);
+ ListCell *l2;
+
+ foreach(l2, initsubplan->setParam)
+ {
+ initSetParam = bms_add_member(initSetParam, lfirst_int(l2));
+ }
+ }
+ }
+
+ /*
+ * Remember the list of all external initplan params that are used by
+ * the children of Gather or Gather merge node.
+ */
+ if (IsA(plan, Gather))
+ ((Gather *) plan)->initParam = bms_intersect(plan->lefttree->extParam, initSetParam);
+ else
+ ((GatherMerge *) plan)->initParam = bms_intersect(plan->lefttree->extParam, initSetParam);
+ }
+}
+
+/*
* Recursively scan an expression tree and convert Aggrefs to the proper
* intermediate form for combining aggregates. This means (1) replacing each
* one's argument list with a single argument that is the original Aggref
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 7961362..3ed2993 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -1087,6 +1087,8 @@ bool
is_parallel_safe(PlannerInfo *root, Node *node)
{
max_parallel_hazard_context context;
+ PlannerInfo *proot;
+ ListCell *l;
/*
* Even if the original querytree contained nothing unsafe, we need to
@@ -1101,6 +1103,25 @@ is_parallel_safe(PlannerInfo *root, Node *node)
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_RESTRICTED;
context.safe_param_ids = NIL;
+
+ /*
+ * The params that refer to the same or parent query level are considered
+ * parallel-safe. The idea is that we compute such params at Gather or
+ * Gather Merge node and pass their value to workers.
+ */
+ for (proot = root; proot != NULL; proot = proot->parent_root)
+ {
+ foreach(l, proot->init_plans)
+ {
+ SubPlan *initsubplan = (SubPlan *) lfirst(l);
+ ListCell *l2;
+
+ foreach(l2, initsubplan->setParam)
+ context.safe_param_ids = lcons_int(lfirst_int(l2),
+ context.safe_param_ids);
+ }
+ }
+
return !max_parallel_hazard_walker(node, &context);
}
@@ -1224,7 +1245,9 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
/*
* We can't pass Params to workers at the moment either, so they are also
* parallel-restricted, unless they are PARAM_EXEC Params listed in
- * safe_param_ids, meaning they could be generated within the worker.
+ * safe_param_ids, meaning they could be either generated within the
+ * worker or can be computed in master and then their value can be passed
+ * to the worker.
*/
else if (IsA(node, Param))
{
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 78d2247..5bbb63a 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -609,6 +609,7 @@ extern ExprEvalOp ExecEvalStepOp(ExprState *state, ExprEvalStep *op);
*/
extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
+extern void ExecEvalParamExecParams(Bitmapset *params, EState *estate);
extern void ExecEvalParamExtern(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
extern void ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op);
diff --git a/src/include/executor/execParallel.h b/src/include/executor/execParallel.h
index e1b3e7a..88e2757 100644
--- a/src/include/executor/execParallel.h
+++ b/src/include/executor/execParallel.h
@@ -35,7 +35,8 @@ typedef struct ParallelExecutorInfo
} ParallelExecutorInfo;
extern ParallelExecutorInfo *ExecInitParallelPlan(PlanState *planstate,
- EState *estate, int nworkers, int64 tuples_needed);
+ EState *estate, Bitmapset *initParam, int nworkers,
+ int64 tuples_needed);
extern void ExecParallelCreateReaders(ParallelExecutorInfo *pei);
extern void ExecParallelFinish(ParallelExecutorInfo *pei);
extern void ExecParallelCleanup(ParallelExecutorInfo *pei);
diff --git a/src/include/executor/nodeSubplan.h b/src/include/executor/nodeSubplan.h
index 5dbaeeb..40b1a34 100644
--- a/src/include/executor/nodeSubplan.h
+++ b/src/include/executor/nodeSubplan.h
@@ -28,4 +28,10 @@ extern void ExecReScanSetParamPlan(SubPlanState *node, PlanState *parent);
extern void ExecSetParamPlan(SubPlanState *node, ExprContext *econtext);
+extern Size EstimateInitPlanParamsSpace(ParamExecData *paramExecVals, Bitmapset *params);
+
+extern void SerializeInitPlanParams(ParamExecData *paramExecVals, Bitmapset *params, char **start_address);
+
+extern void RestoreInitPlanParams(char **start_address, ParamExecData *params);
+
#endif /* NODESUBPLAN_H */
diff --git a/src/include/nodes/params.h b/src/include/nodes/params.h
index 55219da..e96a1ea 100644
--- a/src/include/nodes/params.h
+++ b/src/include/nodes/params.h
@@ -98,6 +98,16 @@ typedef struct ParamExecData
{
void *execPlan; /* should be "SubPlanState *" */
Datum value;
+
+ /*
+ * parameter's datatype, or 0. This is required so that datum value can
+ * be read and used for other purposes like passing it to worker backend
+ * via shared memory. This is required only for initPlan's evaluation,
+ * however for consistency we set this for Subplan as well. We left it
+ * for other cases like CTE or RecursiveUnion cases where this structure
+ * is not used for evaluation of subplans.
+ */
+ Oid ptype;
bool isnull;
} ParamExecData;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index a382331..a16909c 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -841,6 +841,8 @@ typedef struct Gather
int rescan_param; /* ID of Param that signals a rescan, or -1 */
bool single_copy; /* don't execute plan more than once */
bool invisible; /* suppress EXPLAIN display (for testing)? */
+ Bitmapset *initParam; /* param id's of initplans which are referred
+ * at gather or one of it's child node */
} Gather;
/* ------------
@@ -858,6 +860,8 @@ typedef struct GatherMerge
Oid *sortOperators; /* OIDs of operators to sort them by */
Oid *collations; /* OIDs of collations */
bool *nullsFirst; /* NULLS FIRST/LAST directions */
+ Bitmapset *initParam; /* param id's of initplans which are referred
+ * at gather merge or one of it's child node */
} GatherMerge;
/* ----------------
diff --git a/src/test/regress/expected/select_parallel.out b/src/test/regress/expected/select_parallel.out
index 2ae600f..22af824 100644
--- a/src/test/regress/expected/select_parallel.out
+++ b/src/test/regress/expected/select_parallel.out
@@ -139,6 +139,39 @@ explain (costs off)
(4 rows)
alter table tenk2 reset (parallel_workers);
+--test parallel plan for a query containing initplan.
+set enable_indexscan = off;
+set enable_indexonlyscan = off;
+set enable_bitmapscan = off;
+alter table tenk2 set (parallel_workers = 2);
+explain (costs off)
+ select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+ QUERY PLAN
+------------------------------------------------------
+ Aggregate
+ InitPlan 1 (returns $2)
+ -> Finalize Aggregate
+ -> Gather
+ Workers Planned: 2
+ -> Partial Aggregate
+ -> Parallel Seq Scan on tenk2
+ -> Gather
+ Workers Planned: 4
+ Params Evaluated: $2
+ -> Parallel Seq Scan on tenk1
+ Filter: (unique1 = $2)
+(12 rows)
+
+select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+ count
+-------
+ 1
+(1 row)
+
+reset enable_indexscan;
+reset enable_indexonlyscan;
+reset enable_bitmapscan;
+alter table tenk2 reset (parallel_workers);
-- test parallel index scans.
set enable_seqscan to off;
set enable_bitmapscan to off;
diff --git a/src/test/regress/sql/select_parallel.sql b/src/test/regress/sql/select_parallel.sql
index 89fe80a..ae144ca 100644
--- a/src/test/regress/sql/select_parallel.sql
+++ b/src/test/regress/sql/select_parallel.sql
@@ -52,6 +52,21 @@ explain (costs off)
(select ten from tenk2);
alter table tenk2 reset (parallel_workers);
+--test parallel plan for a query containing initplan.
+set enable_indexscan = off;
+set enable_indexonlyscan = off;
+set enable_bitmapscan = off;
+alter table tenk2 set (parallel_workers = 2);
+
+explain (costs off)
+ select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+
+reset enable_indexscan;
+reset enable_indexonlyscan;
+reset enable_bitmapscan;
+alter table tenk2 reset (parallel_workers);
+
-- test parallel index scans.
set enable_seqscan to off;
set enable_bitmapscan to off;
On 10/09/2017 03:26 PM, Amit Kapila wrote:
I have reverted the check
in the attached patch.
I have applied this patch against PG HEAD and run sqlsmith and analyzed
results . didn't find any specific failures against this patch.
--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Oct 9, 2017 at 5:56 AM, Amit Kapila <amit.kapila16@gmail.com> wrote:
How about always returning false for PARAM_EXTERN?
Yeah, I think that's what we should do. Let's do that first as a
separate patch, which we might even want to back-patch, then return to
this.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Oct 11, 2017 at 9:24 PM, Robert Haas <robertmhaas@gmail.com> wrote:
On Mon, Oct 9, 2017 at 5:56 AM, Amit Kapila <amit.kapila16@gmail.com> wrote:
How about always returning false for PARAM_EXTERN?
Yeah, I think that's what we should do. Let's do that first as a
separate patch, which we might even want to back-patch, then return to
this.
Sure, I have started a new thread as this fix lead to some other
failures. I hope that is okay.
/messages/by-id/CAA4eK1+_BuZrmVCeua5Eqnm4Co9DAXdM5HPAOE2J19ePbR912Q@mail.gmail.com
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 10/11/2017 12:42 PM, tushar wrote:
On 10/09/2017 03:26 PM, Amit Kapila wrote:
I have reverted the check
in the attached patch.I have applied this patch against PG HEAD and run sqlsmith and
analyzed results . didn't find any specific failures against this patch.
I did some testing of this feature and written few testcases . PFA the
sql file(along with the expected .out file) .
--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company
On Wed, Oct 11, 2017 at 9:24 PM, Robert Haas <robertmhaas@gmail.com> wrote:
On Mon, Oct 9, 2017 at 5:56 AM, Amit Kapila <amit.kapila16@gmail.com> wrote:
How about always returning false for PARAM_EXTERN?
Yeah, I think that's what we should do. Let's do that first as a
separate patch, which we might even want to back-patch, then return to
this.
Now that the PARAM_EXTERN issue is fixed, I have rebased this patch.
This patch had been switched to Ready For Committer in last CF, then
Robert had comments which I have addressed, so I think the status
should be switched back to Ready For committer. Let me know if you
think it should be switched to some other status.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
pq_pushdown_initplan_v13.patchapplication/octet-stream; name=pq_pushdown_initplan_v13.patchDownload
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 8f7062c..561b85c 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -107,6 +107,7 @@ static void show_tidbitmap_info(BitmapHeapScanState *planstate,
static void show_instrumentation_count(const char *qlabel, int which,
PlanState *planstate, ExplainState *es);
static void show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es);
+static void show_eval_params(Bitmapset *bms_params, ExplainState *es);
static const char *explain_get_index_name(Oid indexId);
static void show_buffer_usage(ExplainState *es, const BufferUsage *usage);
static void ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir,
@@ -631,7 +632,21 @@ ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc)
*/
ps = queryDesc->planstate;
if (IsA(ps, GatherState) &&((Gather *) ps->plan)->invisible)
+ {
+ List *initPlanState = NULL;
+ PlanState *save_ps;
+
+ /*
+ * The Gather node will be omitted in this case, so we need to move
+ * initplans to the outer node to ensure that they remain attached to
+ * the top node (cf standard_planner).
+ */
+ save_ps = ps;
+ initPlanState = ps->initPlan;
ps = outerPlanState(ps);
+ ps->initPlan = initPlanState;
+ save_ps->initPlan = NIL;
+ }
ExplainNode(ps, NIL, NULL, NULL, es);
}
@@ -1441,6 +1456,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
planstate, es);
ExplainPropertyInteger("Workers Planned",
gather->num_workers, es);
+
+ /* Show params evaluated at gather node */
+ if (gather->initParam)
+ show_eval_params(gather->initParam, es);
+
if (es->analyze)
{
int nworkers;
@@ -1463,6 +1483,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
planstate, es);
ExplainPropertyInteger("Workers Planned",
gm->num_workers, es);
+
+ /* Show params evaluated at gather-merge node */
+ if (gm->initParam)
+ show_eval_params(gm->initParam, es);
+
if (es->analyze)
{
int nworkers;
@@ -2488,6 +2513,29 @@ show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es)
}
/*
+ * Show initplan params evaluated at Gather or Gather Merge node.
+ */
+static void
+show_eval_params(Bitmapset *bms_params, ExplainState *es)
+{
+ int paramid = -1;
+ List *params = NIL;
+
+ Assert(bms_params);
+
+ while ((paramid = bms_next_member(bms_params, paramid)) >= 0)
+ {
+ char param[32];
+
+ snprintf(param, sizeof(param), "$%d", paramid);
+ params = lappend(params, pstrdup(param));
+ }
+
+ if (params)
+ ExplainPropertyList("Params Evaluated", params, es);
+}
+
+/*
* Fetch the name of an index in an EXPLAIN
*
* We allow plugins to get control here so that plans involving hypothetical
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index a0f537b..6c4612d 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -1927,6 +1927,33 @@ ExecEvalParamExec(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
}
/*
+ * ExecEvalParamExecParams
+ *
+ * Execute the subplan stored in PARAM_EXEC initplans params, if not executed
+ * till now.
+ */
+void
+ExecEvalParamExecParams(Bitmapset *params, EState *estate)
+{
+ ParamExecData *prm;
+ int paramid;
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ prm = &(estate->es_param_exec_vals[paramid]);
+
+ if (prm->execPlan != NULL)
+ {
+ /* Parameter not evaluated yet, so go do it */
+ ExecSetParamPlan(prm->execPlan, GetPerTupleExprContext(estate));
+ /* ExecSetParamPlan should have processed this param... */
+ Assert(prm->execPlan == NULL);
+ }
+ }
+}
+
+/*
* Evaluate a PARAM_EXTERN parameter.
*
* PARAM_EXTERN parameters must be sought in ecxt_param_list_info.
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index 1b477ba..a6654d1 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -23,6 +23,7 @@
#include "postgres.h"
+#include "executor/execExpr.h"
#include "executor/execParallel.h"
#include "executor/executor.h"
#include "executor/nodeBitmapHeapscan.h"
@@ -31,6 +32,7 @@
#include "executor/nodeIndexscan.h"
#include "executor/nodeIndexonlyscan.h"
#include "executor/nodeSeqscan.h"
+#include "executor/nodeSubplan.h"
#include "executor/nodeSort.h"
#include "executor/tqueue.h"
#include "nodes/nodeFuncs.h"
@@ -56,6 +58,7 @@
#define PARALLEL_KEY_INSTRUMENTATION UINT64CONST(0xE000000000000006)
#define PARALLEL_KEY_DSA UINT64CONST(0xE000000000000007)
#define PARALLEL_KEY_QUERY_TEXT UINT64CONST(0xE000000000000008)
+#define PARALLEL_KEY_INITPLAN_PARAMS UINT64CONST(0xE000000000000009)
#define PARALLEL_TUPLE_QUEUE_SIZE 65536
@@ -395,7 +398,8 @@ ExecParallelSetupTupleQueues(ParallelContext *pcxt, bool reinitialize)
* execution and return results to the main backend.
*/
ParallelExecutorInfo *
-ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
+ExecInitParallelPlan(PlanState *planstate, EState *estate,
+ Bitmapset *initParam, int nworkers,
int64 tuples_needed)
{
ParallelExecutorInfo *pei;
@@ -406,10 +410,12 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
char *pstmt_data;
char *pstmt_space;
char *param_space;
+ char *initplan_param_space;
BufferUsage *bufusage_space;
SharedExecutorInstrumentation *instrumentation = NULL;
int pstmt_len;
int param_len;
+ int initplan_param_len;
int instrumentation_len = 0;
int instrument_offset = 0;
Size dsa_minsize = dsa_minimum_size();
@@ -421,6 +427,8 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
pei->finished = false;
pei->planstate = planstate;
+ ExecEvalParamExecParams(initParam, estate);
+
/* Fix up and serialize plan to be sent to workers. */
pstmt_data = ExecSerializePlan(planstate->plan, estate);
@@ -454,6 +462,11 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
shm_toc_estimate_chunk(&pcxt->estimator, param_len);
shm_toc_estimate_keys(&pcxt->estimator, 1);
+ /* Estimate space for initplan params. */
+ initplan_param_len = EstimateInitPlanParamsSpace(estate->es_param_exec_vals, initParam);
+ shm_toc_estimate_chunk(&pcxt->estimator, initplan_param_len);
+ shm_toc_estimate_keys(&pcxt->estimator, 1);
+
/*
* Estimate space for BufferUsage.
*
@@ -528,6 +541,11 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
shm_toc_insert(pcxt->toc, PARALLEL_KEY_PARAMS, param_space);
SerializeParamList(estate->es_param_list_info, ¶m_space);
+ /* Store serialized initplan params. */
+ initplan_param_space = shm_toc_allocate(pcxt->toc, initplan_param_len);
+ shm_toc_insert(pcxt->toc, PARALLEL_KEY_INITPLAN_PARAMS, initplan_param_space);
+ SerializeInitPlanParams(estate->es_param_exec_vals, initParam, &initplan_param_space);
+
/* Allocate space for each worker's BufferUsage; no need to initialize. */
bufusage_space = shm_toc_allocate(pcxt->toc,
mul_size(sizeof(BufferUsage), pcxt->nworkers));
@@ -862,6 +880,21 @@ ExecParallelGetReceiver(dsm_segment *seg, shm_toc *toc)
}
/*
+ * Copy the ParamExecData params corresponding to initplans from dynamic
+ * shared memory. This has to be done once the params are allocated by
+ * executor; that is after ExecutorStart().
+ */
+static void
+ExecParallelInitializeInitPlanParams(shm_toc *toc, ParamExecData *params)
+{
+ char *paramspace;
+
+ /* Reconstruct initplan params. */
+ paramspace = shm_toc_lookup(toc, PARALLEL_KEY_INITPLAN_PARAMS, false);
+ RestoreInitPlanParams(¶mspace, params);
+}
+
+/*
* Create a QueryDesc for the PlannedStmt we are to execute, and return it.
*/
static QueryDesc *
@@ -1046,6 +1079,7 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
/* Special executor initialization steps for parallel workers */
queryDesc->planstate->state->es_query_dsa = area;
+ ExecParallelInitializeInitPlanParams(toc, queryDesc->estate->es_param_exec_vals);
ExecParallelInitializeWorker(queryDesc->planstate, toc);
/* Pass down any tuple bound */
diff --git a/src/backend/executor/nodeGather.c b/src/backend/executor/nodeGather.c
index 639f4f5..eb9949f 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -158,6 +158,7 @@ ExecGather(PlanState *pstate)
if (!node->pei)
node->pei = ExecInitParallelPlan(node->ps.lefttree,
estate,
+ gather->initParam,
gather->num_workers,
node->tuples_needed);
else
diff --git a/src/backend/executor/nodeGatherMerge.c b/src/backend/executor/nodeGatherMerge.c
index 5625b12..76e07cc 100644
--- a/src/backend/executor/nodeGatherMerge.c
+++ b/src/backend/executor/nodeGatherMerge.c
@@ -202,6 +202,7 @@ ExecGatherMerge(PlanState *pstate)
if (!node->pei)
node->pei = ExecInitParallelPlan(node->ps.lefttree,
estate,
+ gm->initParam,
gm->num_workers,
node->tuples_needed);
else
diff --git a/src/backend/executor/nodeNestloop.c b/src/backend/executor/nodeNestloop.c
index 4447b7c..52514e4 100644
--- a/src/backend/executor/nodeNestloop.c
+++ b/src/backend/executor/nodeNestloop.c
@@ -130,6 +130,7 @@ ExecNestLoop(PlanState *pstate)
{
NestLoopParam *nlp = (NestLoopParam *) lfirst(lc);
int paramno = nlp->paramno;
+ TupleDesc tdesc = outerTupleSlot->tts_tupleDescriptor;
ParamExecData *prm;
prm = &(econtext->ecxt_param_exec_vals[paramno]);
@@ -140,6 +141,7 @@ ExecNestLoop(PlanState *pstate)
prm->value = slot_getattr(outerTupleSlot,
nlp->paramval->varattno,
&(prm->isnull));
+ prm->ptype = TupleDescAttr(tdesc, nlp->paramval->varattno - 1)->atttypid;
/* Flag parameter value as changed */
innerPlan->chgParam = bms_add_member(innerPlan->chgParam,
paramno);
diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c
index 77ef6f3..06409ed 100644
--- a/src/backend/executor/nodeSubplan.c
+++ b/src/backend/executor/nodeSubplan.c
@@ -30,12 +30,16 @@
#include <math.h>
#include "access/htup_details.h"
+#include "catalog/pg_type.h"
#include "executor/executor.h"
#include "executor/nodeSubplan.h"
#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
#include "miscadmin.h"
#include "optimizer/clauses.h"
+#include "storage/shmem.h"
#include "utils/array.h"
+#include "utils/datum.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
@@ -273,11 +277,13 @@ ExecScanSubPlan(SubPlanState *node,
forboth(l, subplan->parParam, pvar, node->args)
{
int paramid = lfirst_int(l);
+ ExprState *exprstate = (ExprState *) lfirst(pvar);
ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]);
- prm->value = ExecEvalExprSwitchContext((ExprState *) lfirst(pvar),
+ prm->value = ExecEvalExprSwitchContext(exprstate,
econtext,
&(prm->isnull));
+ prm->ptype = exprType((Node *) exprstate->expr);
planstate->chgParam = bms_add_member(planstate->chgParam, paramid);
}
@@ -390,6 +396,7 @@ ExecScanSubPlan(SubPlanState *node,
prmdata = &(econtext->ecxt_param_exec_vals[paramid]);
Assert(prmdata->execPlan == NULL);
prmdata->value = slot_getattr(slot, col, &(prmdata->isnull));
+ prmdata->ptype = TupleDescAttr(tdesc, col - 1)->atttypid;
col++;
}
@@ -556,11 +563,13 @@ buildSubPlanHash(SubPlanState *node, ExprContext *econtext)
{
int paramid = lfirst_int(plst);
ParamExecData *prmdata;
+ TupleDesc tdesc = slot->tts_tupleDescriptor;
prmdata = &(innerecontext->ecxt_param_exec_vals[paramid]);
Assert(prmdata->execPlan == NULL);
prmdata->value = slot_getattr(slot, col,
&(prmdata->isnull));
+ prmdata->ptype = TupleDescAttr(tdesc, col - 1)->atttypid;
col++;
}
slot = ExecProject(node->projRight);
@@ -924,6 +933,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
ListCell *l;
bool found = false;
ArrayBuildStateAny *astate = NULL;
+ Oid ptype;
if (subLinkType == ANY_SUBLINK ||
subLinkType == ALL_SUBLINK)
@@ -931,6 +941,8 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
if (subLinkType == CTE_SUBLINK)
elog(ERROR, "CTE subplans should not be executed via ExecSetParamPlan");
+ ptype = exprType((Node *) node->subplan);
+
/* Initialize ArrayBuildStateAny in caller's context, if needed */
if (subLinkType == ARRAY_SUBLINK)
astate = initArrayResultAny(subplan->firstColType,
@@ -953,11 +965,13 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
forboth(l, subplan->parParam, pvar, node->args)
{
int paramid = lfirst_int(l);
+ ExprState *exprstate = (ExprState *) lfirst(pvar);
ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]);
- prm->value = ExecEvalExprSwitchContext((ExprState *) lfirst(pvar),
+ prm->value = ExecEvalExprSwitchContext(exprstate,
econtext,
&(prm->isnull));
+ prm->ptype = exprType((Node *) exprstate->expr);
planstate->chgParam = bms_add_member(planstate->chgParam, paramid);
}
@@ -980,6 +994,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = BoolGetDatum(true);
+ prm->ptype = ptype;
prm->isnull = false;
found = true;
break;
@@ -1031,6 +1046,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = heap_getattr(node->curTuple, i, tdesc,
&(prm->isnull));
+ prm->ptype = TupleDescAttr(tdesc, i - 1)->atttypid;
i++;
}
}
@@ -1053,6 +1069,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
true);
prm->execPlan = NULL;
prm->value = node->curArray;
+ prm->ptype = ptype;
prm->isnull = false;
}
else if (!found)
@@ -1065,6 +1082,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = BoolGetDatum(false);
+ prm->ptype = ptype;
prm->isnull = false;
}
else
@@ -1077,6 +1095,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = (Datum) 0;
+ prm->ptype = VOIDOID;
prm->isnull = true;
}
}
@@ -1207,3 +1226,133 @@ ExecAlternativeSubPlan(AlternativeSubPlanState *node,
return ExecSubPlan(activesp, econtext, isNull);
}
+
+/*
+ * Estimate the amount of space required to serialize the InitPlan params.
+ */
+Size
+EstimateInitPlanParamsSpace(ParamExecData *paramExecVals, Bitmapset *params)
+{
+ int paramid;
+ Size sz = sizeof(int);
+ ParamExecData *prm;
+
+ if (params == NULL)
+ return sz;
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ Oid typeOid;
+ int16 typLen;
+ bool typByVal;
+
+ prm = &(paramExecVals[paramid]);
+ typeOid = prm->ptype;
+
+ sz = add_size(sz, sizeof(int)); /* space for paramid */
+ sz = add_size(sz, sizeof(Oid)); /* space for type OID */
+
+ /* space for datum/isnull */
+ if (OidIsValid(typeOid))
+ get_typlenbyval(typeOid, &typLen, &typByVal);
+ else
+ {
+ /* If no type OID, assume by-value, like copyParamList does. */
+ typLen = sizeof(Datum);
+ typByVal = true;
+ }
+ sz = add_size(sz,
+ datumEstimateSpace(prm->value, prm->isnull, typByVal, typLen));
+ }
+ return sz;
+}
+
+/*
+ * Serialize ParamExecData params corresponding to initplans.
+ *
+ * We write the number of parameters first, as a 4-byte integer, and then
+ * write details for each parameter in turn. The details for each parameter
+ * consist of a 4-byte paramid (location of param in execution time internal
+ * parameter array), 4-byte type OID, and then the datum as serialized by
+ * datumSerialize().
+ *
+ * The above format is quite similar to the format used to serialize
+ * paramListInfo structure, so if we change either format, then consider to
+ * change at both the places.
+ */
+void
+SerializeInitPlanParams(ParamExecData *paramExecVals, Bitmapset *params,
+ char **start_address)
+{
+ int nparams;
+ int paramid;
+ ParamExecData *prm;
+
+ nparams = bms_num_members(params);
+ memcpy(*start_address, &nparams, sizeof(int));
+ *start_address += sizeof(int);
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ Oid typeOid;
+ int16 typLen;
+ bool typByVal;
+
+ prm = &(paramExecVals[paramid]);
+ typeOid = prm->ptype;
+
+ /* Write paramid. */
+ memcpy(*start_address, ¶mid, sizeof(int));
+ *start_address += sizeof(int);
+
+ /* Write OID. */
+ memcpy(*start_address, &typeOid, sizeof(Oid));
+ *start_address += sizeof(Oid);
+
+ /* space for datum/isnull */
+ if (OidIsValid(typeOid))
+ get_typlenbyval(typeOid, &typLen, &typByVal);
+ else
+ {
+ /* If no type OID, assume by-value, like copyParamList does. */
+ typLen = sizeof(Datum);
+ typByVal = true;
+ }
+ datumSerialize(prm->value, prm->isnull, typByVal, typLen,
+ start_address);
+ }
+}
+
+/*
+ * Restore ParamExecData params corresponding to initplans.
+ */
+void
+RestoreInitPlanParams(char **start_address, ParamExecData *params)
+{
+ int nparams;
+ int i;
+ int paramid;
+
+ memcpy(&nparams, *start_address, sizeof(int));
+ *start_address += sizeof(int);
+
+ for (i = 0; i < nparams; i++)
+ {
+ ParamExecData *prm;
+
+ /* Read paramid */
+ memcpy(¶mid, *start_address, sizeof(int));
+ *start_address += sizeof(int);
+ prm = ¶ms[paramid];
+
+ /* Read type OID. */
+ memcpy(&prm->ptype, *start_address, sizeof(Oid));
+ *start_address += sizeof(Oid);
+
+ /* Read datum/isnull. */
+ prm->value = datumRestore(start_address, &prm->isnull);
+ prm->execPlan = NULL;
+ }
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index c1a83ca..61fe226 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -364,6 +364,7 @@ _copyGather(const Gather *from)
COPY_SCALAR_FIELD(rescan_param);
COPY_SCALAR_FIELD(single_copy);
COPY_SCALAR_FIELD(invisible);
+ COPY_BITMAPSET_FIELD(initParam);
return newnode;
}
@@ -391,6 +392,7 @@ _copyGatherMerge(const GatherMerge *from)
COPY_POINTER_FIELD(sortOperators, from->numCols * sizeof(Oid));
COPY_POINTER_FIELD(collations, from->numCols * sizeof(Oid));
COPY_POINTER_FIELD(nullsFirst, from->numCols * sizeof(bool));
+ COPY_BITMAPSET_FIELD(initParam);
return newnode;
}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 43d6206..c33d83d 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -487,6 +487,7 @@ _outGather(StringInfo str, const Gather *node)
WRITE_INT_FIELD(rescan_param);
WRITE_BOOL_FIELD(single_copy);
WRITE_BOOL_FIELD(invisible);
+ WRITE_BITMAPSET_FIELD(initParam);
}
static void
@@ -517,6 +518,8 @@ _outGatherMerge(StringInfo str, const GatherMerge *node)
appendStringInfoString(str, " :nullsFirst");
for (i = 0; i < node->numCols; i++)
appendStringInfo(str, " %s", booltostr(node->nullsFirst[i]));
+
+ WRITE_BITMAPSET_FIELD(initParam);
}
static void
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index ccb6a1f..2034430 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2172,6 +2172,7 @@ _readGather(void)
READ_INT_FIELD(rescan_param);
READ_BOOL_FIELD(single_copy);
READ_BOOL_FIELD(invisible);
+ READ_BITMAPSET_FIELD(initParam);
READ_DONE();
}
@@ -2193,6 +2194,7 @@ _readGatherMerge(void)
READ_OID_ARRAY(sortOperators, local_node->numCols);
READ_OID_ARRAY(collations, local_node->numCols);
READ_BOOL_ARRAY(nullsFirst, local_node->numCols);
+ READ_BITMAPSET_FIELD(initParam);
READ_DONE();
}
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 4e565b3..98f56fb 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -39,6 +39,7 @@
#include "optimizer/planner.h"
#include "optimizer/prep.h"
#include "optimizer/restrictinfo.h"
+#include "optimizer/subselect.h"
#include "optimizer/tlist.h"
#include "optimizer/var.h"
#include "parser/parse_clause.h"
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index c802d61..a07d80f 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -6270,6 +6270,7 @@ make_gather(List *qptlist,
node->rescan_param = rescan_param;
node->single_copy = single_copy;
node->invisible = false;
+ node->initParam = NULL;
return node;
}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index d58635c..7ba825f 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -376,6 +376,14 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
{
Gather *gather = makeNode(Gather);
+ /*
+ * If there are any initPlans attached to the formerly-top plan node,
+ * move them up to the Gather node; same as we do for Material node in
+ * materialize_finished_plan.
+ */
+ gather->plan.initPlan = top_plan->initPlan;
+ top_plan->initPlan = NIL;
+
gather->plan.targetlist = top_plan->targetlist;
gather->plan.qual = NIL;
gather->plan.lefttree = top_plan;
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index fa9a3f0..ca35f79 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -107,6 +107,7 @@ static Node *fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context);
static bool fix_scan_expr_walker(Node *node, fix_scan_expr_context *context);
static void set_join_references(PlannerInfo *root, Join *join, int rtoffset);
static void set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset);
+static void set_param_references(PlannerInfo *root, Plan *plan);
static Node *convert_combining_aggrefs(Node *node, void *context);
static void set_dummy_tlist_references(Plan *plan, int rtoffset);
static indexed_tlist *build_tlist_index(List *tlist);
@@ -632,7 +633,10 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
case T_Gather:
case T_GatherMerge:
- set_upper_references(root, plan, rtoffset);
+ {
+ set_upper_references(root, plan, rtoffset);
+ set_param_references(root, plan);
+ }
break;
case T_Hash:
@@ -1782,6 +1786,49 @@ set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset)
}
/*
+ * set_param_references
+ * Initialize the initParam list in Gather or Gather merge node such that
+ * it contains reference of all the params that needs to be evaluated
+ * before execution of the node. It contains the initplan params that are
+ * being passed to the plan nodes below it.
+ */
+static void
+set_param_references(PlannerInfo *root, Plan *plan)
+{
+ Assert(IsA(plan, Gather) || IsA(plan, GatherMerge));
+
+ if (plan->lefttree->extParam)
+ {
+ PlannerInfo *proot;
+ Bitmapset *initSetParam = NULL;
+ ListCell *l;
+
+ for (proot = root; proot != NULL; proot = proot->parent_root)
+ {
+ foreach(l, proot->init_plans)
+ {
+ SubPlan *initsubplan = (SubPlan *) lfirst(l);
+ ListCell *l2;
+
+ foreach(l2, initsubplan->setParam)
+ {
+ initSetParam = bms_add_member(initSetParam, lfirst_int(l2));
+ }
+ }
+ }
+
+ /*
+ * Remember the list of all external initplan params that are used by
+ * the children of Gather or Gather merge node.
+ */
+ if (IsA(plan, Gather))
+ ((Gather *) plan)->initParam = bms_intersect(plan->lefttree->extParam, initSetParam);
+ else
+ ((GatherMerge *) plan)->initParam = bms_intersect(plan->lefttree->extParam, initSetParam);
+ }
+}
+
+/*
* Recursively scan an expression tree and convert Aggrefs to the proper
* intermediate form for combining aggregates. This means (1) replacing each
* one's argument list with a single argument that is the original Aggref
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 652843a..a0bea63 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -1087,6 +1087,8 @@ bool
is_parallel_safe(PlannerInfo *root, Node *node)
{
max_parallel_hazard_context context;
+ PlannerInfo *proot;
+ ListCell *l;
/*
* Even if the original querytree contained nothing unsafe, we need to
@@ -1101,6 +1103,25 @@ is_parallel_safe(PlannerInfo *root, Node *node)
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_RESTRICTED;
context.safe_param_ids = NIL;
+
+ /*
+ * The params that refer to the same or parent query level are considered
+ * parallel-safe. The idea is that we compute such params at Gather or
+ * Gather Merge node and pass their value to workers.
+ */
+ for (proot = root; proot != NULL; proot = proot->parent_root)
+ {
+ foreach(l, proot->init_plans)
+ {
+ SubPlan *initsubplan = (SubPlan *) lfirst(l);
+ ListCell *l2;
+
+ foreach(l2, initsubplan->setParam)
+ context.safe_param_ids = lcons_int(lfirst_int(l2),
+ context.safe_param_ids);
+ }
+ }
+
return !max_parallel_hazard_walker(node, &context);
}
@@ -1225,7 +1246,8 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
* We can't pass Params to workers at the moment either, so they are also
* parallel-restricted, unless they are PARAM_EXTERN Params or are
* PARAM_EXEC Params listed in safe_param_ids, meaning they could be
- * generated within the worker.
+ * either generated within the worker or can be computed in master and
+ * then their value can be passed to the worker.
*/
else if (IsA(node, Param))
{
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 78d2247..5bbb63a 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -609,6 +609,7 @@ extern ExprEvalOp ExecEvalStepOp(ExprState *state, ExprEvalStep *op);
*/
extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
+extern void ExecEvalParamExecParams(Bitmapset *params, EState *estate);
extern void ExecEvalParamExtern(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
extern void ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op);
diff --git a/src/include/executor/execParallel.h b/src/include/executor/execParallel.h
index e1b3e7a..88e2757 100644
--- a/src/include/executor/execParallel.h
+++ b/src/include/executor/execParallel.h
@@ -35,7 +35,8 @@ typedef struct ParallelExecutorInfo
} ParallelExecutorInfo;
extern ParallelExecutorInfo *ExecInitParallelPlan(PlanState *planstate,
- EState *estate, int nworkers, int64 tuples_needed);
+ EState *estate, Bitmapset *initParam, int nworkers,
+ int64 tuples_needed);
extern void ExecParallelCreateReaders(ParallelExecutorInfo *pei);
extern void ExecParallelFinish(ParallelExecutorInfo *pei);
extern void ExecParallelCleanup(ParallelExecutorInfo *pei);
diff --git a/src/include/executor/nodeSubplan.h b/src/include/executor/nodeSubplan.h
index 5dbaeeb..40b1a34 100644
--- a/src/include/executor/nodeSubplan.h
+++ b/src/include/executor/nodeSubplan.h
@@ -28,4 +28,10 @@ extern void ExecReScanSetParamPlan(SubPlanState *node, PlanState *parent);
extern void ExecSetParamPlan(SubPlanState *node, ExprContext *econtext);
+extern Size EstimateInitPlanParamsSpace(ParamExecData *paramExecVals, Bitmapset *params);
+
+extern void SerializeInitPlanParams(ParamExecData *paramExecVals, Bitmapset *params, char **start_address);
+
+extern void RestoreInitPlanParams(char **start_address, ParamExecData *params);
+
#endif /* NODESUBPLAN_H */
diff --git a/src/include/nodes/params.h b/src/include/nodes/params.h
index 55219da..e96a1ea 100644
--- a/src/include/nodes/params.h
+++ b/src/include/nodes/params.h
@@ -98,6 +98,16 @@ typedef struct ParamExecData
{
void *execPlan; /* should be "SubPlanState *" */
Datum value;
+
+ /*
+ * parameter's datatype, or 0. This is required so that datum value can
+ * be read and used for other purposes like passing it to worker backend
+ * via shared memory. This is required only for initPlan's evaluation,
+ * however for consistency we set this for Subplan as well. We left it
+ * for other cases like CTE or RecursiveUnion cases where this structure
+ * is not used for evaluation of subplans.
+ */
+ Oid ptype;
bool isnull;
} ParamExecData;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index dd74efa..ff05688 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -841,6 +841,8 @@ typedef struct Gather
int rescan_param; /* ID of Param that signals a rescan, or -1 */
bool single_copy; /* don't execute plan more than once */
bool invisible; /* suppress EXPLAIN display (for testing)? */
+ Bitmapset *initParam; /* param id's of initplans which are referred
+ * at gather or one of it's child node */
} Gather;
/* ------------
@@ -858,6 +860,8 @@ typedef struct GatherMerge
Oid *sortOperators; /* OIDs of operators to sort them by */
Oid *collations; /* OIDs of collations */
bool *nullsFirst; /* NULLS FIRST/LAST directions */
+ Bitmapset *initParam; /* param id's of initplans which are referred
+ * at gather merge or one of it's child node */
} GatherMerge;
/* ----------------
diff --git a/src/test/regress/expected/select_parallel.out b/src/test/regress/expected/select_parallel.out
index ac9ad06..ff0e58d 100644
--- a/src/test/regress/expected/select_parallel.out
+++ b/src/test/regress/expected/select_parallel.out
@@ -159,6 +159,39 @@ explain (costs off)
(4 rows)
alter table tenk2 reset (parallel_workers);
+--test parallel plan for a query containing initplan.
+set enable_indexscan = off;
+set enable_indexonlyscan = off;
+set enable_bitmapscan = off;
+alter table tenk2 set (parallel_workers = 2);
+explain (costs off)
+ select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+ QUERY PLAN
+------------------------------------------------------
+ Aggregate
+ InitPlan 1 (returns $2)
+ -> Finalize Aggregate
+ -> Gather
+ Workers Planned: 2
+ -> Partial Aggregate
+ -> Parallel Seq Scan on tenk2
+ -> Gather
+ Workers Planned: 4
+ Params Evaluated: $2
+ -> Parallel Seq Scan on tenk1
+ Filter: (unique1 = $2)
+(12 rows)
+
+select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+ count
+-------
+ 1
+(1 row)
+
+reset enable_indexscan;
+reset enable_indexonlyscan;
+reset enable_bitmapscan;
+alter table tenk2 reset (parallel_workers);
-- test parallel index scans.
set enable_seqscan to off;
set enable_bitmapscan to off;
diff --git a/src/test/regress/sql/select_parallel.sql b/src/test/regress/sql/select_parallel.sql
index 495f033..c4faafd 100644
--- a/src/test/regress/sql/select_parallel.sql
+++ b/src/test/regress/sql/select_parallel.sql
@@ -58,6 +58,21 @@ explain (costs off)
(select ten from tenk2);
alter table tenk2 reset (parallel_workers);
+--test parallel plan for a query containing initplan.
+set enable_indexscan = off;
+set enable_indexonlyscan = off;
+set enable_bitmapscan = off;
+alter table tenk2 set (parallel_workers = 2);
+
+explain (costs off)
+ select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+
+reset enable_indexscan;
+reset enable_indexonlyscan;
+reset enable_bitmapscan;
+alter table tenk2 reset (parallel_workers);
+
-- test parallel index scans.
set enable_seqscan to off;
set enable_bitmapscan to off;
On Wed, Oct 18, 2017 at 2:06 PM, tushar <tushar.ahuja@enterprisedb.com> wrote:
On 10/11/2017 12:42 PM, tushar wrote:
On 10/09/2017 03:26 PM, Amit Kapila wrote:
I have reverted the check
in the attached patch.I have applied this patch against PG HEAD and run sqlsmith and analyzed
results . didn't find any specific failures against this patch.I did some testing of this feature and written few testcases . PFA the sql
file(along with the expected .out file) .
Thanks a lot Tushar for testing this patch. In the latest patch, I
have just rebased some comments, there is no code change, so I don't
expect any change in behavior, but feel free to test it once again.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Oct 30, 2017 at 9:00 AM, Amit Kapila <amit.kapila16@gmail.com> wrote:
Now that the PARAM_EXTERN issue is fixed, I have rebased this patch.
This patch had been switched to Ready For Committer in last CF, then
Robert had comments which I have addressed, so I think the status
should be switched back to Ready For committer. Let me know if you
think it should be switched to some other status.
The change to ExplainPrintPlan doesn't look good to me, because it
actually moves the initPlan; I don't think it's good for EXPLAIN to
mutate the plan state tree. It should find a way to display the
results *as if* the initPlans were attached to the subnode, but
without actually moving them.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 10/30/2017 09:02 AM, Amit Kapila wrote:
Thanks a lot Tushar for testing this patch. In the latest patch, I
have just rebased some comments, there is no code change, so I don't
expect any change in behavior, but feel free to test it once again.
Thanks Amit. Sure.
--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 10/30/2017 01:36 PM, tushar wrote:
On 10/30/2017 09:02 AM, Amit Kapila wrote:
Thanks a lot Tushar for testing this patch. In the latest patch, I
have just rebased some comments, there is no code change, so I don't
expect any change in behavior, but feel free to test it once again.Thanks Amit. Sure.
I have done re-verification ,Everything looks good.
--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Oct 30, 2017 at 10:07 AM, Robert Haas <robertmhaas@gmail.com> wrote:
On Mon, Oct 30, 2017 at 9:00 AM, Amit Kapila <amit.kapila16@gmail.com> wrote:
Now that the PARAM_EXTERN issue is fixed, I have rebased this patch.
This patch had been switched to Ready For Committer in last CF, then
Robert had comments which I have addressed, so I think the status
should be switched back to Ready For committer. Let me know if you
think it should be switched to some other status.The change to ExplainPrintPlan doesn't look good to me, because it
actually moves the initPlan; I don't think it's good for EXPLAIN to
mutate the plan state tree. It should find a way to display the
results *as if* the initPlans were attached to the subnode, but
without actually moving them.
Actually, with the latest patch, we don't need these changes in
ExplainPrintPlan. Earlier, we need these changes because the patch
had changed SS_charge_for_initplans to mark the path with initplan as
parallel safe. However, after removing that change in the previous
version of patch [1]/messages/by-id/CAA4eK1JD=pJYBn8rN5RimiEVtPJmVNmyq5p6VoZBnUw2xRYB7w@mail.gmail.com -- With Regards, Amit Kapila. EnterpriseDB: http://www.enterprisedb.com, this is not required as now we won't add gather
on top plan node having initplan.
[1]: /messages/by-id/CAA4eK1JD=pJYBn8rN5RimiEVtPJmVNmyq5p6VoZBnUw2xRYB7w@mail.gmail.com -- With Regards, Amit Kapila. EnterpriseDB: http://www.enterprisedb.com
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
pq_pushdown_initplan_v14.patchapplication/octet-stream; name=pq_pushdown_initplan_v14.patchDownload
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 8f7062c..447f69d 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -107,6 +107,7 @@ static void show_tidbitmap_info(BitmapHeapScanState *planstate,
static void show_instrumentation_count(const char *qlabel, int which,
PlanState *planstate, ExplainState *es);
static void show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es);
+static void show_eval_params(Bitmapset *bms_params, ExplainState *es);
static const char *explain_get_index_name(Oid indexId);
static void show_buffer_usage(ExplainState *es, const BufferUsage *usage);
static void ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir,
@@ -1441,6 +1442,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
planstate, es);
ExplainPropertyInteger("Workers Planned",
gather->num_workers, es);
+
+ /* Show params evaluated at gather node */
+ if (gather->initParam)
+ show_eval_params(gather->initParam, es);
+
if (es->analyze)
{
int nworkers;
@@ -1463,6 +1469,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
planstate, es);
ExplainPropertyInteger("Workers Planned",
gm->num_workers, es);
+
+ /* Show params evaluated at gather-merge node */
+ if (gm->initParam)
+ show_eval_params(gm->initParam, es);
+
if (es->analyze)
{
int nworkers;
@@ -2488,6 +2499,29 @@ show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es)
}
/*
+ * Show initplan params evaluated at Gather or Gather Merge node.
+ */
+static void
+show_eval_params(Bitmapset *bms_params, ExplainState *es)
+{
+ int paramid = -1;
+ List *params = NIL;
+
+ Assert(bms_params);
+
+ while ((paramid = bms_next_member(bms_params, paramid)) >= 0)
+ {
+ char param[32];
+
+ snprintf(param, sizeof(param), "$%d", paramid);
+ params = lappend(params, pstrdup(param));
+ }
+
+ if (params)
+ ExplainPropertyList("Params Evaluated", params, es);
+}
+
+/*
* Fetch the name of an index in an EXPLAIN
*
* We allow plugins to get control here so that plans involving hypothetical
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index a0f537b..6c4612d 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -1927,6 +1927,33 @@ ExecEvalParamExec(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
}
/*
+ * ExecEvalParamExecParams
+ *
+ * Execute the subplan stored in PARAM_EXEC initplans params, if not executed
+ * till now.
+ */
+void
+ExecEvalParamExecParams(Bitmapset *params, EState *estate)
+{
+ ParamExecData *prm;
+ int paramid;
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ prm = &(estate->es_param_exec_vals[paramid]);
+
+ if (prm->execPlan != NULL)
+ {
+ /* Parameter not evaluated yet, so go do it */
+ ExecSetParamPlan(prm->execPlan, GetPerTupleExprContext(estate));
+ /* ExecSetParamPlan should have processed this param... */
+ Assert(prm->execPlan == NULL);
+ }
+ }
+}
+
+/*
* Evaluate a PARAM_EXTERN parameter.
*
* PARAM_EXTERN parameters must be sought in ecxt_param_list_info.
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index 1b477ba..a6654d1 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -23,6 +23,7 @@
#include "postgres.h"
+#include "executor/execExpr.h"
#include "executor/execParallel.h"
#include "executor/executor.h"
#include "executor/nodeBitmapHeapscan.h"
@@ -31,6 +32,7 @@
#include "executor/nodeIndexscan.h"
#include "executor/nodeIndexonlyscan.h"
#include "executor/nodeSeqscan.h"
+#include "executor/nodeSubplan.h"
#include "executor/nodeSort.h"
#include "executor/tqueue.h"
#include "nodes/nodeFuncs.h"
@@ -56,6 +58,7 @@
#define PARALLEL_KEY_INSTRUMENTATION UINT64CONST(0xE000000000000006)
#define PARALLEL_KEY_DSA UINT64CONST(0xE000000000000007)
#define PARALLEL_KEY_QUERY_TEXT UINT64CONST(0xE000000000000008)
+#define PARALLEL_KEY_INITPLAN_PARAMS UINT64CONST(0xE000000000000009)
#define PARALLEL_TUPLE_QUEUE_SIZE 65536
@@ -395,7 +398,8 @@ ExecParallelSetupTupleQueues(ParallelContext *pcxt, bool reinitialize)
* execution and return results to the main backend.
*/
ParallelExecutorInfo *
-ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
+ExecInitParallelPlan(PlanState *planstate, EState *estate,
+ Bitmapset *initParam, int nworkers,
int64 tuples_needed)
{
ParallelExecutorInfo *pei;
@@ -406,10 +410,12 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
char *pstmt_data;
char *pstmt_space;
char *param_space;
+ char *initplan_param_space;
BufferUsage *bufusage_space;
SharedExecutorInstrumentation *instrumentation = NULL;
int pstmt_len;
int param_len;
+ int initplan_param_len;
int instrumentation_len = 0;
int instrument_offset = 0;
Size dsa_minsize = dsa_minimum_size();
@@ -421,6 +427,8 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
pei->finished = false;
pei->planstate = planstate;
+ ExecEvalParamExecParams(initParam, estate);
+
/* Fix up and serialize plan to be sent to workers. */
pstmt_data = ExecSerializePlan(planstate->plan, estate);
@@ -454,6 +462,11 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
shm_toc_estimate_chunk(&pcxt->estimator, param_len);
shm_toc_estimate_keys(&pcxt->estimator, 1);
+ /* Estimate space for initplan params. */
+ initplan_param_len = EstimateInitPlanParamsSpace(estate->es_param_exec_vals, initParam);
+ shm_toc_estimate_chunk(&pcxt->estimator, initplan_param_len);
+ shm_toc_estimate_keys(&pcxt->estimator, 1);
+
/*
* Estimate space for BufferUsage.
*
@@ -528,6 +541,11 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
shm_toc_insert(pcxt->toc, PARALLEL_KEY_PARAMS, param_space);
SerializeParamList(estate->es_param_list_info, ¶m_space);
+ /* Store serialized initplan params. */
+ initplan_param_space = shm_toc_allocate(pcxt->toc, initplan_param_len);
+ shm_toc_insert(pcxt->toc, PARALLEL_KEY_INITPLAN_PARAMS, initplan_param_space);
+ SerializeInitPlanParams(estate->es_param_exec_vals, initParam, &initplan_param_space);
+
/* Allocate space for each worker's BufferUsage; no need to initialize. */
bufusage_space = shm_toc_allocate(pcxt->toc,
mul_size(sizeof(BufferUsage), pcxt->nworkers));
@@ -862,6 +880,21 @@ ExecParallelGetReceiver(dsm_segment *seg, shm_toc *toc)
}
/*
+ * Copy the ParamExecData params corresponding to initplans from dynamic
+ * shared memory. This has to be done once the params are allocated by
+ * executor; that is after ExecutorStart().
+ */
+static void
+ExecParallelInitializeInitPlanParams(shm_toc *toc, ParamExecData *params)
+{
+ char *paramspace;
+
+ /* Reconstruct initplan params. */
+ paramspace = shm_toc_lookup(toc, PARALLEL_KEY_INITPLAN_PARAMS, false);
+ RestoreInitPlanParams(¶mspace, params);
+}
+
+/*
* Create a QueryDesc for the PlannedStmt we are to execute, and return it.
*/
static QueryDesc *
@@ -1046,6 +1079,7 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
/* Special executor initialization steps for parallel workers */
queryDesc->planstate->state->es_query_dsa = area;
+ ExecParallelInitializeInitPlanParams(toc, queryDesc->estate->es_param_exec_vals);
ExecParallelInitializeWorker(queryDesc->planstate, toc);
/* Pass down any tuple bound */
diff --git a/src/backend/executor/nodeGather.c b/src/backend/executor/nodeGather.c
index 639f4f5..eb9949f 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -158,6 +158,7 @@ ExecGather(PlanState *pstate)
if (!node->pei)
node->pei = ExecInitParallelPlan(node->ps.lefttree,
estate,
+ gather->initParam,
gather->num_workers,
node->tuples_needed);
else
diff --git a/src/backend/executor/nodeGatherMerge.c b/src/backend/executor/nodeGatherMerge.c
index 5625b12..76e07cc 100644
--- a/src/backend/executor/nodeGatherMerge.c
+++ b/src/backend/executor/nodeGatherMerge.c
@@ -202,6 +202,7 @@ ExecGatherMerge(PlanState *pstate)
if (!node->pei)
node->pei = ExecInitParallelPlan(node->ps.lefttree,
estate,
+ gm->initParam,
gm->num_workers,
node->tuples_needed);
else
diff --git a/src/backend/executor/nodeNestloop.c b/src/backend/executor/nodeNestloop.c
index 4447b7c..52514e4 100644
--- a/src/backend/executor/nodeNestloop.c
+++ b/src/backend/executor/nodeNestloop.c
@@ -130,6 +130,7 @@ ExecNestLoop(PlanState *pstate)
{
NestLoopParam *nlp = (NestLoopParam *) lfirst(lc);
int paramno = nlp->paramno;
+ TupleDesc tdesc = outerTupleSlot->tts_tupleDescriptor;
ParamExecData *prm;
prm = &(econtext->ecxt_param_exec_vals[paramno]);
@@ -140,6 +141,7 @@ ExecNestLoop(PlanState *pstate)
prm->value = slot_getattr(outerTupleSlot,
nlp->paramval->varattno,
&(prm->isnull));
+ prm->ptype = TupleDescAttr(tdesc, nlp->paramval->varattno - 1)->atttypid;
/* Flag parameter value as changed */
innerPlan->chgParam = bms_add_member(innerPlan->chgParam,
paramno);
diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c
index 77ef6f3..06409ed 100644
--- a/src/backend/executor/nodeSubplan.c
+++ b/src/backend/executor/nodeSubplan.c
@@ -30,12 +30,16 @@
#include <math.h>
#include "access/htup_details.h"
+#include "catalog/pg_type.h"
#include "executor/executor.h"
#include "executor/nodeSubplan.h"
#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
#include "miscadmin.h"
#include "optimizer/clauses.h"
+#include "storage/shmem.h"
#include "utils/array.h"
+#include "utils/datum.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
@@ -273,11 +277,13 @@ ExecScanSubPlan(SubPlanState *node,
forboth(l, subplan->parParam, pvar, node->args)
{
int paramid = lfirst_int(l);
+ ExprState *exprstate = (ExprState *) lfirst(pvar);
ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]);
- prm->value = ExecEvalExprSwitchContext((ExprState *) lfirst(pvar),
+ prm->value = ExecEvalExprSwitchContext(exprstate,
econtext,
&(prm->isnull));
+ prm->ptype = exprType((Node *) exprstate->expr);
planstate->chgParam = bms_add_member(planstate->chgParam, paramid);
}
@@ -390,6 +396,7 @@ ExecScanSubPlan(SubPlanState *node,
prmdata = &(econtext->ecxt_param_exec_vals[paramid]);
Assert(prmdata->execPlan == NULL);
prmdata->value = slot_getattr(slot, col, &(prmdata->isnull));
+ prmdata->ptype = TupleDescAttr(tdesc, col - 1)->atttypid;
col++;
}
@@ -556,11 +563,13 @@ buildSubPlanHash(SubPlanState *node, ExprContext *econtext)
{
int paramid = lfirst_int(plst);
ParamExecData *prmdata;
+ TupleDesc tdesc = slot->tts_tupleDescriptor;
prmdata = &(innerecontext->ecxt_param_exec_vals[paramid]);
Assert(prmdata->execPlan == NULL);
prmdata->value = slot_getattr(slot, col,
&(prmdata->isnull));
+ prmdata->ptype = TupleDescAttr(tdesc, col - 1)->atttypid;
col++;
}
slot = ExecProject(node->projRight);
@@ -924,6 +933,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
ListCell *l;
bool found = false;
ArrayBuildStateAny *astate = NULL;
+ Oid ptype;
if (subLinkType == ANY_SUBLINK ||
subLinkType == ALL_SUBLINK)
@@ -931,6 +941,8 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
if (subLinkType == CTE_SUBLINK)
elog(ERROR, "CTE subplans should not be executed via ExecSetParamPlan");
+ ptype = exprType((Node *) node->subplan);
+
/* Initialize ArrayBuildStateAny in caller's context, if needed */
if (subLinkType == ARRAY_SUBLINK)
astate = initArrayResultAny(subplan->firstColType,
@@ -953,11 +965,13 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
forboth(l, subplan->parParam, pvar, node->args)
{
int paramid = lfirst_int(l);
+ ExprState *exprstate = (ExprState *) lfirst(pvar);
ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]);
- prm->value = ExecEvalExprSwitchContext((ExprState *) lfirst(pvar),
+ prm->value = ExecEvalExprSwitchContext(exprstate,
econtext,
&(prm->isnull));
+ prm->ptype = exprType((Node *) exprstate->expr);
planstate->chgParam = bms_add_member(planstate->chgParam, paramid);
}
@@ -980,6 +994,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = BoolGetDatum(true);
+ prm->ptype = ptype;
prm->isnull = false;
found = true;
break;
@@ -1031,6 +1046,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = heap_getattr(node->curTuple, i, tdesc,
&(prm->isnull));
+ prm->ptype = TupleDescAttr(tdesc, i - 1)->atttypid;
i++;
}
}
@@ -1053,6 +1069,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
true);
prm->execPlan = NULL;
prm->value = node->curArray;
+ prm->ptype = ptype;
prm->isnull = false;
}
else if (!found)
@@ -1065,6 +1082,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = BoolGetDatum(false);
+ prm->ptype = ptype;
prm->isnull = false;
}
else
@@ -1077,6 +1095,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
prm->execPlan = NULL;
prm->value = (Datum) 0;
+ prm->ptype = VOIDOID;
prm->isnull = true;
}
}
@@ -1207,3 +1226,133 @@ ExecAlternativeSubPlan(AlternativeSubPlanState *node,
return ExecSubPlan(activesp, econtext, isNull);
}
+
+/*
+ * Estimate the amount of space required to serialize the InitPlan params.
+ */
+Size
+EstimateInitPlanParamsSpace(ParamExecData *paramExecVals, Bitmapset *params)
+{
+ int paramid;
+ Size sz = sizeof(int);
+ ParamExecData *prm;
+
+ if (params == NULL)
+ return sz;
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ Oid typeOid;
+ int16 typLen;
+ bool typByVal;
+
+ prm = &(paramExecVals[paramid]);
+ typeOid = prm->ptype;
+
+ sz = add_size(sz, sizeof(int)); /* space for paramid */
+ sz = add_size(sz, sizeof(Oid)); /* space for type OID */
+
+ /* space for datum/isnull */
+ if (OidIsValid(typeOid))
+ get_typlenbyval(typeOid, &typLen, &typByVal);
+ else
+ {
+ /* If no type OID, assume by-value, like copyParamList does. */
+ typLen = sizeof(Datum);
+ typByVal = true;
+ }
+ sz = add_size(sz,
+ datumEstimateSpace(prm->value, prm->isnull, typByVal, typLen));
+ }
+ return sz;
+}
+
+/*
+ * Serialize ParamExecData params corresponding to initplans.
+ *
+ * We write the number of parameters first, as a 4-byte integer, and then
+ * write details for each parameter in turn. The details for each parameter
+ * consist of a 4-byte paramid (location of param in execution time internal
+ * parameter array), 4-byte type OID, and then the datum as serialized by
+ * datumSerialize().
+ *
+ * The above format is quite similar to the format used to serialize
+ * paramListInfo structure, so if we change either format, then consider to
+ * change at both the places.
+ */
+void
+SerializeInitPlanParams(ParamExecData *paramExecVals, Bitmapset *params,
+ char **start_address)
+{
+ int nparams;
+ int paramid;
+ ParamExecData *prm;
+
+ nparams = bms_num_members(params);
+ memcpy(*start_address, &nparams, sizeof(int));
+ *start_address += sizeof(int);
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ Oid typeOid;
+ int16 typLen;
+ bool typByVal;
+
+ prm = &(paramExecVals[paramid]);
+ typeOid = prm->ptype;
+
+ /* Write paramid. */
+ memcpy(*start_address, ¶mid, sizeof(int));
+ *start_address += sizeof(int);
+
+ /* Write OID. */
+ memcpy(*start_address, &typeOid, sizeof(Oid));
+ *start_address += sizeof(Oid);
+
+ /* space for datum/isnull */
+ if (OidIsValid(typeOid))
+ get_typlenbyval(typeOid, &typLen, &typByVal);
+ else
+ {
+ /* If no type OID, assume by-value, like copyParamList does. */
+ typLen = sizeof(Datum);
+ typByVal = true;
+ }
+ datumSerialize(prm->value, prm->isnull, typByVal, typLen,
+ start_address);
+ }
+}
+
+/*
+ * Restore ParamExecData params corresponding to initplans.
+ */
+void
+RestoreInitPlanParams(char **start_address, ParamExecData *params)
+{
+ int nparams;
+ int i;
+ int paramid;
+
+ memcpy(&nparams, *start_address, sizeof(int));
+ *start_address += sizeof(int);
+
+ for (i = 0; i < nparams; i++)
+ {
+ ParamExecData *prm;
+
+ /* Read paramid */
+ memcpy(¶mid, *start_address, sizeof(int));
+ *start_address += sizeof(int);
+ prm = ¶ms[paramid];
+
+ /* Read type OID. */
+ memcpy(&prm->ptype, *start_address, sizeof(Oid));
+ *start_address += sizeof(Oid);
+
+ /* Read datum/isnull. */
+ prm->value = datumRestore(start_address, &prm->isnull);
+ prm->execPlan = NULL;
+ }
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index c1a83ca..61fe226 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -364,6 +364,7 @@ _copyGather(const Gather *from)
COPY_SCALAR_FIELD(rescan_param);
COPY_SCALAR_FIELD(single_copy);
COPY_SCALAR_FIELD(invisible);
+ COPY_BITMAPSET_FIELD(initParam);
return newnode;
}
@@ -391,6 +392,7 @@ _copyGatherMerge(const GatherMerge *from)
COPY_POINTER_FIELD(sortOperators, from->numCols * sizeof(Oid));
COPY_POINTER_FIELD(collations, from->numCols * sizeof(Oid));
COPY_POINTER_FIELD(nullsFirst, from->numCols * sizeof(bool));
+ COPY_BITMAPSET_FIELD(initParam);
return newnode;
}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 43d6206..c33d83d 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -487,6 +487,7 @@ _outGather(StringInfo str, const Gather *node)
WRITE_INT_FIELD(rescan_param);
WRITE_BOOL_FIELD(single_copy);
WRITE_BOOL_FIELD(invisible);
+ WRITE_BITMAPSET_FIELD(initParam);
}
static void
@@ -517,6 +518,8 @@ _outGatherMerge(StringInfo str, const GatherMerge *node)
appendStringInfoString(str, " :nullsFirst");
for (i = 0; i < node->numCols; i++)
appendStringInfo(str, " %s", booltostr(node->nullsFirst[i]));
+
+ WRITE_BITMAPSET_FIELD(initParam);
}
static void
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index ccb6a1f..2034430 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2172,6 +2172,7 @@ _readGather(void)
READ_INT_FIELD(rescan_param);
READ_BOOL_FIELD(single_copy);
READ_BOOL_FIELD(invisible);
+ READ_BITMAPSET_FIELD(initParam);
READ_DONE();
}
@@ -2193,6 +2194,7 @@ _readGatherMerge(void)
READ_OID_ARRAY(sortOperators, local_node->numCols);
READ_OID_ARRAY(collations, local_node->numCols);
READ_BOOL_ARRAY(nullsFirst, local_node->numCols);
+ READ_BITMAPSET_FIELD(initParam);
READ_DONE();
}
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index a6efb4e..39da757 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -39,6 +39,7 @@
#include "optimizer/planner.h"
#include "optimizer/prep.h"
#include "optimizer/restrictinfo.h"
+#include "optimizer/subselect.h"
#include "optimizer/tlist.h"
#include "optimizer/var.h"
#include "parser/parse_clause.h"
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 4b49748..e91524c 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -6279,6 +6279,7 @@ make_gather(List *qptlist,
node->rescan_param = rescan_param;
node->single_copy = single_copy;
node->invisible = false;
+ node->initParam = NULL;
return node;
}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index d58635c..7ba825f 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -376,6 +376,14 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
{
Gather *gather = makeNode(Gather);
+ /*
+ * If there are any initPlans attached to the formerly-top plan node,
+ * move them up to the Gather node; same as we do for Material node in
+ * materialize_finished_plan.
+ */
+ gather->plan.initPlan = top_plan->initPlan;
+ top_plan->initPlan = NIL;
+
gather->plan.targetlist = top_plan->targetlist;
gather->plan.qual = NIL;
gather->plan.lefttree = top_plan;
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index fa9a3f0..ca35f79 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -107,6 +107,7 @@ static Node *fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context);
static bool fix_scan_expr_walker(Node *node, fix_scan_expr_context *context);
static void set_join_references(PlannerInfo *root, Join *join, int rtoffset);
static void set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset);
+static void set_param_references(PlannerInfo *root, Plan *plan);
static Node *convert_combining_aggrefs(Node *node, void *context);
static void set_dummy_tlist_references(Plan *plan, int rtoffset);
static indexed_tlist *build_tlist_index(List *tlist);
@@ -632,7 +633,10 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
case T_Gather:
case T_GatherMerge:
- set_upper_references(root, plan, rtoffset);
+ {
+ set_upper_references(root, plan, rtoffset);
+ set_param_references(root, plan);
+ }
break;
case T_Hash:
@@ -1782,6 +1786,49 @@ set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset)
}
/*
+ * set_param_references
+ * Initialize the initParam list in Gather or Gather merge node such that
+ * it contains reference of all the params that needs to be evaluated
+ * before execution of the node. It contains the initplan params that are
+ * being passed to the plan nodes below it.
+ */
+static void
+set_param_references(PlannerInfo *root, Plan *plan)
+{
+ Assert(IsA(plan, Gather) || IsA(plan, GatherMerge));
+
+ if (plan->lefttree->extParam)
+ {
+ PlannerInfo *proot;
+ Bitmapset *initSetParam = NULL;
+ ListCell *l;
+
+ for (proot = root; proot != NULL; proot = proot->parent_root)
+ {
+ foreach(l, proot->init_plans)
+ {
+ SubPlan *initsubplan = (SubPlan *) lfirst(l);
+ ListCell *l2;
+
+ foreach(l2, initsubplan->setParam)
+ {
+ initSetParam = bms_add_member(initSetParam, lfirst_int(l2));
+ }
+ }
+ }
+
+ /*
+ * Remember the list of all external initplan params that are used by
+ * the children of Gather or Gather merge node.
+ */
+ if (IsA(plan, Gather))
+ ((Gather *) plan)->initParam = bms_intersect(plan->lefttree->extParam, initSetParam);
+ else
+ ((GatherMerge *) plan)->initParam = bms_intersect(plan->lefttree->extParam, initSetParam);
+ }
+}
+
+/*
* Recursively scan an expression tree and convert Aggrefs to the proper
* intermediate form for combining aggregates. This means (1) replacing each
* one's argument list with a single argument that is the original Aggref
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 652843a..a0bea63 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -1087,6 +1087,8 @@ bool
is_parallel_safe(PlannerInfo *root, Node *node)
{
max_parallel_hazard_context context;
+ PlannerInfo *proot;
+ ListCell *l;
/*
* Even if the original querytree contained nothing unsafe, we need to
@@ -1101,6 +1103,25 @@ is_parallel_safe(PlannerInfo *root, Node *node)
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_RESTRICTED;
context.safe_param_ids = NIL;
+
+ /*
+ * The params that refer to the same or parent query level are considered
+ * parallel-safe. The idea is that we compute such params at Gather or
+ * Gather Merge node and pass their value to workers.
+ */
+ for (proot = root; proot != NULL; proot = proot->parent_root)
+ {
+ foreach(l, proot->init_plans)
+ {
+ SubPlan *initsubplan = (SubPlan *) lfirst(l);
+ ListCell *l2;
+
+ foreach(l2, initsubplan->setParam)
+ context.safe_param_ids = lcons_int(lfirst_int(l2),
+ context.safe_param_ids);
+ }
+ }
+
return !max_parallel_hazard_walker(node, &context);
}
@@ -1225,7 +1246,8 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
* We can't pass Params to workers at the moment either, so they are also
* parallel-restricted, unless they are PARAM_EXTERN Params or are
* PARAM_EXEC Params listed in safe_param_ids, meaning they could be
- * generated within the worker.
+ * either generated within the worker or can be computed in master and
+ * then their value can be passed to the worker.
*/
else if (IsA(node, Param))
{
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 78d2247..5bbb63a 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -609,6 +609,7 @@ extern ExprEvalOp ExecEvalStepOp(ExprState *state, ExprEvalStep *op);
*/
extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
+extern void ExecEvalParamExecParams(Bitmapset *params, EState *estate);
extern void ExecEvalParamExtern(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
extern void ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op);
diff --git a/src/include/executor/execParallel.h b/src/include/executor/execParallel.h
index e1b3e7a..88e2757 100644
--- a/src/include/executor/execParallel.h
+++ b/src/include/executor/execParallel.h
@@ -35,7 +35,8 @@ typedef struct ParallelExecutorInfo
} ParallelExecutorInfo;
extern ParallelExecutorInfo *ExecInitParallelPlan(PlanState *planstate,
- EState *estate, int nworkers, int64 tuples_needed);
+ EState *estate, Bitmapset *initParam, int nworkers,
+ int64 tuples_needed);
extern void ExecParallelCreateReaders(ParallelExecutorInfo *pei);
extern void ExecParallelFinish(ParallelExecutorInfo *pei);
extern void ExecParallelCleanup(ParallelExecutorInfo *pei);
diff --git a/src/include/executor/nodeSubplan.h b/src/include/executor/nodeSubplan.h
index 5dbaeeb..40b1a34 100644
--- a/src/include/executor/nodeSubplan.h
+++ b/src/include/executor/nodeSubplan.h
@@ -28,4 +28,10 @@ extern void ExecReScanSetParamPlan(SubPlanState *node, PlanState *parent);
extern void ExecSetParamPlan(SubPlanState *node, ExprContext *econtext);
+extern Size EstimateInitPlanParamsSpace(ParamExecData *paramExecVals, Bitmapset *params);
+
+extern void SerializeInitPlanParams(ParamExecData *paramExecVals, Bitmapset *params, char **start_address);
+
+extern void RestoreInitPlanParams(char **start_address, ParamExecData *params);
+
#endif /* NODESUBPLAN_H */
diff --git a/src/include/nodes/params.h b/src/include/nodes/params.h
index 55219da..e96a1ea 100644
--- a/src/include/nodes/params.h
+++ b/src/include/nodes/params.h
@@ -98,6 +98,16 @@ typedef struct ParamExecData
{
void *execPlan; /* should be "SubPlanState *" */
Datum value;
+
+ /*
+ * parameter's datatype, or 0. This is required so that datum value can
+ * be read and used for other purposes like passing it to worker backend
+ * via shared memory. This is required only for initPlan's evaluation,
+ * however for consistency we set this for Subplan as well. We left it
+ * for other cases like CTE or RecursiveUnion cases where this structure
+ * is not used for evaluation of subplans.
+ */
+ Oid ptype;
bool isnull;
} ParamExecData;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index dd74efa..ff05688 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -841,6 +841,8 @@ typedef struct Gather
int rescan_param; /* ID of Param that signals a rescan, or -1 */
bool single_copy; /* don't execute plan more than once */
bool invisible; /* suppress EXPLAIN display (for testing)? */
+ Bitmapset *initParam; /* param id's of initplans which are referred
+ * at gather or one of it's child node */
} Gather;
/* ------------
@@ -858,6 +860,8 @@ typedef struct GatherMerge
Oid *sortOperators; /* OIDs of operators to sort them by */
Oid *collations; /* OIDs of collations */
bool *nullsFirst; /* NULLS FIRST/LAST directions */
+ Bitmapset *initParam; /* param id's of initplans which are referred
+ * at gather merge or one of it's child node */
} GatherMerge;
/* ----------------
diff --git a/src/test/regress/expected/select_parallel.out b/src/test/regress/expected/select_parallel.out
index ac9ad06..ff0e58d 100644
--- a/src/test/regress/expected/select_parallel.out
+++ b/src/test/regress/expected/select_parallel.out
@@ -159,6 +159,39 @@ explain (costs off)
(4 rows)
alter table tenk2 reset (parallel_workers);
+--test parallel plan for a query containing initplan.
+set enable_indexscan = off;
+set enable_indexonlyscan = off;
+set enable_bitmapscan = off;
+alter table tenk2 set (parallel_workers = 2);
+explain (costs off)
+ select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+ QUERY PLAN
+------------------------------------------------------
+ Aggregate
+ InitPlan 1 (returns $2)
+ -> Finalize Aggregate
+ -> Gather
+ Workers Planned: 2
+ -> Partial Aggregate
+ -> Parallel Seq Scan on tenk2
+ -> Gather
+ Workers Planned: 4
+ Params Evaluated: $2
+ -> Parallel Seq Scan on tenk1
+ Filter: (unique1 = $2)
+(12 rows)
+
+select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+ count
+-------
+ 1
+(1 row)
+
+reset enable_indexscan;
+reset enable_indexonlyscan;
+reset enable_bitmapscan;
+alter table tenk2 reset (parallel_workers);
-- test parallel index scans.
set enable_seqscan to off;
set enable_bitmapscan to off;
diff --git a/src/test/regress/sql/select_parallel.sql b/src/test/regress/sql/select_parallel.sql
index 495f033..c4faafd 100644
--- a/src/test/regress/sql/select_parallel.sql
+++ b/src/test/regress/sql/select_parallel.sql
@@ -58,6 +58,21 @@ explain (costs off)
(select ten from tenk2);
alter table tenk2 reset (parallel_workers);
+--test parallel plan for a query containing initplan.
+set enable_indexscan = off;
+set enable_indexonlyscan = off;
+set enable_bitmapscan = off;
+alter table tenk2 set (parallel_workers = 2);
+
+explain (costs off)
+ select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+select count(*) from tenk1 where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+
+reset enable_indexscan;
+reset enable_indexonlyscan;
+reset enable_bitmapscan;
+alter table tenk2 reset (parallel_workers);
+
-- test parallel index scans.
set enable_seqscan to off;
set enable_bitmapscan to off;
On Mon, Oct 30, 2017 at 9:00 AM, Amit Kapila <amit.kapila16@gmail.com> wrote:
On Wed, Oct 11, 2017 at 9:24 PM, Robert Haas <robertmhaas@gmail.com> wrote:
On Mon, Oct 9, 2017 at 5:56 AM, Amit Kapila <amit.kapila16@gmail.com> wrote:
How about always returning false for PARAM_EXTERN?
Yeah, I think that's what we should do. Let's do that first as a
separate patch, which we might even want to back-patch, then return to
this.Now that the PARAM_EXTERN issue is fixed, I have rebased this patch.
This patch had been switched to Ready For Committer in last CF, then
Robert had comments which I have addressed, so I think the status
should be switched back to Ready For committer.
As mentioned, changed the status of the patch in CF app.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Nov 7, 2017 at 4:45 AM, Amit Kapila <amit.kapila16@gmail.com> wrote:
As mentioned, changed the status of the patch in CF app.
I spent some time reviewing this patch today and found myself still
quite uncomfortable with the fact that it was adding execution-time
work to track the types of parameters - types that would usually not
even be used. I found the changes to nodeNestLoop.c to be
particularly objectionable, because we could end up doing the work
over and over when it is actually not needed at all, or at most once.
I decided to try instead teaching the planner to keep track of the
types of PARAM_EXEC parameters as they were created, and that seems to
work fine. See 0001, attached.
0002, attached, is my worked-over version of the rest of the patch. I
moved the code that serializes and deserializes PARAM_EXEC from
nodeSubplan.c -- which seemed like a strange choice - to
execParallel.c. I removed the type OID from the serialization format
because there's no reason to do that any more; the worker already
knows the types from the plan. I did some renaming of the functions
involved and some adjustment of the comments to refer to "PARAM_EXEC
parameters" instead of initPlan parameters, because there's no reason
that I can see why this can only work for initPlans. A Gather node on
the inner side of a nested loop doesn't sound like a great idea, but I
think this infrastructure could handle it (though it would need some
more planner work). I broke a lot of long lines in your version of
the patch into multiple lines; please try to be attentive to this
issue when writing patches in general, as it is a bit tedious to go
through and insert line breaks in many places.
Please let me know your thoughts on the attached patches.
Thanks,
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Attachments:
0001-param-exec-types-v1.patchapplication/octet-stream; name=0001-param-exec-types-v1.patchDownload
From c07f90e7803b43f23fe6091100c8e19df79fc0e3 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Fri, 10 Nov 2017 12:28:34 -0500
Subject: [PATCH 1/2] param-exec-types-v1
---
src/backend/executor/execMain.c | 20 +++++++++++++------
src/backend/executor/execParallel.c | 2 +-
src/backend/nodes/copyfuncs.c | 2 +-
src/backend/nodes/outfuncs.c | 4 ++--
src/backend/nodes/readfuncs.c | 2 +-
src/backend/optimizer/plan/planner.c | 6 +++---
src/backend/optimizer/plan/subselect.c | 35 +++++++++++++++++++++++++---------
src/backend/optimizer/util/clauses.c | 2 +-
src/include/nodes/plannodes.h | 2 +-
src/include/nodes/relation.h | 6 +++---
10 files changed, 53 insertions(+), 28 deletions(-)
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 493ff82775..47f2131642 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -195,9 +195,14 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
*/
estate->es_param_list_info = queryDesc->params;
- if (queryDesc->plannedstmt->nParamExec > 0)
+ if (queryDesc->plannedstmt->paramExecTypes != NIL)
+ {
+ int nParamExec;
+
+ nParamExec = list_length(queryDesc->plannedstmt->paramExecTypes);
estate->es_param_exec_vals = (ParamExecData *)
- palloc0(queryDesc->plannedstmt->nParamExec * sizeof(ParamExecData));
+ palloc0(nParamExec * sizeof(ParamExecData));
+ }
estate->es_sourceText = queryDesc->sourceText;
@@ -3032,9 +3037,11 @@ EvalPlanQualBegin(EPQState *epqstate, EState *parentestate)
MemSet(estate->es_epqScanDone, 0, rtsize * sizeof(bool));
/* Recopy current values of parent parameters */
- if (parentestate->es_plannedstmt->nParamExec > 0)
+ if (parentestate->es_plannedstmt->paramExecTypes != NIL)
{
- int i = parentestate->es_plannedstmt->nParamExec;
+ int i;
+
+ i = list_length(parentestate->es_plannedstmt->paramExecTypes);
while (--i >= 0)
{
@@ -3122,10 +3129,11 @@ EvalPlanQualStart(EPQState *epqstate, EState *parentestate, Plan *planTree)
* already set from other parts of the parent's plan tree.
*/
estate->es_param_list_info = parentestate->es_param_list_info;
- if (parentestate->es_plannedstmt->nParamExec > 0)
+ if (parentestate->es_plannedstmt->paramExecTypes != NIL)
{
- int i = parentestate->es_plannedstmt->nParamExec;
+ int i;
+ i = list_length(parentestate->es_plannedstmt->paramExecTypes);
estate->es_param_exec_vals = (ParamExecData *)
palloc0(i * sizeof(ParamExecData));
while (--i >= 0)
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index 1b477baecb..fd7e7cbf3d 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -195,7 +195,7 @@ ExecSerializePlan(Plan *plan, EState *estate)
pstmt->rowMarks = NIL;
pstmt->relationOids = NIL;
pstmt->invalItems = NIL; /* workers can't replan anyway... */
- pstmt->nParamExec = estate->es_plannedstmt->nParamExec;
+ pstmt->paramExecTypes = estate->es_plannedstmt->paramExecTypes;
pstmt->utilityStmt = NULL;
pstmt->stmt_location = -1;
pstmt->stmt_len = -1;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index cadd253ef1..76e75459b4 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -97,7 +97,7 @@ _copyPlannedStmt(const PlannedStmt *from)
COPY_NODE_FIELD(rowMarks);
COPY_NODE_FIELD(relationOids);
COPY_NODE_FIELD(invalItems);
- COPY_SCALAR_FIELD(nParamExec);
+ COPY_NODE_FIELD(paramExecTypes);
COPY_NODE_FIELD(utilityStmt);
COPY_LOCATION_FIELD(stmt_location);
COPY_LOCATION_FIELD(stmt_len);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 291d1eeb46..dc35df9e4f 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -282,7 +282,7 @@ _outPlannedStmt(StringInfo str, const PlannedStmt *node)
WRITE_NODE_FIELD(rowMarks);
WRITE_NODE_FIELD(relationOids);
WRITE_NODE_FIELD(invalItems);
- WRITE_INT_FIELD(nParamExec);
+ WRITE_NODE_FIELD(paramExecTypes);
WRITE_NODE_FIELD(utilityStmt);
WRITE_LOCATION_FIELD(stmt_location);
WRITE_LOCATION_FIELD(stmt_len);
@@ -2181,7 +2181,7 @@ _outPlannerGlobal(StringInfo str, const PlannerGlobal *node)
WRITE_NODE_FIELD(rootResultRelations);
WRITE_NODE_FIELD(relationOids);
WRITE_NODE_FIELD(invalItems);
- WRITE_INT_FIELD(nParamExec);
+ WRITE_NODE_FIELD(paramExecTypes);
WRITE_UINT_FIELD(lastPHId);
WRITE_UINT_FIELD(lastRowMarkId);
WRITE_INT_FIELD(lastPlanNodeId);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 42c595dc03..593658dd8a 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1480,7 +1480,7 @@ _readPlannedStmt(void)
READ_NODE_FIELD(rowMarks);
READ_NODE_FIELD(relationOids);
READ_NODE_FIELD(invalItems);
- READ_INT_FIELD(nParamExec);
+ READ_NODE_FIELD(paramExecTypes);
READ_NODE_FIELD(utilityStmt);
READ_LOCATION_FIELD(stmt_location);
READ_LOCATION_FIELD(stmt_len);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 9b7a8fd82c..607f7cd251 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -243,7 +243,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
glob->rootResultRelations = NIL;
glob->relationOids = NIL;
glob->invalItems = NIL;
- glob->nParamExec = 0;
+ glob->paramExecTypes = NIL;
glob->lastPHId = 0;
glob->lastRowMarkId = 0;
glob->lastPlanNodeId = 0;
@@ -415,7 +415,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
* set_plan_references' tree traversal, but for now it has to be separate
* because we need to visit subplans before not after main plan.
*/
- if (glob->nParamExec > 0)
+ if (glob->paramExecTypes != NIL)
{
Assert(list_length(glob->subplans) == list_length(glob->subroots));
forboth(lp, glob->subplans, lr, glob->subroots)
@@ -466,7 +466,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
result->rowMarks = glob->finalrowmarks;
result->relationOids = glob->relationOids;
result->invalItems = glob->invalItems;
- result->nParamExec = glob->nParamExec;
+ result->paramExecTypes = glob->paramExecTypes;
/* utilityStmt should be null, but we might as well copy it */
result->utilityStmt = parse->utilityStmt;
result->stmt_location = parse->stmt_location;
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 8f75fa98ed..1aad0d9503 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -131,7 +131,9 @@ assign_param_for_var(PlannerInfo *root, Var *var)
pitem = makeNode(PlannerParamItem);
pitem->item = (Node *) var;
- pitem->paramId = root->glob->nParamExec++;
+ pitem->paramId = list_length(root->glob->paramExecTypes);
+ root->glob->paramExecTypes = lappend_oid(root->glob->paramExecTypes,
+ var->vartype);
root->plan_params = lappend(root->plan_params, pitem);
@@ -234,7 +236,9 @@ assign_param_for_placeholdervar(PlannerInfo *root, PlaceHolderVar *phv)
pitem = makeNode(PlannerParamItem);
pitem->item = (Node *) phv;
- pitem->paramId = root->glob->nParamExec++;
+ pitem->paramId = list_length(root->glob->paramExecTypes);
+ root->glob->paramExecTypes = lappend_oid(root->glob->paramExecTypes,
+ exprType((Node *) phv->phexpr));
root->plan_params = lappend(root->plan_params, pitem);
@@ -323,7 +327,9 @@ replace_outer_agg(PlannerInfo *root, Aggref *agg)
pitem = makeNode(PlannerParamItem);
pitem->item = (Node *) agg;
- pitem->paramId = root->glob->nParamExec++;
+ pitem->paramId = list_length(root->glob->paramExecTypes);
+ root->glob->paramExecTypes = lappend_oid(root->glob->paramExecTypes,
+ agg->aggtype);
root->plan_params = lappend(root->plan_params, pitem);
@@ -348,6 +354,7 @@ replace_outer_grouping(PlannerInfo *root, GroupingFunc *grp)
Param *retval;
PlannerParamItem *pitem;
Index levelsup;
+ Oid ptype;
Assert(grp->agglevelsup > 0 && grp->agglevelsup < root->query_level);
@@ -362,17 +369,20 @@ replace_outer_grouping(PlannerInfo *root, GroupingFunc *grp)
grp = copyObject(grp);
IncrementVarSublevelsUp((Node *) grp, -((int) grp->agglevelsup), 0);
Assert(grp->agglevelsup == 0);
+ ptype = exprType((Node *) grp);
pitem = makeNode(PlannerParamItem);
pitem->item = (Node *) grp;
- pitem->paramId = root->glob->nParamExec++;
+ pitem->paramId = list_length(root->glob->paramExecTypes);
+ root->glob->paramExecTypes = lappend_oid(root->glob->paramExecTypes,
+ ptype);
root->plan_params = lappend(root->plan_params, pitem);
retval = makeNode(Param);
retval->paramkind = PARAM_EXEC;
retval->paramid = pitem->paramId;
- retval->paramtype = exprType((Node *) grp);
+ retval->paramtype = ptype;
retval->paramtypmod = -1;
retval->paramcollid = InvalidOid;
retval->location = grp->location;
@@ -385,7 +395,8 @@ replace_outer_grouping(PlannerInfo *root, GroupingFunc *grp)
*
* This is used to create Params representing subplan outputs.
* We don't need to build a PlannerParamItem for such a Param, but we do
- * need to record the PARAM_EXEC slot number as being allocated.
+ * need to make sure we have record the type in paramExecTypes (otherwise,
+ * there won't be a slot allocated for it).
*/
static Param *
generate_new_param(PlannerInfo *root, Oid paramtype, int32 paramtypmod,
@@ -395,7 +406,9 @@ generate_new_param(PlannerInfo *root, Oid paramtype, int32 paramtypmod,
retval = makeNode(Param);
retval->paramkind = PARAM_EXEC;
- retval->paramid = root->glob->nParamExec++;
+ retval->paramid = list_length(root->glob->paramExecTypes);
+ root->glob->paramExecTypes = lappend_oid(root->glob->paramExecTypes,
+ paramtype);
retval->paramtype = paramtype;
retval->paramtypmod = paramtypmod;
retval->paramcollid = paramcollation;
@@ -415,7 +428,11 @@ generate_new_param(PlannerInfo *root, Oid paramtype, int32 paramtypmod,
int
SS_assign_special_param(PlannerInfo *root)
{
- return root->glob->nParamExec++;
+ int paramId = list_length(root->glob->paramExecTypes);
+
+ root->glob->paramExecTypes = lappend_oid(root->glob->paramExecTypes,
+ InvalidOid);
+ return paramId;
}
/*
@@ -2098,7 +2115,7 @@ SS_identify_outer_params(PlannerInfo *root)
* If no parameters have been assigned anywhere in the tree, we certainly
* don't need to do anything here.
*/
- if (root->glob->nParamExec == 0)
+ if (root->glob->paramExecTypes == NIL)
return;
/*
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 30cdd3da4c..66e098f488 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -1095,7 +1095,7 @@ is_parallel_safe(PlannerInfo *root, Node *node)
* in this expression. But otherwise we don't need to look.
*/
if (root->glob->maxParallelHazard == PROPARALLEL_SAFE &&
- root->glob->nParamExec == 0)
+ root->glob->paramExecTypes == NIL)
return true;
/* Else use max_parallel_hazard's search logic, but stop on RESTRICTED */
context.max_hazard = PROPARALLEL_SAFE;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index dd74efa9a4..a127682b0e 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -89,7 +89,7 @@ typedef struct PlannedStmt
List *invalItems; /* other dependencies, as PlanInvalItems */
- int nParamExec; /* number of PARAM_EXEC Params used */
+ List *paramExecTypes; /* type OIDs for PARAM_EXEC Params */
Node *utilityStmt; /* non-null if this is utility stmt */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 05fc9a3f48..9e68e65cc6 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -114,7 +114,7 @@ typedef struct PlannerGlobal
List *invalItems; /* other dependencies, as PlanInvalItems */
- int nParamExec; /* number of PARAM_EXEC Params used */
+ List *paramExecTypes; /* type OIDs for PARAM_EXEC Params */
Index lastPHId; /* highest PlaceHolderVar ID assigned */
@@ -2219,8 +2219,8 @@ typedef struct MinMaxAggInfo
* from subplans (values that are setParam items for those subplans). These
* IDs need not be tracked via PlannerParamItems, since we do not need any
* duplicate-elimination nor later processing of the represented expressions.
- * Instead, we just record the assignment of the slot number by incrementing
- * root->glob->nParamExec.
+ * Instead, we just record the assignment of the slot number by appending to
+ * root->glob->paramExecTypes.
*/
typedef struct PlannerParamItem
{
--
2.13.5 (Apple Git-94)
0002-pq-pushdown-initplan-rebased.patchapplication/octet-stream; name=0002-pq-pushdown-initplan-rebased.patchDownload
From 052ff4877bb72180463fd9fb333a90c9f0390d08 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Fri, 10 Nov 2017 13:28:59 -0500
Subject: [PATCH 2/2] pq-pushdown-initplan-rebased
---
src/backend/commands/explain.c | 34 ++++++
src/backend/executor/execExprInterp.c | 27 ++++
src/backend/executor/execParallel.c | 170 +++++++++++++++++++++++---
src/backend/executor/nodeGather.c | 1 +
src/backend/executor/nodeGatherMerge.c | 1 +
src/backend/nodes/copyfuncs.c | 2 +
src/backend/nodes/outfuncs.c | 3 +
src/backend/nodes/readfuncs.c | 2 +
src/backend/optimizer/plan/createplan.c | 1 +
src/backend/optimizer/plan/planner.c | 8 ++
src/backend/optimizer/plan/setrefs.c | 51 +++++++-
src/backend/optimizer/util/clauses.c | 24 +++-
src/include/executor/execExpr.h | 1 +
src/include/executor/execParallel.h | 3 +-
src/include/nodes/plannodes.h | 4 +
src/test/regress/expected/select_parallel.out | 35 ++++++
src/test/regress/sql/select_parallel.sql | 17 +++
17 files changed, 366 insertions(+), 18 deletions(-)
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 8f7062cd6e..447f69d044 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -107,6 +107,7 @@ static void show_tidbitmap_info(BitmapHeapScanState *planstate,
static void show_instrumentation_count(const char *qlabel, int which,
PlanState *planstate, ExplainState *es);
static void show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es);
+static void show_eval_params(Bitmapset *bms_params, ExplainState *es);
static const char *explain_get_index_name(Oid indexId);
static void show_buffer_usage(ExplainState *es, const BufferUsage *usage);
static void ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir,
@@ -1441,6 +1442,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
planstate, es);
ExplainPropertyInteger("Workers Planned",
gather->num_workers, es);
+
+ /* Show params evaluated at gather node */
+ if (gather->initParam)
+ show_eval_params(gather->initParam, es);
+
if (es->analyze)
{
int nworkers;
@@ -1463,6 +1469,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
planstate, es);
ExplainPropertyInteger("Workers Planned",
gm->num_workers, es);
+
+ /* Show params evaluated at gather-merge node */
+ if (gm->initParam)
+ show_eval_params(gm->initParam, es);
+
if (es->analyze)
{
int nworkers;
@@ -2488,6 +2499,29 @@ show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es)
}
/*
+ * Show initplan params evaluated at Gather or Gather Merge node.
+ */
+static void
+show_eval_params(Bitmapset *bms_params, ExplainState *es)
+{
+ int paramid = -1;
+ List *params = NIL;
+
+ Assert(bms_params);
+
+ while ((paramid = bms_next_member(bms_params, paramid)) >= 0)
+ {
+ char param[32];
+
+ snprintf(param, sizeof(param), "$%d", paramid);
+ params = lappend(params, pstrdup(param));
+ }
+
+ if (params)
+ ExplainPropertyList("Params Evaluated", params, es);
+}
+
+/*
* Fetch the name of an index in an EXPLAIN
*
* We allow plugins to get control here so that plans involving hypothetical
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index a0f537b706..6c4612dad4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -1927,6 +1927,33 @@ ExecEvalParamExec(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
}
/*
+ * ExecEvalParamExecParams
+ *
+ * Execute the subplan stored in PARAM_EXEC initplans params, if not executed
+ * till now.
+ */
+void
+ExecEvalParamExecParams(Bitmapset *params, EState *estate)
+{
+ ParamExecData *prm;
+ int paramid;
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ prm = &(estate->es_param_exec_vals[paramid]);
+
+ if (prm->execPlan != NULL)
+ {
+ /* Parameter not evaluated yet, so go do it */
+ ExecSetParamPlan(prm->execPlan, GetPerTupleExprContext(estate));
+ /* ExecSetParamPlan should have processed this param... */
+ Assert(prm->execPlan == NULL);
+ }
+ }
+}
+
+/*
* Evaluate a PARAM_EXTERN parameter.
*
* PARAM_EXTERN parameters must be sought in ecxt_param_list_info.
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index fd7e7cbf3d..a684bbfc38 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -23,6 +23,7 @@
#include "postgres.h"
+#include "executor/execExpr.h"
#include "executor/execParallel.h"
#include "executor/executor.h"
#include "executor/nodeBitmapHeapscan.h"
@@ -31,6 +32,7 @@
#include "executor/nodeIndexscan.h"
#include "executor/nodeIndexonlyscan.h"
#include "executor/nodeSeqscan.h"
+#include "executor/nodeSubplan.h"
#include "executor/nodeSort.h"
#include "executor/tqueue.h"
#include "nodes/nodeFuncs.h"
@@ -38,7 +40,9 @@
#include "optimizer/planner.h"
#include "storage/spin.h"
#include "tcop/tcopprot.h"
+#include "utils/datum.h"
#include "utils/dsa.h"
+#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/snapmgr.h"
#include "pgstat.h"
@@ -50,12 +54,13 @@
*/
#define PARALLEL_KEY_EXECUTOR_FIXED UINT64CONST(0xE000000000000001)
#define PARALLEL_KEY_PLANNEDSTMT UINT64CONST(0xE000000000000002)
-#define PARALLEL_KEY_PARAMS UINT64CONST(0xE000000000000003)
-#define PARALLEL_KEY_BUFFER_USAGE UINT64CONST(0xE000000000000004)
-#define PARALLEL_KEY_TUPLE_QUEUE UINT64CONST(0xE000000000000005)
-#define PARALLEL_KEY_INSTRUMENTATION UINT64CONST(0xE000000000000006)
-#define PARALLEL_KEY_DSA UINT64CONST(0xE000000000000007)
-#define PARALLEL_KEY_QUERY_TEXT UINT64CONST(0xE000000000000008)
+#define PARALLEL_KEY_PARAMLISTINFO UINT64CONST(0xE000000000000003)
+#define PARALLEL_KEY_PARAM_EXEC UINT64CONST(0xE000000000000004)
+#define PARALLEL_KEY_BUFFER_USAGE UINT64CONST(0xE000000000000005)
+#define PARALLEL_KEY_TUPLE_QUEUE UINT64CONST(0xE000000000000006)
+#define PARALLEL_KEY_INSTRUMENTATION UINT64CONST(0xE000000000000007)
+#define PARALLEL_KEY_DSA UINT64CONST(0xE000000000000008)
+#define PARALLEL_KEY_QUERY_TEXT UINT64CONST(0xE000000000000009)
#define PARALLEL_TUPLE_QUEUE_SIZE 65536
@@ -267,6 +272,122 @@ ExecParallelEstimate(PlanState *planstate, ExecParallelEstimateContext *e)
}
/*
+ * Estimate the amount of space required to serialize the indicated parameters.
+ */
+static Size
+EstimateParamExecSpace(EState *estate, Bitmapset *params)
+{
+ int paramid;
+ Size sz = sizeof(int);
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ Oid typeOid;
+ int16 typLen;
+ bool typByVal;
+ ParamExecData *prm;
+
+ prm = &(estate->es_param_exec_vals[paramid]);
+ typeOid = list_nth_oid(estate->es_plannedstmt->paramExecTypes,
+ paramid);
+
+ sz = add_size(sz, sizeof(int)); /* space for paramid */
+
+ /* space for datum/isnull */
+ if (OidIsValid(typeOid))
+ get_typlenbyval(typeOid, &typLen, &typByVal);
+ else
+ {
+ /* If no type OID, assume by-value, like copyParamList does. */
+ typLen = sizeof(Datum);
+ typByVal = true;
+ }
+ sz = add_size(sz,
+ datumEstimateSpace(prm->value, prm->isnull,
+ typByVal, typLen));
+ }
+ return sz;
+}
+
+/*
+ * Serialize ParamExecData params corresponding to initplans.
+ *
+ * We write the number of parameters first, as a 4-byte integer, and then
+ * write details for each parameter in turn. The details for each parameter
+ * consist of a 4-byte paramid (location of param in execution time internal
+ * parameter array) and then the datum as serialized by datumSerialize().
+ */
+static void
+SerializeParamExecParams(EState *estate, Bitmapset *params,
+ char *start_address)
+{
+ int nparams;
+ int paramid;
+ ParamExecData *prm;
+
+ nparams = bms_num_members(params);
+ memcpy(start_address, &nparams, sizeof(int));
+ start_address += sizeof(int);
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ Oid typeOid;
+ int16 typLen;
+ bool typByVal;
+
+ prm = &(estate->es_param_exec_vals[paramid]);
+ typeOid = list_nth_oid(estate->es_plannedstmt->paramExecTypes,
+ paramid);
+
+ /* Write paramid. */
+ memcpy(start_address, ¶mid, sizeof(int));
+ start_address += sizeof(int);
+
+ /* space for datum/isnull */
+ if (OidIsValid(typeOid))
+ get_typlenbyval(typeOid, &typLen, &typByVal);
+ else
+ {
+ /* If no type OID, assume by-value, like copyParamList does. */
+ typLen = sizeof(Datum);
+ typByVal = true;
+ }
+ datumSerialize(prm->value, prm->isnull, typByVal, typLen,
+ &start_address);
+ }
+}
+
+/*
+ * Restore ParamExecData params corresponding to initplans.
+ */
+static void
+RestoreParamExecParams(char *start_address, EState *estate)
+{
+ int nparams;
+ int i;
+ int paramid;
+
+ memcpy(&nparams, start_address, sizeof(int));
+ start_address += sizeof(int);
+
+ for (i = 0; i < nparams; i++)
+ {
+ ParamExecData *prm;
+
+ /* Read paramid */
+ memcpy(¶mid, start_address, sizeof(int));
+ start_address += sizeof(int);
+ prm = &(estate->es_param_exec_vals[paramid]);
+
+ /* Read datum/isnull. */
+ prm->value = datumRestore(&start_address, &prm->isnull);
+ prm->execPlan = NULL;
+ }
+}
+
+/*
* Initialize the dynamic shared memory segment that will be used to control
* parallel execution.
*/
@@ -395,7 +516,8 @@ ExecParallelSetupTupleQueues(ParallelContext *pcxt, bool reinitialize)
* execution and return results to the main backend.
*/
ParallelExecutorInfo *
-ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
+ExecInitParallelPlan(PlanState *planstate, EState *estate,
+ Bitmapset *sendParams, int nworkers,
int64 tuples_needed)
{
ParallelExecutorInfo *pei;
@@ -405,17 +527,22 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
FixedParallelExecutorState *fpes;
char *pstmt_data;
char *pstmt_space;
- char *param_space;
+ char *paramlistinfo_space;
+ char *paramexec_space;
BufferUsage *bufusage_space;
SharedExecutorInstrumentation *instrumentation = NULL;
int pstmt_len;
- int param_len;
+ int paramlistinfo_len;
+ int paramexec_len;
int instrumentation_len = 0;
int instrument_offset = 0;
Size dsa_minsize = dsa_minimum_size();
char *query_string;
int query_len;
+ /* Force parameters we're going to pass to workers to be evaluated. */
+ ExecEvalParamExecParams(sendParams, estate);
+
/* Allocate object for return value. */
pei = palloc0(sizeof(ParallelExecutorInfo));
pei->finished = false;
@@ -450,8 +577,13 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
shm_toc_estimate_keys(&pcxt->estimator, 1);
/* Estimate space for serialized ParamListInfo. */
- param_len = EstimateParamListSpace(estate->es_param_list_info);
- shm_toc_estimate_chunk(&pcxt->estimator, param_len);
+ paramlistinfo_len = EstimateParamListSpace(estate->es_param_list_info);
+ shm_toc_estimate_chunk(&pcxt->estimator, paramlistinfo_len);
+ shm_toc_estimate_keys(&pcxt->estimator, 1);
+
+ /* Estimate space for serialized PARAM_EXEC parameters. */
+ paramexec_len = EstimateParamExecSpace(estate, sendParams);
+ shm_toc_estimate_chunk(&pcxt->estimator, paramexec_len);
shm_toc_estimate_keys(&pcxt->estimator, 1);
/*
@@ -524,9 +656,14 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
shm_toc_insert(pcxt->toc, PARALLEL_KEY_PLANNEDSTMT, pstmt_space);
/* Store serialized ParamListInfo. */
- param_space = shm_toc_allocate(pcxt->toc, param_len);
- shm_toc_insert(pcxt->toc, PARALLEL_KEY_PARAMS, param_space);
- SerializeParamList(estate->es_param_list_info, ¶m_space);
+ paramlistinfo_space = shm_toc_allocate(pcxt->toc, paramlistinfo_len);
+ shm_toc_insert(pcxt->toc, PARALLEL_KEY_PARAMLISTINFO, paramlistinfo_space);
+ SerializeParamList(estate->es_param_list_info, ¶mlistinfo_space);
+
+ /* Store serialized PARAM_EXEC parameters. */
+ paramexec_space = shm_toc_allocate(pcxt->toc, paramexec_len);
+ shm_toc_insert(pcxt->toc, PARALLEL_KEY_PARAM_EXEC, paramexec_space);
+ SerializeParamExecParams(estate, sendParams, paramexec_space);
/* Allocate space for each worker's BufferUsage; no need to initialize. */
bufusage_space = shm_toc_allocate(pcxt->toc,
@@ -882,7 +1019,7 @@ ExecParallelGetQueryDesc(shm_toc *toc, DestReceiver *receiver,
pstmt = (PlannedStmt *) stringToNode(pstmtspace);
/* Reconstruct ParamListInfo. */
- paramspace = shm_toc_lookup(toc, PARALLEL_KEY_PARAMS, false);
+ paramspace = shm_toc_lookup(toc, PARALLEL_KEY_PARAMLISTINFO, false);
paramLI = RestoreParamList(¶mspace);
/*
@@ -1017,6 +1154,7 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
int instrument_options = 0;
void *area_space;
dsa_area *area;
+ char *paramexec_space;
/* Get fixed-size state. */
fpes = shm_toc_lookup(toc, PARALLEL_KEY_EXECUTOR_FIXED, false);
@@ -1046,6 +1184,8 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
/* Special executor initialization steps for parallel workers */
queryDesc->planstate->state->es_query_dsa = area;
+ paramexec_space = shm_toc_lookup(toc, PARALLEL_KEY_PARAM_EXEC, false);
+ RestoreParamExecParams(paramexec_space, queryDesc->estate);
ExecParallelInitializeWorker(queryDesc->planstate, toc);
/* Pass down any tuple bound */
diff --git a/src/backend/executor/nodeGather.c b/src/backend/executor/nodeGather.c
index 639f4f5af8..eb9949f529 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -158,6 +158,7 @@ ExecGather(PlanState *pstate)
if (!node->pei)
node->pei = ExecInitParallelPlan(node->ps.lefttree,
estate,
+ gather->initParam,
gather->num_workers,
node->tuples_needed);
else
diff --git a/src/backend/executor/nodeGatherMerge.c b/src/backend/executor/nodeGatherMerge.c
index 5625b12521..76e07ccf57 100644
--- a/src/backend/executor/nodeGatherMerge.c
+++ b/src/backend/executor/nodeGatherMerge.c
@@ -202,6 +202,7 @@ ExecGatherMerge(PlanState *pstate)
if (!node->pei)
node->pei = ExecInitParallelPlan(node->ps.lefttree,
estate,
+ gm->initParam,
gm->num_workers,
node->tuples_needed);
else
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 76e75459b4..d9ff8a7e51 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -364,6 +364,7 @@ _copyGather(const Gather *from)
COPY_SCALAR_FIELD(rescan_param);
COPY_SCALAR_FIELD(single_copy);
COPY_SCALAR_FIELD(invisible);
+ COPY_BITMAPSET_FIELD(initParam);
return newnode;
}
@@ -391,6 +392,7 @@ _copyGatherMerge(const GatherMerge *from)
COPY_POINTER_FIELD(sortOperators, from->numCols * sizeof(Oid));
COPY_POINTER_FIELD(collations, from->numCols * sizeof(Oid));
COPY_POINTER_FIELD(nullsFirst, from->numCols * sizeof(bool));
+ COPY_BITMAPSET_FIELD(initParam);
return newnode;
}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index dc35df9e4f..c97ee24ade 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -487,6 +487,7 @@ _outGather(StringInfo str, const Gather *node)
WRITE_INT_FIELD(rescan_param);
WRITE_BOOL_FIELD(single_copy);
WRITE_BOOL_FIELD(invisible);
+ WRITE_BITMAPSET_FIELD(initParam);
}
static void
@@ -517,6 +518,8 @@ _outGatherMerge(StringInfo str, const GatherMerge *node)
appendStringInfoString(str, " :nullsFirst");
for (i = 0; i < node->numCols; i++)
appendStringInfo(str, " %s", booltostr(node->nullsFirst[i]));
+
+ WRITE_BITMAPSET_FIELD(initParam);
}
static void
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 593658dd8a..7eb67fc040 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2172,6 +2172,7 @@ _readGather(void)
READ_INT_FIELD(rescan_param);
READ_BOOL_FIELD(single_copy);
READ_BOOL_FIELD(invisible);
+ READ_BITMAPSET_FIELD(initParam);
READ_DONE();
}
@@ -2193,6 +2194,7 @@ _readGatherMerge(void)
READ_OID_ARRAY(sortOperators, local_node->numCols);
READ_OID_ARRAY(collations, local_node->numCols);
READ_BOOL_ARRAY(nullsFirst, local_node->numCols);
+ READ_BITMAPSET_FIELD(initParam);
READ_DONE();
}
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 9c74e39bd3..d4454779ee 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -6279,6 +6279,7 @@ make_gather(List *qptlist,
node->rescan_param = rescan_param;
node->single_copy = single_copy;
node->invisible = false;
+ node->initParam = NULL;
return node;
}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 607f7cd251..712216e5e3 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -376,6 +376,14 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
{
Gather *gather = makeNode(Gather);
+ /*
+ * If there are any initPlans attached to the formerly-top plan node,
+ * move them up to the Gather node; same as we do for Material node in
+ * materialize_finished_plan.
+ */
+ gather->plan.initPlan = top_plan->initPlan;
+ top_plan->initPlan = NIL;
+
gather->plan.targetlist = top_plan->targetlist;
gather->plan.qual = NIL;
gather->plan.lefttree = top_plan;
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index fa9a3f0b47..b5c41241d7 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -107,6 +107,7 @@ static Node *fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context);
static bool fix_scan_expr_walker(Node *node, fix_scan_expr_context *context);
static void set_join_references(PlannerInfo *root, Join *join, int rtoffset);
static void set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset);
+static void set_param_references(PlannerInfo *root, Plan *plan);
static Node *convert_combining_aggrefs(Node *node, void *context);
static void set_dummy_tlist_references(Plan *plan, int rtoffset);
static indexed_tlist *build_tlist_index(List *tlist);
@@ -632,7 +633,10 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
case T_Gather:
case T_GatherMerge:
- set_upper_references(root, plan, rtoffset);
+ {
+ set_upper_references(root, plan, rtoffset);
+ set_param_references(root, plan);
+ }
break;
case T_Hash:
@@ -1782,6 +1786,51 @@ set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset)
}
/*
+ * set_param_references
+ * Initialize the initParam list in Gather or Gather merge node such that
+ * it contains reference of all the params that needs to be evaluated
+ * before execution of the node. It contains the initplan params that are
+ * being passed to the plan nodes below it.
+ */
+static void
+set_param_references(PlannerInfo *root, Plan *plan)
+{
+ Assert(IsA(plan, Gather) ||IsA(plan, GatherMerge));
+
+ if (plan->lefttree->extParam)
+ {
+ PlannerInfo *proot;
+ Bitmapset *initSetParam = NULL;
+ ListCell *l;
+
+ for (proot = root; proot != NULL; proot = proot->parent_root)
+ {
+ foreach(l, proot->init_plans)
+ {
+ SubPlan *initsubplan = (SubPlan *) lfirst(l);
+ ListCell *l2;
+
+ foreach(l2, initsubplan->setParam)
+ {
+ initSetParam = bms_add_member(initSetParam, lfirst_int(l2));
+ }
+ }
+ }
+
+ /*
+ * Remember the list of all external initplan params that are used by
+ * the children of Gather or Gather merge node.
+ */
+ if (IsA(plan, Gather))
+ ((Gather *) plan)->initParam =
+ bms_intersect(plan->lefttree->extParam, initSetParam);
+ else
+ ((GatherMerge *) plan)->initParam =
+ bms_intersect(plan->lefttree->extParam, initSetParam);
+ }
+}
+
+/*
* Recursively scan an expression tree and convert Aggrefs to the proper
* intermediate form for combining aggregates. This means (1) replacing each
* one's argument list with a single argument that is the original Aggref
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 66e098f488..d14ef31eae 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -1087,6 +1087,8 @@ bool
is_parallel_safe(PlannerInfo *root, Node *node)
{
max_parallel_hazard_context context;
+ PlannerInfo *proot;
+ ListCell *l;
/*
* Even if the original querytree contained nothing unsafe, we need to
@@ -1101,6 +1103,25 @@ is_parallel_safe(PlannerInfo *root, Node *node)
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_RESTRICTED;
context.safe_param_ids = NIL;
+
+ /*
+ * The params that refer to the same or parent query level are considered
+ * parallel-safe. The idea is that we compute such params at Gather or
+ * Gather Merge node and pass their value to workers.
+ */
+ for (proot = root; proot != NULL; proot = proot->parent_root)
+ {
+ foreach(l, proot->init_plans)
+ {
+ SubPlan *initsubplan = (SubPlan *) lfirst(l);
+ ListCell *l2;
+
+ foreach(l2, initsubplan->setParam)
+ context.safe_param_ids = lcons_int(lfirst_int(l2),
+ context.safe_param_ids);
+ }
+ }
+
return !max_parallel_hazard_walker(node, &context);
}
@@ -1225,7 +1246,8 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
* We can't pass Params to workers at the moment either, so they are also
* parallel-restricted, unless they are PARAM_EXTERN Params or are
* PARAM_EXEC Params listed in safe_param_ids, meaning they could be
- * generated within the worker.
+ * either generated within the worker or can be computed in master and
+ * then their value can be passed to the worker.
*/
else if (IsA(node, Param))
{
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 78d2247816..5bbb63a9d8 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -609,6 +609,7 @@ extern ExprEvalOp ExecEvalStepOp(ExprState *state, ExprEvalStep *op);
*/
extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
+extern void ExecEvalParamExecParams(Bitmapset *params, EState *estate);
extern void ExecEvalParamExtern(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
extern void ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op);
diff --git a/src/include/executor/execParallel.h b/src/include/executor/execParallel.h
index e1b3e7af1f..88e275742b 100644
--- a/src/include/executor/execParallel.h
+++ b/src/include/executor/execParallel.h
@@ -35,7 +35,8 @@ typedef struct ParallelExecutorInfo
} ParallelExecutorInfo;
extern ParallelExecutorInfo *ExecInitParallelPlan(PlanState *planstate,
- EState *estate, int nworkers, int64 tuples_needed);
+ EState *estate, Bitmapset *initParam, int nworkers,
+ int64 tuples_needed);
extern void ExecParallelCreateReaders(ParallelExecutorInfo *pei);
extern void ExecParallelFinish(ParallelExecutorInfo *pei);
extern void ExecParallelCleanup(ParallelExecutorInfo *pei);
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index a127682b0e..9b38d44ba0 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -841,6 +841,8 @@ typedef struct Gather
int rescan_param; /* ID of Param that signals a rescan, or -1 */
bool single_copy; /* don't execute plan more than once */
bool invisible; /* suppress EXPLAIN display (for testing)? */
+ Bitmapset *initParam; /* param id's of initplans which are referred
+ * at gather or one of it's child node */
} Gather;
/* ------------
@@ -858,6 +860,8 @@ typedef struct GatherMerge
Oid *sortOperators; /* OIDs of operators to sort them by */
Oid *collations; /* OIDs of collations */
bool *nullsFirst; /* NULLS FIRST/LAST directions */
+ Bitmapset *initParam; /* param id's of initplans which are referred
+ * at gather merge or one of it's child node */
} GatherMerge;
/* ----------------
diff --git a/src/test/regress/expected/select_parallel.out b/src/test/regress/expected/select_parallel.out
index ac9ad0668d..5447e081de 100644
--- a/src/test/regress/expected/select_parallel.out
+++ b/src/test/regress/expected/select_parallel.out
@@ -159,6 +159,41 @@ explain (costs off)
(4 rows)
alter table tenk2 reset (parallel_workers);
+-- test parallel plan for a query containing initplan.
+set enable_indexscan = off;
+set enable_indexonlyscan = off;
+set enable_bitmapscan = off;
+alter table tenk2 set (parallel_workers = 2);
+explain (costs off)
+ select count(*) from tenk1
+ where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+ QUERY PLAN
+------------------------------------------------------
+ Aggregate
+ InitPlan 1 (returns $2)
+ -> Finalize Aggregate
+ -> Gather
+ Workers Planned: 2
+ -> Partial Aggregate
+ -> Parallel Seq Scan on tenk2
+ -> Gather
+ Workers Planned: 4
+ Params Evaluated: $2
+ -> Parallel Seq Scan on tenk1
+ Filter: (unique1 = $2)
+(12 rows)
+
+select count(*) from tenk1
+ where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+ count
+-------
+ 1
+(1 row)
+
+reset enable_indexscan;
+reset enable_indexonlyscan;
+reset enable_bitmapscan;
+alter table tenk2 reset (parallel_workers);
-- test parallel index scans.
set enable_seqscan to off;
set enable_bitmapscan to off;
diff --git a/src/test/regress/sql/select_parallel.sql b/src/test/regress/sql/select_parallel.sql
index 495f0335dc..dd2816971f 100644
--- a/src/test/regress/sql/select_parallel.sql
+++ b/src/test/regress/sql/select_parallel.sql
@@ -58,6 +58,23 @@ explain (costs off)
(select ten from tenk2);
alter table tenk2 reset (parallel_workers);
+-- test parallel plan for a query containing initplan.
+set enable_indexscan = off;
+set enable_indexonlyscan = off;
+set enable_bitmapscan = off;
+alter table tenk2 set (parallel_workers = 2);
+
+explain (costs off)
+ select count(*) from tenk1
+ where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+select count(*) from tenk1
+ where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+
+reset enable_indexscan;
+reset enable_indexonlyscan;
+reset enable_bitmapscan;
+alter table tenk2 reset (parallel_workers);
+
-- test parallel index scans.
set enable_seqscan to off;
set enable_bitmapscan to off;
--
2.13.5 (Apple Git-94)
Robert Haas <robertmhaas@gmail.com> writes:
I decided to try instead teaching the planner to keep track of the
types of PARAM_EXEC parameters as they were created, and that seems to
work fine. See 0001, attached.
I did not look at the other part, but 0001 looks reasonable to me.
I might quibble with the grammar in the generate_new_param comment:
- * need to record the PARAM_EXEC slot number as being allocated.
+ * need to make sure we have record the type in paramExecTypes (otherwise,
+ * there won't be a slot allocated for it).
*/
I'd just go with "need to record the type in ..."
Also, I wonder whether the InvalidOid hack in SS_assign_special_param
requires commentary. It might be safer to use a valid type OID there,
perhaps VOIDOID or INTERNALOID.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Nov 10, 2017 at 2:24 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Robert Haas <robertmhaas@gmail.com> writes:
I decided to try instead teaching the planner to keep track of the
types of PARAM_EXEC parameters as they were created, and that seems to
work fine. See 0001, attached.I did not look at the other part, but 0001 looks reasonable to me.
Thanks for looking.
I might quibble with the grammar in the generate_new_param comment:
- * need to record the PARAM_EXEC slot number as being allocated. + * need to make sure we have record the type in paramExecTypes (otherwise, + * there won't be a slot allocated for it). */I'd just go with "need to record the type in ..."
Noted.
Also, I wonder whether the InvalidOid hack in SS_assign_special_param
requires commentary. It might be safer to use a valid type OID there,
perhaps VOIDOID or INTERNALOID.
I think it's pretty straightforward -- if, as the existing comments
say, no Param node will be present and no value will be stored, then
we do not and cannot record the type of the value that we're not
storing.
There is existing precedent for using InvalidOid to denote the absence
of a parameter -- cf. copyParamList, SerializeParamList. That
convention was not invented by me or the parallel query stuff,
although it has become more widespread for that reason. I am
disinclined to have this patch invent a New Way To Do It.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Robert Haas <robertmhaas@gmail.com> writes:
On Fri, Nov 10, 2017 at 2:24 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Also, I wonder whether the InvalidOid hack in SS_assign_special_param
requires commentary. It might be safer to use a valid type OID there,
perhaps VOIDOID or INTERNALOID.
There is existing precedent for using InvalidOid to denote the absence
of a parameter -- cf. copyParamList, SerializeParamList.
OK, fair enough. I was concerned that this was adding a corner case
not otherwise seen with Params, but evidently not.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Sat, Nov 11, 2017 at 12:15 AM, Robert Haas <robertmhaas@gmail.com> wrote:
On Tue, Nov 7, 2017 at 4:45 AM, Amit Kapila <amit.kapila16@gmail.com> wrote:
As mentioned, changed the status of the patch in CF app.
I spent some time reviewing this patch today and found myself still
quite uncomfortable with the fact that it was adding execution-time
work to track the types of parameters - types that would usually not
even be used. I found the changes to nodeNestLoop.c to be
particularly objectionable, because we could end up doing the work
over and over when it is actually not needed at all, or at most once.
That's right, but we are just accessing tuple descriptor to get the
type, there shouldn't be much work involved in that. However, I think
your approach has a merit that we don't need to even do that during
execution time.
I decided to try instead teaching the planner to keep track of the
types of PARAM_EXEC parameters as they were created, and that seems to
work fine. See 0001, attached.
This looks good to me.
0002, attached, is my worked-over version of the rest of the patch. I
moved the code that serializes and deserializes PARAM_EXEC from
nodeSubplan.c -- which seemed like a strange choice - to
execParallel.c.
I have tried to follow the practice we have used for param extern
params (SerializeParamList is in params.c) and most of the handling of
initplans is done in nodeSubplan.c, so I choose to keep the newly
added functions there. However, I think keeping it in execParallel.c
is also okay as we do it for serialize plan.
I removed the type OID from the serialization format
because there's no reason to do that any more; the worker already
knows the types from the plan. I did some renaming of the functions
involved and some adjustment of the comments to refer to "PARAM_EXEC
parameters" instead of initPlan parameters, because there's no reason
that I can see why this can only work for initPlans. A Gather node on
the inner side of a nested loop doesn't sound like a great idea, but I
think this infrastructure could handle it (though it would need some
more planner work).
I think it would need some work in execution as well because the size
won't be fixed in that case for varchar type of params. We might end
up with something different as well.
I broke a lot of long lines in your version of
the patch into multiple lines; please try to be attentive to this
issue when writing patches in general, as it is a bit tedious to go
through and insert line breaks in many places.
Okay, but I sometimes rely on pgindent for such things as for few
things it becomes difficult to decide which way it will be better.
Please let me know your thoughts on the attached patches.
Few minor comments:
1.
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -23,6 +23,7 @@
#include "postgres.h"
+#include "executor/execExpr.h"
#include "executor/execParallel.h"
#include "executor/executor.h"
#include "executor/nodeBitmapHeapscan.h"
@@ -31,6 +32,7 @@
#include "executor/nodeIndexscan.h"
#include "executor/nodeIndexonlyscan.h"
#include "executor/nodeSeqscan.h"
+#include "executor/nodeSubplan.h"
This is not required if we move serialize and other functions to execParallel.c
2.
+set_param_references(PlannerInfo *root, Plan *plan)
+{
+ Assert(IsA(plan, Gather) ||IsA(plan, GatherMerge));
I think there should be a space after || operator.
3.
+/*
+ * Serialize ParamExecData params corresponding to initplans.
+ *
..
+/*
+ * Restore ParamExecData params corresponding to initplans.
+ */
Shouldn't we change the reference to initplans here as well?
I have fixed the first two in attached patch and left the last one as
I was not sure what you have in mind
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
0002-pq-pushdown-initplan-rebased-1.patchapplication/octet-stream; name=0002-pq-pushdown-initplan-rebased-1.patchDownload
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 8f7062c..447f69d 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -107,6 +107,7 @@ static void show_tidbitmap_info(BitmapHeapScanState *planstate,
static void show_instrumentation_count(const char *qlabel, int which,
PlanState *planstate, ExplainState *es);
static void show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es);
+static void show_eval_params(Bitmapset *bms_params, ExplainState *es);
static const char *explain_get_index_name(Oid indexId);
static void show_buffer_usage(ExplainState *es, const BufferUsage *usage);
static void ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir,
@@ -1441,6 +1442,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
planstate, es);
ExplainPropertyInteger("Workers Planned",
gather->num_workers, es);
+
+ /* Show params evaluated at gather node */
+ if (gather->initParam)
+ show_eval_params(gather->initParam, es);
+
if (es->analyze)
{
int nworkers;
@@ -1463,6 +1469,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
planstate, es);
ExplainPropertyInteger("Workers Planned",
gm->num_workers, es);
+
+ /* Show params evaluated at gather-merge node */
+ if (gm->initParam)
+ show_eval_params(gm->initParam, es);
+
if (es->analyze)
{
int nworkers;
@@ -2488,6 +2499,29 @@ show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es)
}
/*
+ * Show initplan params evaluated at Gather or Gather Merge node.
+ */
+static void
+show_eval_params(Bitmapset *bms_params, ExplainState *es)
+{
+ int paramid = -1;
+ List *params = NIL;
+
+ Assert(bms_params);
+
+ while ((paramid = bms_next_member(bms_params, paramid)) >= 0)
+ {
+ char param[32];
+
+ snprintf(param, sizeof(param), "$%d", paramid);
+ params = lappend(params, pstrdup(param));
+ }
+
+ if (params)
+ ExplainPropertyList("Params Evaluated", params, es);
+}
+
+/*
* Fetch the name of an index in an EXPLAIN
*
* We allow plugins to get control here so that plans involving hypothetical
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index a0f537b..6c4612d 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -1927,6 +1927,33 @@ ExecEvalParamExec(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
}
/*
+ * ExecEvalParamExecParams
+ *
+ * Execute the subplan stored in PARAM_EXEC initplans params, if not executed
+ * till now.
+ */
+void
+ExecEvalParamExecParams(Bitmapset *params, EState *estate)
+{
+ ParamExecData *prm;
+ int paramid;
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ prm = &(estate->es_param_exec_vals[paramid]);
+
+ if (prm->execPlan != NULL)
+ {
+ /* Parameter not evaluated yet, so go do it */
+ ExecSetParamPlan(prm->execPlan, GetPerTupleExprContext(estate));
+ /* ExecSetParamPlan should have processed this param... */
+ Assert(prm->execPlan == NULL);
+ }
+ }
+}
+
+/*
* Evaluate a PARAM_EXTERN parameter.
*
* PARAM_EXTERN parameters must be sought in ecxt_param_list_info.
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index fd7e7cb..3a10978 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -23,6 +23,7 @@
#include "postgres.h"
+#include "executor/execExpr.h"
#include "executor/execParallel.h"
#include "executor/executor.h"
#include "executor/nodeBitmapHeapscan.h"
@@ -38,7 +39,9 @@
#include "optimizer/planner.h"
#include "storage/spin.h"
#include "tcop/tcopprot.h"
+#include "utils/datum.h"
#include "utils/dsa.h"
+#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/snapmgr.h"
#include "pgstat.h"
@@ -50,12 +53,13 @@
*/
#define PARALLEL_KEY_EXECUTOR_FIXED UINT64CONST(0xE000000000000001)
#define PARALLEL_KEY_PLANNEDSTMT UINT64CONST(0xE000000000000002)
-#define PARALLEL_KEY_PARAMS UINT64CONST(0xE000000000000003)
-#define PARALLEL_KEY_BUFFER_USAGE UINT64CONST(0xE000000000000004)
-#define PARALLEL_KEY_TUPLE_QUEUE UINT64CONST(0xE000000000000005)
-#define PARALLEL_KEY_INSTRUMENTATION UINT64CONST(0xE000000000000006)
-#define PARALLEL_KEY_DSA UINT64CONST(0xE000000000000007)
-#define PARALLEL_KEY_QUERY_TEXT UINT64CONST(0xE000000000000008)
+#define PARALLEL_KEY_PARAMLISTINFO UINT64CONST(0xE000000000000003)
+#define PARALLEL_KEY_PARAM_EXEC UINT64CONST(0xE000000000000004)
+#define PARALLEL_KEY_BUFFER_USAGE UINT64CONST(0xE000000000000005)
+#define PARALLEL_KEY_TUPLE_QUEUE UINT64CONST(0xE000000000000006)
+#define PARALLEL_KEY_INSTRUMENTATION UINT64CONST(0xE000000000000007)
+#define PARALLEL_KEY_DSA UINT64CONST(0xE000000000000008)
+#define PARALLEL_KEY_QUERY_TEXT UINT64CONST(0xE000000000000009)
#define PARALLEL_TUPLE_QUEUE_SIZE 65536
@@ -267,6 +271,122 @@ ExecParallelEstimate(PlanState *planstate, ExecParallelEstimateContext *e)
}
/*
+ * Estimate the amount of space required to serialize the indicated parameters.
+ */
+static Size
+EstimateParamExecSpace(EState *estate, Bitmapset *params)
+{
+ int paramid;
+ Size sz = sizeof(int);
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ Oid typeOid;
+ int16 typLen;
+ bool typByVal;
+ ParamExecData *prm;
+
+ prm = &(estate->es_param_exec_vals[paramid]);
+ typeOid = list_nth_oid(estate->es_plannedstmt->paramExecTypes,
+ paramid);
+
+ sz = add_size(sz, sizeof(int)); /* space for paramid */
+
+ /* space for datum/isnull */
+ if (OidIsValid(typeOid))
+ get_typlenbyval(typeOid, &typLen, &typByVal);
+ else
+ {
+ /* If no type OID, assume by-value, like copyParamList does. */
+ typLen = sizeof(Datum);
+ typByVal = true;
+ }
+ sz = add_size(sz,
+ datumEstimateSpace(prm->value, prm->isnull,
+ typByVal, typLen));
+ }
+ return sz;
+}
+
+/*
+ * Serialize ParamExecData params corresponding to initplans.
+ *
+ * We write the number of parameters first, as a 4-byte integer, and then
+ * write details for each parameter in turn. The details for each parameter
+ * consist of a 4-byte paramid (location of param in execution time internal
+ * parameter array) and then the datum as serialized by datumSerialize().
+ */
+static void
+SerializeParamExecParams(EState *estate, Bitmapset *params,
+ char *start_address)
+{
+ int nparams;
+ int paramid;
+ ParamExecData *prm;
+
+ nparams = bms_num_members(params);
+ memcpy(start_address, &nparams, sizeof(int));
+ start_address += sizeof(int);
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ Oid typeOid;
+ int16 typLen;
+ bool typByVal;
+
+ prm = &(estate->es_param_exec_vals[paramid]);
+ typeOid = list_nth_oid(estate->es_plannedstmt->paramExecTypes,
+ paramid);
+
+ /* Write paramid. */
+ memcpy(start_address, ¶mid, sizeof(int));
+ start_address += sizeof(int);
+
+ /* space for datum/isnull */
+ if (OidIsValid(typeOid))
+ get_typlenbyval(typeOid, &typLen, &typByVal);
+ else
+ {
+ /* If no type OID, assume by-value, like copyParamList does. */
+ typLen = sizeof(Datum);
+ typByVal = true;
+ }
+ datumSerialize(prm->value, prm->isnull, typByVal, typLen,
+ &start_address);
+ }
+}
+
+/*
+ * Restore ParamExecData params corresponding to initplans.
+ */
+static void
+RestoreParamExecParams(char *start_address, EState *estate)
+{
+ int nparams;
+ int i;
+ int paramid;
+
+ memcpy(&nparams, start_address, sizeof(int));
+ start_address += sizeof(int);
+
+ for (i = 0; i < nparams; i++)
+ {
+ ParamExecData *prm;
+
+ /* Read paramid */
+ memcpy(¶mid, start_address, sizeof(int));
+ start_address += sizeof(int);
+ prm = &(estate->es_param_exec_vals[paramid]);
+
+ /* Read datum/isnull. */
+ prm->value = datumRestore(&start_address, &prm->isnull);
+ prm->execPlan = NULL;
+ }
+}
+
+/*
* Initialize the dynamic shared memory segment that will be used to control
* parallel execution.
*/
@@ -395,7 +515,8 @@ ExecParallelSetupTupleQueues(ParallelContext *pcxt, bool reinitialize)
* execution and return results to the main backend.
*/
ParallelExecutorInfo *
-ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
+ExecInitParallelPlan(PlanState *planstate, EState *estate,
+ Bitmapset *sendParams, int nworkers,
int64 tuples_needed)
{
ParallelExecutorInfo *pei;
@@ -405,17 +526,22 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
FixedParallelExecutorState *fpes;
char *pstmt_data;
char *pstmt_space;
- char *param_space;
+ char *paramlistinfo_space;
+ char *paramexec_space;
BufferUsage *bufusage_space;
SharedExecutorInstrumentation *instrumentation = NULL;
int pstmt_len;
- int param_len;
+ int paramlistinfo_len;
+ int paramexec_len;
int instrumentation_len = 0;
int instrument_offset = 0;
Size dsa_minsize = dsa_minimum_size();
char *query_string;
int query_len;
+ /* Force parameters we're going to pass to workers to be evaluated. */
+ ExecEvalParamExecParams(sendParams, estate);
+
/* Allocate object for return value. */
pei = palloc0(sizeof(ParallelExecutorInfo));
pei->finished = false;
@@ -450,8 +576,13 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
shm_toc_estimate_keys(&pcxt->estimator, 1);
/* Estimate space for serialized ParamListInfo. */
- param_len = EstimateParamListSpace(estate->es_param_list_info);
- shm_toc_estimate_chunk(&pcxt->estimator, param_len);
+ paramlistinfo_len = EstimateParamListSpace(estate->es_param_list_info);
+ shm_toc_estimate_chunk(&pcxt->estimator, paramlistinfo_len);
+ shm_toc_estimate_keys(&pcxt->estimator, 1);
+
+ /* Estimate space for serialized PARAM_EXEC parameters. */
+ paramexec_len = EstimateParamExecSpace(estate, sendParams);
+ shm_toc_estimate_chunk(&pcxt->estimator, paramexec_len);
shm_toc_estimate_keys(&pcxt->estimator, 1);
/*
@@ -524,9 +655,14 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
shm_toc_insert(pcxt->toc, PARALLEL_KEY_PLANNEDSTMT, pstmt_space);
/* Store serialized ParamListInfo. */
- param_space = shm_toc_allocate(pcxt->toc, param_len);
- shm_toc_insert(pcxt->toc, PARALLEL_KEY_PARAMS, param_space);
- SerializeParamList(estate->es_param_list_info, ¶m_space);
+ paramlistinfo_space = shm_toc_allocate(pcxt->toc, paramlistinfo_len);
+ shm_toc_insert(pcxt->toc, PARALLEL_KEY_PARAMLISTINFO, paramlistinfo_space);
+ SerializeParamList(estate->es_param_list_info, ¶mlistinfo_space);
+
+ /* Store serialized PARAM_EXEC parameters. */
+ paramexec_space = shm_toc_allocate(pcxt->toc, paramexec_len);
+ shm_toc_insert(pcxt->toc, PARALLEL_KEY_PARAM_EXEC, paramexec_space);
+ SerializeParamExecParams(estate, sendParams, paramexec_space);
/* Allocate space for each worker's BufferUsage; no need to initialize. */
bufusage_space = shm_toc_allocate(pcxt->toc,
@@ -882,7 +1018,7 @@ ExecParallelGetQueryDesc(shm_toc *toc, DestReceiver *receiver,
pstmt = (PlannedStmt *) stringToNode(pstmtspace);
/* Reconstruct ParamListInfo. */
- paramspace = shm_toc_lookup(toc, PARALLEL_KEY_PARAMS, false);
+ paramspace = shm_toc_lookup(toc, PARALLEL_KEY_PARAMLISTINFO, false);
paramLI = RestoreParamList(¶mspace);
/*
@@ -1017,6 +1153,7 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
int instrument_options = 0;
void *area_space;
dsa_area *area;
+ char *paramexec_space;
/* Get fixed-size state. */
fpes = shm_toc_lookup(toc, PARALLEL_KEY_EXECUTOR_FIXED, false);
@@ -1046,6 +1183,8 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
/* Special executor initialization steps for parallel workers */
queryDesc->planstate->state->es_query_dsa = area;
+ paramexec_space = shm_toc_lookup(toc, PARALLEL_KEY_PARAM_EXEC, false);
+ RestoreParamExecParams(paramexec_space, queryDesc->estate);
ExecParallelInitializeWorker(queryDesc->planstate, toc);
/* Pass down any tuple bound */
diff --git a/src/backend/executor/nodeGather.c b/src/backend/executor/nodeGather.c
index 639f4f5..eb9949f 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -158,6 +158,7 @@ ExecGather(PlanState *pstate)
if (!node->pei)
node->pei = ExecInitParallelPlan(node->ps.lefttree,
estate,
+ gather->initParam,
gather->num_workers,
node->tuples_needed);
else
diff --git a/src/backend/executor/nodeGatherMerge.c b/src/backend/executor/nodeGatherMerge.c
index 5625b12..76e07cc 100644
--- a/src/backend/executor/nodeGatherMerge.c
+++ b/src/backend/executor/nodeGatherMerge.c
@@ -202,6 +202,7 @@ ExecGatherMerge(PlanState *pstate)
if (!node->pei)
node->pei = ExecInitParallelPlan(node->ps.lefttree,
estate,
+ gm->initParam,
gm->num_workers,
node->tuples_needed);
else
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 76e7545..d9ff8a7 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -364,6 +364,7 @@ _copyGather(const Gather *from)
COPY_SCALAR_FIELD(rescan_param);
COPY_SCALAR_FIELD(single_copy);
COPY_SCALAR_FIELD(invisible);
+ COPY_BITMAPSET_FIELD(initParam);
return newnode;
}
@@ -391,6 +392,7 @@ _copyGatherMerge(const GatherMerge *from)
COPY_POINTER_FIELD(sortOperators, from->numCols * sizeof(Oid));
COPY_POINTER_FIELD(collations, from->numCols * sizeof(Oid));
COPY_POINTER_FIELD(nullsFirst, from->numCols * sizeof(bool));
+ COPY_BITMAPSET_FIELD(initParam);
return newnode;
}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index dc35df9..c97ee24 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -487,6 +487,7 @@ _outGather(StringInfo str, const Gather *node)
WRITE_INT_FIELD(rescan_param);
WRITE_BOOL_FIELD(single_copy);
WRITE_BOOL_FIELD(invisible);
+ WRITE_BITMAPSET_FIELD(initParam);
}
static void
@@ -517,6 +518,8 @@ _outGatherMerge(StringInfo str, const GatherMerge *node)
appendStringInfoString(str, " :nullsFirst");
for (i = 0; i < node->numCols; i++)
appendStringInfo(str, " %s", booltostr(node->nullsFirst[i]));
+
+ WRITE_BITMAPSET_FIELD(initParam);
}
static void
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 593658d..7eb67fc0 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2172,6 +2172,7 @@ _readGather(void)
READ_INT_FIELD(rescan_param);
READ_BOOL_FIELD(single_copy);
READ_BOOL_FIELD(invisible);
+ READ_BITMAPSET_FIELD(initParam);
READ_DONE();
}
@@ -2193,6 +2194,7 @@ _readGatherMerge(void)
READ_OID_ARRAY(sortOperators, local_node->numCols);
READ_OID_ARRAY(collations, local_node->numCols);
READ_BOOL_ARRAY(nullsFirst, local_node->numCols);
+ READ_BITMAPSET_FIELD(initParam);
READ_DONE();
}
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 9c74e39..d445477 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -6279,6 +6279,7 @@ make_gather(List *qptlist,
node->rescan_param = rescan_param;
node->single_copy = single_copy;
node->invisible = false;
+ node->initParam = NULL;
return node;
}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 607f7cd..712216e 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -376,6 +376,14 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
{
Gather *gather = makeNode(Gather);
+ /*
+ * If there are any initPlans attached to the formerly-top plan node,
+ * move them up to the Gather node; same as we do for Material node in
+ * materialize_finished_plan.
+ */
+ gather->plan.initPlan = top_plan->initPlan;
+ top_plan->initPlan = NIL;
+
gather->plan.targetlist = top_plan->targetlist;
gather->plan.qual = NIL;
gather->plan.lefttree = top_plan;
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index fa9a3f0..28a7f7e 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -107,6 +107,7 @@ static Node *fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context);
static bool fix_scan_expr_walker(Node *node, fix_scan_expr_context *context);
static void set_join_references(PlannerInfo *root, Join *join, int rtoffset);
static void set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset);
+static void set_param_references(PlannerInfo *root, Plan *plan);
static Node *convert_combining_aggrefs(Node *node, void *context);
static void set_dummy_tlist_references(Plan *plan, int rtoffset);
static indexed_tlist *build_tlist_index(List *tlist);
@@ -632,7 +633,10 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
case T_Gather:
case T_GatherMerge:
- set_upper_references(root, plan, rtoffset);
+ {
+ set_upper_references(root, plan, rtoffset);
+ set_param_references(root, plan);
+ }
break;
case T_Hash:
@@ -1782,6 +1786,51 @@ set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset)
}
/*
+ * set_param_references
+ * Initialize the initParam list in Gather or Gather merge node such that
+ * it contains reference of all the params that needs to be evaluated
+ * before execution of the node. It contains the initplan params that are
+ * being passed to the plan nodes below it.
+ */
+static void
+set_param_references(PlannerInfo *root, Plan *plan)
+{
+ Assert(IsA(plan, Gather) || IsA(plan, GatherMerge));
+
+ if (plan->lefttree->extParam)
+ {
+ PlannerInfo *proot;
+ Bitmapset *initSetParam = NULL;
+ ListCell *l;
+
+ for (proot = root; proot != NULL; proot = proot->parent_root)
+ {
+ foreach(l, proot->init_plans)
+ {
+ SubPlan *initsubplan = (SubPlan *) lfirst(l);
+ ListCell *l2;
+
+ foreach(l2, initsubplan->setParam)
+ {
+ initSetParam = bms_add_member(initSetParam, lfirst_int(l2));
+ }
+ }
+ }
+
+ /*
+ * Remember the list of all external initplan params that are used by
+ * the children of Gather or Gather merge node.
+ */
+ if (IsA(plan, Gather))
+ ((Gather *) plan)->initParam =
+ bms_intersect(plan->lefttree->extParam, initSetParam);
+ else
+ ((GatherMerge *) plan)->initParam =
+ bms_intersect(plan->lefttree->extParam, initSetParam);
+ }
+}
+
+/*
* Recursively scan an expression tree and convert Aggrefs to the proper
* intermediate form for combining aggregates. This means (1) replacing each
* one's argument list with a single argument that is the original Aggref
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 66e098f..d14ef31 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -1087,6 +1087,8 @@ bool
is_parallel_safe(PlannerInfo *root, Node *node)
{
max_parallel_hazard_context context;
+ PlannerInfo *proot;
+ ListCell *l;
/*
* Even if the original querytree contained nothing unsafe, we need to
@@ -1101,6 +1103,25 @@ is_parallel_safe(PlannerInfo *root, Node *node)
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_RESTRICTED;
context.safe_param_ids = NIL;
+
+ /*
+ * The params that refer to the same or parent query level are considered
+ * parallel-safe. The idea is that we compute such params at Gather or
+ * Gather Merge node and pass their value to workers.
+ */
+ for (proot = root; proot != NULL; proot = proot->parent_root)
+ {
+ foreach(l, proot->init_plans)
+ {
+ SubPlan *initsubplan = (SubPlan *) lfirst(l);
+ ListCell *l2;
+
+ foreach(l2, initsubplan->setParam)
+ context.safe_param_ids = lcons_int(lfirst_int(l2),
+ context.safe_param_ids);
+ }
+ }
+
return !max_parallel_hazard_walker(node, &context);
}
@@ -1225,7 +1246,8 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
* We can't pass Params to workers at the moment either, so they are also
* parallel-restricted, unless they are PARAM_EXTERN Params or are
* PARAM_EXEC Params listed in safe_param_ids, meaning they could be
- * generated within the worker.
+ * either generated within the worker or can be computed in master and
+ * then their value can be passed to the worker.
*/
else if (IsA(node, Param))
{
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 78d2247..5bbb63a 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -609,6 +609,7 @@ extern ExprEvalOp ExecEvalStepOp(ExprState *state, ExprEvalStep *op);
*/
extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
+extern void ExecEvalParamExecParams(Bitmapset *params, EState *estate);
extern void ExecEvalParamExtern(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
extern void ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op);
diff --git a/src/include/executor/execParallel.h b/src/include/executor/execParallel.h
index e1b3e7a..88e2757 100644
--- a/src/include/executor/execParallel.h
+++ b/src/include/executor/execParallel.h
@@ -35,7 +35,8 @@ typedef struct ParallelExecutorInfo
} ParallelExecutorInfo;
extern ParallelExecutorInfo *ExecInitParallelPlan(PlanState *planstate,
- EState *estate, int nworkers, int64 tuples_needed);
+ EState *estate, Bitmapset *initParam, int nworkers,
+ int64 tuples_needed);
extern void ExecParallelCreateReaders(ParallelExecutorInfo *pei);
extern void ExecParallelFinish(ParallelExecutorInfo *pei);
extern void ExecParallelCleanup(ParallelExecutorInfo *pei);
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index a127682..9b38d44 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -841,6 +841,8 @@ typedef struct Gather
int rescan_param; /* ID of Param that signals a rescan, or -1 */
bool single_copy; /* don't execute plan more than once */
bool invisible; /* suppress EXPLAIN display (for testing)? */
+ Bitmapset *initParam; /* param id's of initplans which are referred
+ * at gather or one of it's child node */
} Gather;
/* ------------
@@ -858,6 +860,8 @@ typedef struct GatherMerge
Oid *sortOperators; /* OIDs of operators to sort them by */
Oid *collations; /* OIDs of collations */
bool *nullsFirst; /* NULLS FIRST/LAST directions */
+ Bitmapset *initParam; /* param id's of initplans which are referred
+ * at gather merge or one of it's child node */
} GatherMerge;
/* ----------------
diff --git a/src/test/regress/expected/select_parallel.out b/src/test/regress/expected/select_parallel.out
index ac9ad06..5447e08 100644
--- a/src/test/regress/expected/select_parallel.out
+++ b/src/test/regress/expected/select_parallel.out
@@ -159,6 +159,41 @@ explain (costs off)
(4 rows)
alter table tenk2 reset (parallel_workers);
+-- test parallel plan for a query containing initplan.
+set enable_indexscan = off;
+set enable_indexonlyscan = off;
+set enable_bitmapscan = off;
+alter table tenk2 set (parallel_workers = 2);
+explain (costs off)
+ select count(*) from tenk1
+ where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+ QUERY PLAN
+------------------------------------------------------
+ Aggregate
+ InitPlan 1 (returns $2)
+ -> Finalize Aggregate
+ -> Gather
+ Workers Planned: 2
+ -> Partial Aggregate
+ -> Parallel Seq Scan on tenk2
+ -> Gather
+ Workers Planned: 4
+ Params Evaluated: $2
+ -> Parallel Seq Scan on tenk1
+ Filter: (unique1 = $2)
+(12 rows)
+
+select count(*) from tenk1
+ where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+ count
+-------
+ 1
+(1 row)
+
+reset enable_indexscan;
+reset enable_indexonlyscan;
+reset enable_bitmapscan;
+alter table tenk2 reset (parallel_workers);
-- test parallel index scans.
set enable_seqscan to off;
set enable_bitmapscan to off;
diff --git a/src/test/regress/sql/select_parallel.sql b/src/test/regress/sql/select_parallel.sql
index 495f033..dd28169 100644
--- a/src/test/regress/sql/select_parallel.sql
+++ b/src/test/regress/sql/select_parallel.sql
@@ -58,6 +58,23 @@ explain (costs off)
(select ten from tenk2);
alter table tenk2 reset (parallel_workers);
+-- test parallel plan for a query containing initplan.
+set enable_indexscan = off;
+set enable_indexonlyscan = off;
+set enable_bitmapscan = off;
+alter table tenk2 set (parallel_workers = 2);
+
+explain (costs off)
+ select count(*) from tenk1
+ where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+select count(*) from tenk1
+ where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+
+reset enable_indexscan;
+reset enable_indexonlyscan;
+reset enable_bitmapscan;
+alter table tenk2 reset (parallel_workers);
+
-- test parallel index scans.
set enable_seqscan to off;
set enable_bitmapscan to off;
On Sat, Nov 11, 2017 at 7:19 AM, Amit Kapila <amit.kapila16@gmail.com> wrote:
I have tried to follow the practice we have used for param extern
params (SerializeParamList is in params.c) and most of the handling of
initplans is done in nodeSubplan.c, so I choose to keep the newly
added functions there. However, I think keeping it in execParallel.c
is also okay as we do it for serialize plan.
To me it feels like there is only a very loose coupling between this
code and what's currently in nodeSubplan.c, but maybe I'm wrong. I am
also not sure execParallel.c is the perfect place either; we might
eventually split execParallel.c into multiple files if it accumulates
too many things that are too different from each other.
I think it would need some work in execution as well because the size
won't be fixed in that case for varchar type of params. We might end
up with something different as well.
Hmm, true. But don't we also have that problem with the patch as
written? I mean, didn't we determine at some point that the value of
an InitPlan can change, if the initplan depends on a parameter that is
correlated but not directly correlated? The existence of
ExecReScanSetParamPlan() seems to suggest that this is indeed
possible, and that means that the parameter could be reevaluated and
evaluate, on the second time through, to a value that requires more
storage than before. That would be a problem, because
ExecParallelReInitializeDSM reuses the old DSM.
I broke a lot of long lines in your version of
the patch into multiple lines; please try to be attentive to this
issue when writing patches in general, as it is a bit tedious to go
through and insert line breaks in many places.Okay, but I sometimes rely on pgindent for such things as for few
things it becomes difficult to decide which way it will be better.
pgindent doesn't in general break long lines. There are a few cases
where it does, like reflowing comments. But mostly you have to do
that manually. For instance, if you write a =
verylongfunctionname(longargument(thing), stuff(thing), foobar(thing,
thing, thing)) it's going to keep all that on one line. If you insert
line breaks between the arguments it will keep them, though. I think
it's worth doing something like:
git diff | awk 'length($0)>81'
on your patches before you submit them. If you see things in there
that can be reformatted to make the long lines shorter, do it.
I have fixed the first two in attached patch and left the last one as
I was not sure what you have in mind
Oh, yeah, that should be changed too, something like "Serialize
PARAM_EXEC parameters." Sorry, I thought I caught all of the
references to initplans specifically, but I guess I missed a few.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On Tue, Nov 14, 2017 at 2:39 AM, Robert Haas <robertmhaas@gmail.com> wrote:
On Sat, Nov 11, 2017 at 7:19 AM, Amit Kapila <amit.kapila16@gmail.com> wrote:
I have tried to follow the practice we have used for param extern
params (SerializeParamList is in params.c) and most of the handling of
initplans is done in nodeSubplan.c, so I choose to keep the newly
added functions there. However, I think keeping it in execParallel.c
is also okay as we do it for serialize plan.To me it feels like there is only a very loose coupling between this
code and what's currently in nodeSubplan.c, but maybe I'm wrong. I am
also not sure execParallel.c is the perfect place either; we might
eventually split execParallel.c into multiple files if it accumulates
too many things that are too different from each other.
Another possibility is subselect.c, because it contains initplan
stuff, however, most of the things are related to planning, so not
sure. I think nodeSubplan.c won't be a bad choice as it is mentioned
in the file header that it is concerned about initplan stuff.
I think it would need some work in execution as well because the size
won't be fixed in that case for varchar type of params. We might end
up with something different as well.Hmm, true. But don't we also have that problem with the patch as
written? I mean, didn't we determine at some point that the value of
an InitPlan can change, if the initplan depends on a parameter that is
correlated but not directly correlated? The existence of
ExecReScanSetParamPlan() seems to suggest that this is indeed
possible, and that means that the parameter could be reevaluated and
evaluate, on the second time through, to a value that requires more
storage than before. That would be a problem, because
ExecParallelReInitializeDSM reuses the old DSM.
I think this would have been a matter of concern if we use initplans
below Gather/Gather Merge. The patch uses initplans which are between
current query level and root. So, I think we don't need to reevaluate
such parameters. Let us try to see it via example:
QUERY PLAN
----------------------------------------------------------------------------------------
Aggregate (cost=62.08..62.08 rows=1 width=8)
InitPlan 1 (returns $1)
-> Finalize Aggregate (cost=20.64..20.65 rows=1 width=8)
-> Gather (cost=20.63..20.64 rows=2 width=8)
Workers Planned: 2
-> Partial Aggregate (cost=20.63..20.64 rows=1 width=8)
-> Parallel Seq Scan on t3 (cost=0.00..18.50
rows=850 width=4)
-> Hash Join (cost=20.75..41.42 rows=1 width=4)
Hash Cond: (t1.j = t2.j)
-> Gather (cost=0.00..20.63 rows=10 width=12)
Workers Planned: 2
Params Evaluated: $1
-> Parallel Seq Scan on t1 (cost=0.00..20.63 rows=4 width=12)
Filter: (k = $1)
-> Hash (cost=20.63..20.63 rows=10 width=8)
-> Gather (cost=0.00..20.63 rows=10 width=8)
Workers Planned: 2
Params Evaluated: $1
-> Parallel Seq Scan on t2 (cost=0.00..20.63
rows=4 width=8)
Filter: (k = $1)
(20 rows)
Now, here even if initplan would have been an undirect correlated
plan, it wouldn't have been a problem, because we don't need to
reevaluate it for Gather node below Hash.
Am I missing something? Do you have some test or shape of the plan in
mind which can cause a problem?
I broke a lot of long lines in your version of
the patch into multiple lines; please try to be attentive to this
issue when writing patches in general, as it is a bit tedious to go
through and insert line breaks in many places.Okay, but I sometimes rely on pgindent for such things as for few
things it becomes difficult to decide which way it will be better.pgindent doesn't in general break long lines. There are a few cases
where it does, like reflowing comments. But mostly you have to do
that manually. For instance, if you write a =
verylongfunctionname(longargument(thing), stuff(thing), foobar(thing,
thing, thing)) it's going to keep all that on one line. If you insert
line breaks between the arguments it will keep them, though. I think
it's worth doing something like:git diff | awk 'length($0)>81'
on your patches before you submit them. If you see things in there
that can be reformatted to make the long lines shorter, do it.
Okay, point noted.
I have fixed the first two in attached patch and left the last one as
I was not sure what you have in mindOh, yeah, that should be changed too, something like "Serialize
PARAM_EXEC parameters." Sorry, I thought I caught all of the
references to initplans specifically, but I guess I missed a few.
No issues, I can go through it once more to change the comments
wherever initplans is used after we settle on the above discussion.
If we decide that initplan and other PARAM_EXEC params need different
treatment, then we might want to retain initplans in the comments.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
On Tue, Nov 14, 2017 at 12:00 AM, Amit Kapila <amit.kapila16@gmail.com> wrote:
I think this would have been a matter of concern if we use initplans
below Gather/Gather Merge. The patch uses initplans which are between
current query level and root. So, I think we don't need to reevaluate
such parameters. Let us try to see it via example:QUERY PLAN
----------------------------------------------------------------------------------------
Aggregate (cost=62.08..62.08 rows=1 width=8)
InitPlan 1 (returns $1)
-> Finalize Aggregate (cost=20.64..20.65 rows=1 width=8)
-> Gather (cost=20.63..20.64 rows=2 width=8)
Workers Planned: 2
-> Partial Aggregate (cost=20.63..20.64 rows=1 width=8)
-> Parallel Seq Scan on t3 (cost=0.00..18.50
rows=850 width=4)
-> Hash Join (cost=20.75..41.42 rows=1 width=4)
Hash Cond: (t1.j = t2.j)
-> Gather (cost=0.00..20.63 rows=10 width=12)
Workers Planned: 2
Params Evaluated: $1
-> Parallel Seq Scan on t1 (cost=0.00..20.63 rows=4 width=12)
Filter: (k = $1)
-> Hash (cost=20.63..20.63 rows=10 width=8)
-> Gather (cost=0.00..20.63 rows=10 width=8)
Workers Planned: 2
Params Evaluated: $1
-> Parallel Seq Scan on t2 (cost=0.00..20.63
rows=4 width=8)
Filter: (k = $1)
(20 rows)Now, here even if initplan would have been an undirect correlated
plan, it wouldn't have been a problem, because we don't need to
reevaluate it for Gather node below Hash.Am I missing something? Do you have some test or shape of the plan in
mind which can cause a problem?
The problem would happen if the plan for InitPlan $1 in the above
example itself referenced a parameter from an upper query level, and
the value of that parameter changed, and then this section of the plan
tree was rescanned. I'm not sure I can write a query like that
off-hand, but I think it's possible to do so.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On Tue, Nov 14, 2017 at 10:37 AM, Robert Haas <robertmhaas@gmail.com> wrote:
The problem would happen if the plan for InitPlan $1 in the above
example itself referenced a parameter from an upper query level, and
the value of that parameter changed, and then this section of the plan
tree was rescanned. I'm not sure I can write a query like that
off-hand, but I think it's possible to do so.
OK, here's an example:
regression=# explain select * from tenk1 a where unique1 = (select
unique1 from tenk1 b where (select string4 from tenk1 c where c.ten =
a.ten order by unique1 limit 1) < b.string4 limit 1);
QUERY PLAN
----------------------------------------------------------------------------------------------------------
Seq Scan on tenk1 a (cost=0.00..22051.31 rows=50 width=244)
Filter: (unique1 = (SubPlan 2))
SubPlan 2
-> Limit (cost=2.01..2.16 rows=1 width=4)
InitPlan 1 (returns $1)
-> Limit (cost=0.29..2.01 rows=1 width=68)
-> Index Scan using tenk1_unique1 on tenk1 c
(cost=0.29..1727.20 rows=1000 width=68)
Filter: (ten = a.ten)
-> Seq Scan on tenk1 b (cost=0.00..483.00 rows=3333 width=4)
Filter: ($1 < string4)
(10 rows)
InitPlan 1 has got to be re-evaluated for every row in the "Seq Scan
on tenk1 a", and each iteration could return a different value for $1,
and some of those values might be wider than others -- well, not
really, because in this example string4 is actually declared as type
"name". But if you imagine it as type "text" then you can see the
problem.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Robert Haas <robertmhaas@gmail.com> writes:
On Tue, Nov 14, 2017 at 12:00 AM, Amit Kapila <amit.kapila16@gmail.com> wrote:
Am I missing something? Do you have some test or shape of the plan in
mind which can cause a problem?
The problem would happen if the plan for InitPlan $1 in the above
example itself referenced a parameter from an upper query level, and
the value of that parameter changed, and then this section of the plan
tree was rescanned. I'm not sure I can write a query like that
off-hand, but I think it's possible to do so.
Yeah, I'm sure it is. I have a vague recollection that there might be
existing regression test cases exercising such things, though I won't
swear to that. The "orderstest" bit in subselect.sql looks like it
might be meant to do that...
regards, tom lane
On Tue, Nov 14, 2017 at 11:00 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Yeah, I'm sure it is. I have a vague recollection that there might be
existing regression test cases exercising such things, though I won't
swear to that. The "orderstest" bit in subselect.sql looks like it
might be meant to do that...
Here's an updated patch that attempts to work around this problem using DSA.
It could use a test case that actually tickles the new logic in
ExecParallelReinitialize ... this is mostly just to show the concept.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Attachments:
initplan-pushdown-with-dsa.patchapplication/octet-stream; name=initplan-pushdown-with-dsa.patchDownload
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 8f7062cd6e..447f69d044 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -107,6 +107,7 @@ static void show_tidbitmap_info(BitmapHeapScanState *planstate,
static void show_instrumentation_count(const char *qlabel, int which,
PlanState *planstate, ExplainState *es);
static void show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es);
+static void show_eval_params(Bitmapset *bms_params, ExplainState *es);
static const char *explain_get_index_name(Oid indexId);
static void show_buffer_usage(ExplainState *es, const BufferUsage *usage);
static void ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir,
@@ -1441,6 +1442,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
planstate, es);
ExplainPropertyInteger("Workers Planned",
gather->num_workers, es);
+
+ /* Show params evaluated at gather node */
+ if (gather->initParam)
+ show_eval_params(gather->initParam, es);
+
if (es->analyze)
{
int nworkers;
@@ -1463,6 +1469,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
planstate, es);
ExplainPropertyInteger("Workers Planned",
gm->num_workers, es);
+
+ /* Show params evaluated at gather-merge node */
+ if (gm->initParam)
+ show_eval_params(gm->initParam, es);
+
if (es->analyze)
{
int nworkers;
@@ -2488,6 +2499,29 @@ show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es)
}
/*
+ * Show initplan params evaluated at Gather or Gather Merge node.
+ */
+static void
+show_eval_params(Bitmapset *bms_params, ExplainState *es)
+{
+ int paramid = -1;
+ List *params = NIL;
+
+ Assert(bms_params);
+
+ while ((paramid = bms_next_member(bms_params, paramid)) >= 0)
+ {
+ char param[32];
+
+ snprintf(param, sizeof(param), "$%d", paramid);
+ params = lappend(params, pstrdup(param));
+ }
+
+ if (params)
+ ExplainPropertyList("Params Evaluated", params, es);
+}
+
+/*
* Fetch the name of an index in an EXPLAIN
*
* We allow plugins to get control here so that plans involving hypothetical
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index a0f537b706..6c4612dad4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -1927,6 +1927,33 @@ ExecEvalParamExec(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
}
/*
+ * ExecEvalParamExecParams
+ *
+ * Execute the subplan stored in PARAM_EXEC initplans params, if not executed
+ * till now.
+ */
+void
+ExecEvalParamExecParams(Bitmapset *params, EState *estate)
+{
+ ParamExecData *prm;
+ int paramid;
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ prm = &(estate->es_param_exec_vals[paramid]);
+
+ if (prm->execPlan != NULL)
+ {
+ /* Parameter not evaluated yet, so go do it */
+ ExecSetParamPlan(prm->execPlan, GetPerTupleExprContext(estate));
+ /* ExecSetParamPlan should have processed this param... */
+ Assert(prm->execPlan == NULL);
+ }
+ }
+}
+
+/*
* Evaluate a PARAM_EXTERN parameter.
*
* PARAM_EXTERN parameters must be sought in ecxt_param_list_info.
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index fd7e7cbf3d..9b639c5a30 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -23,6 +23,7 @@
#include "postgres.h"
+#include "executor/execExpr.h"
#include "executor/execParallel.h"
#include "executor/executor.h"
#include "executor/nodeBitmapHeapscan.h"
@@ -38,7 +39,9 @@
#include "optimizer/planner.h"
#include "storage/spin.h"
#include "tcop/tcopprot.h"
+#include "utils/datum.h"
#include "utils/dsa.h"
+#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/snapmgr.h"
#include "pgstat.h"
@@ -50,7 +53,7 @@
*/
#define PARALLEL_KEY_EXECUTOR_FIXED UINT64CONST(0xE000000000000001)
#define PARALLEL_KEY_PLANNEDSTMT UINT64CONST(0xE000000000000002)
-#define PARALLEL_KEY_PARAMS UINT64CONST(0xE000000000000003)
+#define PARALLEL_KEY_PARAMLISTINFO UINT64CONST(0xE000000000000003)
#define PARALLEL_KEY_BUFFER_USAGE UINT64CONST(0xE000000000000004)
#define PARALLEL_KEY_TUPLE_QUEUE UINT64CONST(0xE000000000000005)
#define PARALLEL_KEY_INSTRUMENTATION UINT64CONST(0xE000000000000006)
@@ -65,6 +68,7 @@
typedef struct FixedParallelExecutorState
{
int64 tuples_needed; /* tuple bound, see ExecSetTupleBound */
+ dsa_pointer param_exec;
} FixedParallelExecutorState;
/*
@@ -267,6 +271,133 @@ ExecParallelEstimate(PlanState *planstate, ExecParallelEstimateContext *e)
}
/*
+ * Estimate the amount of space required to serialize the indicated parameters.
+ */
+static Size
+EstimateParamExecSpace(EState *estate, Bitmapset *params)
+{
+ int paramid;
+ Size sz = sizeof(int);
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ Oid typeOid;
+ int16 typLen;
+ bool typByVal;
+ ParamExecData *prm;
+
+ prm = &(estate->es_param_exec_vals[paramid]);
+ typeOid = list_nth_oid(estate->es_plannedstmt->paramExecTypes,
+ paramid);
+
+ sz = add_size(sz, sizeof(int)); /* space for paramid */
+
+ /* space for datum/isnull */
+ if (OidIsValid(typeOid))
+ get_typlenbyval(typeOid, &typLen, &typByVal);
+ else
+ {
+ /* If no type OID, assume by-value, like copyParamList does. */
+ typLen = sizeof(Datum);
+ typByVal = true;
+ }
+ sz = add_size(sz,
+ datumEstimateSpace(prm->value, prm->isnull,
+ typByVal, typLen));
+ }
+ return sz;
+}
+
+/*
+ * Serialize specified PARAM_EXEC parameters.
+ *
+ * We write the number of parameters first, as a 4-byte integer, and then
+ * write details for each parameter in turn. The details for each parameter
+ * consist of a 4-byte paramid (location of param in execution time internal
+ * parameter array) and then the datum as serialized by datumSerialize().
+ */
+static dsa_pointer
+SerializeParamExecParams(EState *estate, Bitmapset *params)
+{
+ Size size;
+ int nparams;
+ int paramid;
+ ParamExecData *prm;
+ dsa_pointer handle;
+ char *start_address;
+
+ /* Allocate enough space for the current parameter values. */
+ size = EstimateParamExecSpace(estate, params);
+ handle = dsa_allocate(estate->es_query_dsa, size);
+ start_address = dsa_get_address(estate->es_query_dsa, handle);
+
+ /* First write the number of parameters as a 4-byte integer. */
+ nparams = bms_num_members(params);
+ memcpy(start_address, &nparams, sizeof(int));
+ start_address += sizeof(int);
+
+ /* Write details for each parameter in turn. */
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ Oid typeOid;
+ int16 typLen;
+ bool typByVal;
+
+ prm = &(estate->es_param_exec_vals[paramid]);
+ typeOid = list_nth_oid(estate->es_plannedstmt->paramExecTypes,
+ paramid);
+
+ /* Write paramid. */
+ memcpy(start_address, ¶mid, sizeof(int));
+ start_address += sizeof(int);
+
+ /* Write datum/isnull */
+ if (OidIsValid(typeOid))
+ get_typlenbyval(typeOid, &typLen, &typByVal);
+ else
+ {
+ /* If no type OID, assume by-value, like copyParamList does. */
+ typLen = sizeof(Datum);
+ typByVal = true;
+ }
+ datumSerialize(prm->value, prm->isnull, typByVal, typLen,
+ &start_address);
+ }
+
+ return handle;
+}
+
+/*
+ * Restore specified PARAM_EXEC parameters.
+ */
+static void
+RestoreParamExecParams(char *start_address, EState *estate)
+{
+ int nparams;
+ int i;
+ int paramid;
+
+ memcpy(&nparams, start_address, sizeof(int));
+ start_address += sizeof(int);
+
+ for (i = 0; i < nparams; i++)
+ {
+ ParamExecData *prm;
+
+ /* Read paramid */
+ memcpy(¶mid, start_address, sizeof(int));
+ start_address += sizeof(int);
+ prm = &(estate->es_param_exec_vals[paramid]);
+
+ /* Read datum/isnull. */
+ prm->value = datumRestore(&start_address, &prm->isnull);
+ prm->execPlan = NULL;
+ }
+}
+
+/*
* Initialize the dynamic shared memory segment that will be used to control
* parallel execution.
*/
@@ -395,7 +526,8 @@ ExecParallelSetupTupleQueues(ParallelContext *pcxt, bool reinitialize)
* execution and return results to the main backend.
*/
ParallelExecutorInfo *
-ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
+ExecInitParallelPlan(PlanState *planstate, EState *estate,
+ Bitmapset *sendParams, int nworkers,
int64 tuples_needed)
{
ParallelExecutorInfo *pei;
@@ -405,17 +537,20 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
FixedParallelExecutorState *fpes;
char *pstmt_data;
char *pstmt_space;
- char *param_space;
+ char *paramlistinfo_space;
BufferUsage *bufusage_space;
SharedExecutorInstrumentation *instrumentation = NULL;
int pstmt_len;
- int param_len;
+ int paramlistinfo_len;
int instrumentation_len = 0;
int instrument_offset = 0;
Size dsa_minsize = dsa_minimum_size();
char *query_string;
int query_len;
+ /* Force parameters we're going to pass to workers to be evaluated. */
+ ExecEvalParamExecParams(sendParams, estate);
+
/* Allocate object for return value. */
pei = palloc0(sizeof(ParallelExecutorInfo));
pei->finished = false;
@@ -450,8 +585,8 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
shm_toc_estimate_keys(&pcxt->estimator, 1);
/* Estimate space for serialized ParamListInfo. */
- param_len = EstimateParamListSpace(estate->es_param_list_info);
- shm_toc_estimate_chunk(&pcxt->estimator, param_len);
+ paramlistinfo_len = EstimateParamListSpace(estate->es_param_list_info);
+ shm_toc_estimate_chunk(&pcxt->estimator, paramlistinfo_len);
shm_toc_estimate_keys(&pcxt->estimator, 1);
/*
@@ -511,6 +646,7 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
/* Store fixed-size state. */
fpes = shm_toc_allocate(pcxt->toc, sizeof(FixedParallelExecutorState));
fpes->tuples_needed = tuples_needed;
+ fpes->param_exec = InvalidDsaPointer;
shm_toc_insert(pcxt->toc, PARALLEL_KEY_EXECUTOR_FIXED, fpes);
/* Store query string */
@@ -524,9 +660,9 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
shm_toc_insert(pcxt->toc, PARALLEL_KEY_PLANNEDSTMT, pstmt_space);
/* Store serialized ParamListInfo. */
- param_space = shm_toc_allocate(pcxt->toc, param_len);
- shm_toc_insert(pcxt->toc, PARALLEL_KEY_PARAMS, param_space);
- SerializeParamList(estate->es_param_list_info, ¶m_space);
+ paramlistinfo_space = shm_toc_allocate(pcxt->toc, paramlistinfo_len);
+ shm_toc_insert(pcxt->toc, PARALLEL_KEY_PARAMLISTINFO, paramlistinfo_space);
+ SerializeParamList(estate->es_param_list_info, ¶mlistinfo_space);
/* Allocate space for each worker's BufferUsage; no need to initialize. */
bufusage_space = shm_toc_allocate(pcxt->toc,
@@ -577,13 +713,25 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
pei->area = dsa_create_in_place(area_space, dsa_minsize,
LWTRANCHE_PARALLEL_QUERY_DSA,
pcxt->seg);
- }
- /*
- * Make the area available to executor nodes running in the leader. See
- * also ParallelQueryMain which makes it available to workers.
- */
- estate->es_query_dsa = pei->area;
+ /*
+ * Make the area available to executor nodes running in the leader.
+ * See also ParallelQueryMain which makes it available to workers.
+ */
+ estate->es_query_dsa = pei->area;
+
+ /*
+ * Serialize parameters, if any, using DSA storage. We don't dare use
+ * the main parallel query DSM for this because we might relaunch
+ * workers after the values (and thus the amount of storage required
+ * has changed).
+ */
+ if (!bms_is_empty(sendParams))
+ {
+ pei->param_exec = SerializeParamExecParams(estate, sendParams);
+ fpes->param_exec = pei->param_exec;
+ }
+ }
/*
* Give parallel-aware nodes a chance to initialize their shared data.
@@ -640,8 +788,12 @@ ExecParallelCreateReaders(ParallelExecutorInfo *pei)
*/
void
ExecParallelReinitialize(PlanState *planstate,
- ParallelExecutorInfo *pei)
+ ParallelExecutorInfo *pei,
+ Bitmapset *sendParams)
{
+ EState *estate = planstate->state;
+ FixedParallelExecutorState *fpes;
+
/* Old workers must already be shut down */
Assert(pei->finished);
@@ -650,6 +802,22 @@ ExecParallelReinitialize(PlanState *planstate,
pei->reader = NULL;
pei->finished = false;
+ fpes = shm_toc_lookup(pei->pcxt->toc, PARALLEL_KEY_EXECUTOR_FIXED, false);
+
+ /* Free any serialized parameters from the last round. */
+ if (DsaPointerIsValid(fpes->param_exec))
+ {
+ dsa_free(estate->es_query_dsa, fpes->param_exec);
+ fpes->param_exec = InvalidDsaPointer;
+ }
+
+ /* Serialize current parameter values if required. */
+ if (!bms_is_empty(sendParams))
+ {
+ pei->param_exec = SerializeParamExecParams(estate, sendParams);
+ fpes->param_exec = pei->param_exec;
+ }
+
/* Traverse plan tree and let each child node reset associated state. */
ExecParallelReInitializeDSM(planstate, pei->pcxt);
}
@@ -882,7 +1050,7 @@ ExecParallelGetQueryDesc(shm_toc *toc, DestReceiver *receiver,
pstmt = (PlannedStmt *) stringToNode(pstmtspace);
/* Reconstruct ParamListInfo. */
- paramspace = shm_toc_lookup(toc, PARALLEL_KEY_PARAMS, false);
+ paramspace = shm_toc_lookup(toc, PARALLEL_KEY_PARAMLISTINFO, false);
paramLI = RestoreParamList(¶mspace);
/*
@@ -1046,6 +1214,14 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
/* Special executor initialization steps for parallel workers */
queryDesc->planstate->state->es_query_dsa = area;
+ if (DsaPointerIsValid(fpes->param_exec))
+ {
+ char *paramexec_space;
+
+ paramexec_space = dsa_get_address(area, fpes->param_exec);
+ RestoreParamExecParams(paramexec_space, queryDesc->estate);
+
+ }
ExecParallelInitializeWorker(queryDesc->planstate, toc);
/* Pass down any tuple bound */
diff --git a/src/backend/executor/nodeGather.c b/src/backend/executor/nodeGather.c
index 639f4f5af8..9e1b36f283 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -158,11 +158,13 @@ ExecGather(PlanState *pstate)
if (!node->pei)
node->pei = ExecInitParallelPlan(node->ps.lefttree,
estate,
+ gather->initParam,
gather->num_workers,
node->tuples_needed);
else
ExecParallelReinitialize(node->ps.lefttree,
- node->pei);
+ node->pei,
+ gather->initParam);
/*
* Register backend workers. We might not get as many as we
diff --git a/src/backend/executor/nodeGatherMerge.c b/src/backend/executor/nodeGatherMerge.c
index 5625b12521..54f7eaa5f9 100644
--- a/src/backend/executor/nodeGatherMerge.c
+++ b/src/backend/executor/nodeGatherMerge.c
@@ -202,11 +202,13 @@ ExecGatherMerge(PlanState *pstate)
if (!node->pei)
node->pei = ExecInitParallelPlan(node->ps.lefttree,
estate,
+ gm->initParam,
gm->num_workers,
node->tuples_needed);
else
ExecParallelReinitialize(node->ps.lefttree,
- node->pei);
+ node->pei,
+ gm->initParam);
/* Try to launch workers. */
pcxt = node->pei->pcxt;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 76e75459b4..d9ff8a7e51 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -364,6 +364,7 @@ _copyGather(const Gather *from)
COPY_SCALAR_FIELD(rescan_param);
COPY_SCALAR_FIELD(single_copy);
COPY_SCALAR_FIELD(invisible);
+ COPY_BITMAPSET_FIELD(initParam);
return newnode;
}
@@ -391,6 +392,7 @@ _copyGatherMerge(const GatherMerge *from)
COPY_POINTER_FIELD(sortOperators, from->numCols * sizeof(Oid));
COPY_POINTER_FIELD(collations, from->numCols * sizeof(Oid));
COPY_POINTER_FIELD(nullsFirst, from->numCols * sizeof(bool));
+ COPY_BITMAPSET_FIELD(initParam);
return newnode;
}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index dc35df9e4f..c97ee24ade 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -487,6 +487,7 @@ _outGather(StringInfo str, const Gather *node)
WRITE_INT_FIELD(rescan_param);
WRITE_BOOL_FIELD(single_copy);
WRITE_BOOL_FIELD(invisible);
+ WRITE_BITMAPSET_FIELD(initParam);
}
static void
@@ -517,6 +518,8 @@ _outGatherMerge(StringInfo str, const GatherMerge *node)
appendStringInfoString(str, " :nullsFirst");
for (i = 0; i < node->numCols; i++)
appendStringInfo(str, " %s", booltostr(node->nullsFirst[i]));
+
+ WRITE_BITMAPSET_FIELD(initParam);
}
static void
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 593658dd8a..7eb67fc040 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2172,6 +2172,7 @@ _readGather(void)
READ_INT_FIELD(rescan_param);
READ_BOOL_FIELD(single_copy);
READ_BOOL_FIELD(invisible);
+ READ_BITMAPSET_FIELD(initParam);
READ_DONE();
}
@@ -2193,6 +2194,7 @@ _readGatherMerge(void)
READ_OID_ARRAY(sortOperators, local_node->numCols);
READ_OID_ARRAY(collations, local_node->numCols);
READ_BOOL_ARRAY(nullsFirst, local_node->numCols);
+ READ_BITMAPSET_FIELD(initParam);
READ_DONE();
}
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 9c74e39bd3..d4454779ee 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -6279,6 +6279,7 @@ make_gather(List *qptlist,
node->rescan_param = rescan_param;
node->single_copy = single_copy;
node->invisible = false;
+ node->initParam = NULL;
return node;
}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 90fd9cc959..cbd02069c5 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -376,6 +376,14 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
{
Gather *gather = makeNode(Gather);
+ /*
+ * If there are any initPlans attached to the formerly-top plan node,
+ * move them up to the Gather node; same as we do for Material node in
+ * materialize_finished_plan.
+ */
+ gather->plan.initPlan = top_plan->initPlan;
+ top_plan->initPlan = NIL;
+
gather->plan.targetlist = top_plan->targetlist;
gather->plan.qual = NIL;
gather->plan.lefttree = top_plan;
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index fa9a3f0b47..28a7f7ec45 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -107,6 +107,7 @@ static Node *fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context);
static bool fix_scan_expr_walker(Node *node, fix_scan_expr_context *context);
static void set_join_references(PlannerInfo *root, Join *join, int rtoffset);
static void set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset);
+static void set_param_references(PlannerInfo *root, Plan *plan);
static Node *convert_combining_aggrefs(Node *node, void *context);
static void set_dummy_tlist_references(Plan *plan, int rtoffset);
static indexed_tlist *build_tlist_index(List *tlist);
@@ -632,7 +633,10 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
case T_Gather:
case T_GatherMerge:
- set_upper_references(root, plan, rtoffset);
+ {
+ set_upper_references(root, plan, rtoffset);
+ set_param_references(root, plan);
+ }
break;
case T_Hash:
@@ -1782,6 +1786,51 @@ set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset)
}
/*
+ * set_param_references
+ * Initialize the initParam list in Gather or Gather merge node such that
+ * it contains reference of all the params that needs to be evaluated
+ * before execution of the node. It contains the initplan params that are
+ * being passed to the plan nodes below it.
+ */
+static void
+set_param_references(PlannerInfo *root, Plan *plan)
+{
+ Assert(IsA(plan, Gather) || IsA(plan, GatherMerge));
+
+ if (plan->lefttree->extParam)
+ {
+ PlannerInfo *proot;
+ Bitmapset *initSetParam = NULL;
+ ListCell *l;
+
+ for (proot = root; proot != NULL; proot = proot->parent_root)
+ {
+ foreach(l, proot->init_plans)
+ {
+ SubPlan *initsubplan = (SubPlan *) lfirst(l);
+ ListCell *l2;
+
+ foreach(l2, initsubplan->setParam)
+ {
+ initSetParam = bms_add_member(initSetParam, lfirst_int(l2));
+ }
+ }
+ }
+
+ /*
+ * Remember the list of all external initplan params that are used by
+ * the children of Gather or Gather merge node.
+ */
+ if (IsA(plan, Gather))
+ ((Gather *) plan)->initParam =
+ bms_intersect(plan->lefttree->extParam, initSetParam);
+ else
+ ((GatherMerge *) plan)->initParam =
+ bms_intersect(plan->lefttree->extParam, initSetParam);
+ }
+}
+
+/*
* Recursively scan an expression tree and convert Aggrefs to the proper
* intermediate form for combining aggregates. This means (1) replacing each
* one's argument list with a single argument that is the original Aggref
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 66e098f488..d14ef31eae 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -1087,6 +1087,8 @@ bool
is_parallel_safe(PlannerInfo *root, Node *node)
{
max_parallel_hazard_context context;
+ PlannerInfo *proot;
+ ListCell *l;
/*
* Even if the original querytree contained nothing unsafe, we need to
@@ -1101,6 +1103,25 @@ is_parallel_safe(PlannerInfo *root, Node *node)
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_RESTRICTED;
context.safe_param_ids = NIL;
+
+ /*
+ * The params that refer to the same or parent query level are considered
+ * parallel-safe. The idea is that we compute such params at Gather or
+ * Gather Merge node and pass their value to workers.
+ */
+ for (proot = root; proot != NULL; proot = proot->parent_root)
+ {
+ foreach(l, proot->init_plans)
+ {
+ SubPlan *initsubplan = (SubPlan *) lfirst(l);
+ ListCell *l2;
+
+ foreach(l2, initsubplan->setParam)
+ context.safe_param_ids = lcons_int(lfirst_int(l2),
+ context.safe_param_ids);
+ }
+ }
+
return !max_parallel_hazard_walker(node, &context);
}
@@ -1225,7 +1246,8 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
* We can't pass Params to workers at the moment either, so they are also
* parallel-restricted, unless they are PARAM_EXTERN Params or are
* PARAM_EXEC Params listed in safe_param_ids, meaning they could be
- * generated within the worker.
+ * either generated within the worker or can be computed in master and
+ * then their value can be passed to the worker.
*/
else if (IsA(node, Param))
{
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 78d2247816..5bbb63a9d8 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -609,6 +609,7 @@ extern ExprEvalOp ExecEvalStepOp(ExprState *state, ExprEvalStep *op);
*/
extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
+extern void ExecEvalParamExecParams(Bitmapset *params, EState *estate);
extern void ExecEvalParamExtern(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
extern void ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op);
diff --git a/src/include/executor/execParallel.h b/src/include/executor/execParallel.h
index e1b3e7af1f..3fc2876a53 100644
--- a/src/include/executor/execParallel.h
+++ b/src/include/executor/execParallel.h
@@ -28,6 +28,7 @@ typedef struct ParallelExecutorInfo
BufferUsage *buffer_usage; /* points to bufusage area in DSM */
SharedExecutorInstrumentation *instrumentation; /* optional */
dsa_area *area; /* points to DSA area in DSM */
+ dsa_handle param_exec; /* serialized PARAM_EXEC parameters */
bool finished; /* set true by ExecParallelFinish */
/* These two arrays have pcxt->nworkers_launched entries: */
shm_mq_handle **tqueue; /* tuple queues for worker output */
@@ -35,12 +36,13 @@ typedef struct ParallelExecutorInfo
} ParallelExecutorInfo;
extern ParallelExecutorInfo *ExecInitParallelPlan(PlanState *planstate,
- EState *estate, int nworkers, int64 tuples_needed);
+ EState *estate, Bitmapset *sendParam, int nworkers,
+ int64 tuples_needed);
extern void ExecParallelCreateReaders(ParallelExecutorInfo *pei);
extern void ExecParallelFinish(ParallelExecutorInfo *pei);
extern void ExecParallelCleanup(ParallelExecutorInfo *pei);
extern void ExecParallelReinitialize(PlanState *planstate,
- ParallelExecutorInfo *pei);
+ ParallelExecutorInfo *pei, Bitmapset *sendParam);
extern void ParallelQueryMain(dsm_segment *seg, shm_toc *toc);
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index a127682b0e..9b38d44ba0 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -841,6 +841,8 @@ typedef struct Gather
int rescan_param; /* ID of Param that signals a rescan, or -1 */
bool single_copy; /* don't execute plan more than once */
bool invisible; /* suppress EXPLAIN display (for testing)? */
+ Bitmapset *initParam; /* param id's of initplans which are referred
+ * at gather or one of it's child node */
} Gather;
/* ------------
@@ -858,6 +860,8 @@ typedef struct GatherMerge
Oid *sortOperators; /* OIDs of operators to sort them by */
Oid *collations; /* OIDs of collations */
bool *nullsFirst; /* NULLS FIRST/LAST directions */
+ Bitmapset *initParam; /* param id's of initplans which are referred
+ * at gather merge or one of it's child node */
} GatherMerge;
/* ----------------
diff --git a/src/test/regress/expected/select_parallel.out b/src/test/regress/expected/select_parallel.out
index 6f04769e3e..2a64435d8c 100644
--- a/src/test/regress/expected/select_parallel.out
+++ b/src/test/regress/expected/select_parallel.out
@@ -159,6 +159,41 @@ explain (costs off)
(4 rows)
alter table tenk2 reset (parallel_workers);
+-- test parallel plan for a query containing initplan.
+set enable_indexscan = off;
+set enable_indexonlyscan = off;
+set enable_bitmapscan = off;
+alter table tenk2 set (parallel_workers = 2);
+explain (costs off)
+ select count(*) from tenk1
+ where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+ QUERY PLAN
+------------------------------------------------------
+ Aggregate
+ InitPlan 1 (returns $2)
+ -> Finalize Aggregate
+ -> Gather
+ Workers Planned: 2
+ -> Partial Aggregate
+ -> Parallel Seq Scan on tenk2
+ -> Gather
+ Workers Planned: 4
+ Params Evaluated: $2
+ -> Parallel Seq Scan on tenk1
+ Filter: (unique1 = $2)
+(12 rows)
+
+select count(*) from tenk1
+ where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+ count
+-------
+ 1
+(1 row)
+
+reset enable_indexscan;
+reset enable_indexonlyscan;
+reset enable_bitmapscan;
+alter table tenk2 reset (parallel_workers);
-- test parallel index scans.
set enable_seqscan to off;
set enable_bitmapscan to off;
diff --git a/src/test/regress/sql/select_parallel.sql b/src/test/regress/sql/select_parallel.sql
index 9c1b87abdf..91491d5f09 100644
--- a/src/test/regress/sql/select_parallel.sql
+++ b/src/test/regress/sql/select_parallel.sql
@@ -58,6 +58,23 @@ explain (costs off)
(select ten from tenk2);
alter table tenk2 reset (parallel_workers);
+-- test parallel plan for a query containing initplan.
+set enable_indexscan = off;
+set enable_indexonlyscan = off;
+set enable_bitmapscan = off;
+alter table tenk2 set (parallel_workers = 2);
+
+explain (costs off)
+ select count(*) from tenk1
+ where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+select count(*) from tenk1
+ where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+
+reset enable_indexscan;
+reset enable_indexonlyscan;
+reset enable_bitmapscan;
+alter table tenk2 reset (parallel_workers);
+
-- test parallel index scans.
set enable_seqscan to off;
set enable_bitmapscan to off;
On Wed, Nov 15, 2017 at 12:25 AM, Robert Haas <robertmhaas@gmail.com> wrote:
On Tue, Nov 14, 2017 at 11:00 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Yeah, I'm sure it is. I have a vague recollection that there might be
existing regression test cases exercising such things, though I won't
swear to that. The "orderstest" bit in subselect.sql looks like it
might be meant to do that...
I agree that such cases can cause a problem with fixed memory.
Here's an updated patch that attempts to work around this problem using DSA.
There were a couple of problems with your changes:
1.
BufferUsage *buffer_usage; /* points to bufusage area in DSM */
+ dsa_handle param_exec; /* serialized PARAM_EXEC parameters */
@@ -35,12 +36,13 @@ typedef struct ParallelExecutorInfo
} ParallelExecutorInfo;
This should be dsa_pointer, otherwise, the value returned by
SerializeParamExecParams will get truncated.
2. In ExecParallelReinitialize(), we need to evaluate the params
before serializing them.
3. I think we should free the dsa pointer at the end of the gather.
Attached patch fixes the mentioned problems.
It could use a test case that actually tickles the new logic in
ExecParallelReinitialize ... this is mostly just to show the concept.
Thanks, it was quite helpful.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
initplan-pushdown-with-dsa-1.patchapplication/octet-stream; name=initplan-pushdown-with-dsa-1.patchDownload
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 8f7062c..447f69d 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -107,6 +107,7 @@ static void show_tidbitmap_info(BitmapHeapScanState *planstate,
static void show_instrumentation_count(const char *qlabel, int which,
PlanState *planstate, ExplainState *es);
static void show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es);
+static void show_eval_params(Bitmapset *bms_params, ExplainState *es);
static const char *explain_get_index_name(Oid indexId);
static void show_buffer_usage(ExplainState *es, const BufferUsage *usage);
static void ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir,
@@ -1441,6 +1442,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
planstate, es);
ExplainPropertyInteger("Workers Planned",
gather->num_workers, es);
+
+ /* Show params evaluated at gather node */
+ if (gather->initParam)
+ show_eval_params(gather->initParam, es);
+
if (es->analyze)
{
int nworkers;
@@ -1463,6 +1469,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
planstate, es);
ExplainPropertyInteger("Workers Planned",
gm->num_workers, es);
+
+ /* Show params evaluated at gather-merge node */
+ if (gm->initParam)
+ show_eval_params(gm->initParam, es);
+
if (es->analyze)
{
int nworkers;
@@ -2488,6 +2499,29 @@ show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es)
}
/*
+ * Show initplan params evaluated at Gather or Gather Merge node.
+ */
+static void
+show_eval_params(Bitmapset *bms_params, ExplainState *es)
+{
+ int paramid = -1;
+ List *params = NIL;
+
+ Assert(bms_params);
+
+ while ((paramid = bms_next_member(bms_params, paramid)) >= 0)
+ {
+ char param[32];
+
+ snprintf(param, sizeof(param), "$%d", paramid);
+ params = lappend(params, pstrdup(param));
+ }
+
+ if (params)
+ ExplainPropertyList("Params Evaluated", params, es);
+}
+
+/*
* Fetch the name of an index in an EXPLAIN
*
* We allow plugins to get control here so that plans involving hypothetical
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index a0f537b..6c4612d 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -1927,6 +1927,33 @@ ExecEvalParamExec(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
}
/*
+ * ExecEvalParamExecParams
+ *
+ * Execute the subplan stored in PARAM_EXEC initplans params, if not executed
+ * till now.
+ */
+void
+ExecEvalParamExecParams(Bitmapset *params, EState *estate)
+{
+ ParamExecData *prm;
+ int paramid;
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ prm = &(estate->es_param_exec_vals[paramid]);
+
+ if (prm->execPlan != NULL)
+ {
+ /* Parameter not evaluated yet, so go do it */
+ ExecSetParamPlan(prm->execPlan, GetPerTupleExprContext(estate));
+ /* ExecSetParamPlan should have processed this param... */
+ Assert(prm->execPlan == NULL);
+ }
+ }
+}
+
+/*
* Evaluate a PARAM_EXTERN parameter.
*
* PARAM_EXTERN parameters must be sought in ecxt_param_list_info.
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index fd7e7cb..c435550 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -23,6 +23,7 @@
#include "postgres.h"
+#include "executor/execExpr.h"
#include "executor/execParallel.h"
#include "executor/executor.h"
#include "executor/nodeBitmapHeapscan.h"
@@ -38,7 +39,9 @@
#include "optimizer/planner.h"
#include "storage/spin.h"
#include "tcop/tcopprot.h"
+#include "utils/datum.h"
#include "utils/dsa.h"
+#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/snapmgr.h"
#include "pgstat.h"
@@ -50,7 +53,7 @@
*/
#define PARALLEL_KEY_EXECUTOR_FIXED UINT64CONST(0xE000000000000001)
#define PARALLEL_KEY_PLANNEDSTMT UINT64CONST(0xE000000000000002)
-#define PARALLEL_KEY_PARAMS UINT64CONST(0xE000000000000003)
+#define PARALLEL_KEY_PARAMLISTINFO UINT64CONST(0xE000000000000003)
#define PARALLEL_KEY_BUFFER_USAGE UINT64CONST(0xE000000000000004)
#define PARALLEL_KEY_TUPLE_QUEUE UINT64CONST(0xE000000000000005)
#define PARALLEL_KEY_INSTRUMENTATION UINT64CONST(0xE000000000000006)
@@ -65,6 +68,7 @@
typedef struct FixedParallelExecutorState
{
int64 tuples_needed; /* tuple bound, see ExecSetTupleBound */
+ dsa_pointer param_exec;
} FixedParallelExecutorState;
/*
@@ -267,6 +271,133 @@ ExecParallelEstimate(PlanState *planstate, ExecParallelEstimateContext *e)
}
/*
+ * Estimate the amount of space required to serialize the indicated parameters.
+ */
+static Size
+EstimateParamExecSpace(EState *estate, Bitmapset *params)
+{
+ int paramid;
+ Size sz = sizeof(int);
+
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ Oid typeOid;
+ int16 typLen;
+ bool typByVal;
+ ParamExecData *prm;
+
+ prm = &(estate->es_param_exec_vals[paramid]);
+ typeOid = list_nth_oid(estate->es_plannedstmt->paramExecTypes,
+ paramid);
+
+ sz = add_size(sz, sizeof(int)); /* space for paramid */
+
+ /* space for datum/isnull */
+ if (OidIsValid(typeOid))
+ get_typlenbyval(typeOid, &typLen, &typByVal);
+ else
+ {
+ /* If no type OID, assume by-value, like copyParamList does. */
+ typLen = sizeof(Datum);
+ typByVal = true;
+ }
+ sz = add_size(sz,
+ datumEstimateSpace(prm->value, prm->isnull,
+ typByVal, typLen));
+ }
+ return sz;
+}
+
+/*
+ * Serialize specified PARAM_EXEC parameters.
+ *
+ * We write the number of parameters first, as a 4-byte integer, and then
+ * write details for each parameter in turn. The details for each parameter
+ * consist of a 4-byte paramid (location of param in execution time internal
+ * parameter array) and then the datum as serialized by datumSerialize().
+ */
+static dsa_pointer
+SerializeParamExecParams(EState *estate, Bitmapset *params)
+{
+ Size size;
+ int nparams;
+ int paramid;
+ ParamExecData *prm;
+ dsa_pointer handle;
+ char *start_address;
+
+ /* Allocate enough space for the current parameter values. */
+ size = EstimateParamExecSpace(estate, params);
+ handle = dsa_allocate(estate->es_query_dsa, size);
+ start_address = dsa_get_address(estate->es_query_dsa, handle);
+
+ /* First write the number of parameters as a 4-byte integer. */
+ nparams = bms_num_members(params);
+ memcpy(start_address, &nparams, sizeof(int));
+ start_address += sizeof(int);
+
+ /* Write details for each parameter in turn. */
+ paramid = -1;
+ while ((paramid = bms_next_member(params, paramid)) >= 0)
+ {
+ Oid typeOid;
+ int16 typLen;
+ bool typByVal;
+
+ prm = &(estate->es_param_exec_vals[paramid]);
+ typeOid = list_nth_oid(estate->es_plannedstmt->paramExecTypes,
+ paramid);
+
+ /* Write paramid. */
+ memcpy(start_address, ¶mid, sizeof(int));
+ start_address += sizeof(int);
+
+ /* Write datum/isnull */
+ if (OidIsValid(typeOid))
+ get_typlenbyval(typeOid, &typLen, &typByVal);
+ else
+ {
+ /* If no type OID, assume by-value, like copyParamList does. */
+ typLen = sizeof(Datum);
+ typByVal = true;
+ }
+ datumSerialize(prm->value, prm->isnull, typByVal, typLen,
+ &start_address);
+ }
+
+ return handle;
+}
+
+/*
+ * Restore specified PARAM_EXEC parameters.
+ */
+static void
+RestoreParamExecParams(char *start_address, EState *estate)
+{
+ int nparams;
+ int i;
+ int paramid;
+
+ memcpy(&nparams, start_address, sizeof(int));
+ start_address += sizeof(int);
+
+ for (i = 0; i < nparams; i++)
+ {
+ ParamExecData *prm;
+
+ /* Read paramid */
+ memcpy(¶mid, start_address, sizeof(int));
+ start_address += sizeof(int);
+ prm = &(estate->es_param_exec_vals[paramid]);
+
+ /* Read datum/isnull. */
+ prm->value = datumRestore(&start_address, &prm->isnull);
+ prm->execPlan = NULL;
+ }
+}
+
+/*
* Initialize the dynamic shared memory segment that will be used to control
* parallel execution.
*/
@@ -395,7 +526,8 @@ ExecParallelSetupTupleQueues(ParallelContext *pcxt, bool reinitialize)
* execution and return results to the main backend.
*/
ParallelExecutorInfo *
-ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
+ExecInitParallelPlan(PlanState *planstate, EState *estate,
+ Bitmapset *sendParams, int nworkers,
int64 tuples_needed)
{
ParallelExecutorInfo *pei;
@@ -405,17 +537,20 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
FixedParallelExecutorState *fpes;
char *pstmt_data;
char *pstmt_space;
- char *param_space;
+ char *paramlistinfo_space;
BufferUsage *bufusage_space;
SharedExecutorInstrumentation *instrumentation = NULL;
int pstmt_len;
- int param_len;
+ int paramlistinfo_len;
int instrumentation_len = 0;
int instrument_offset = 0;
Size dsa_minsize = dsa_minimum_size();
char *query_string;
int query_len;
+ /* Force parameters we're going to pass to workers to be evaluated. */
+ ExecEvalParamExecParams(sendParams, estate);
+
/* Allocate object for return value. */
pei = palloc0(sizeof(ParallelExecutorInfo));
pei->finished = false;
@@ -450,8 +585,8 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
shm_toc_estimate_keys(&pcxt->estimator, 1);
/* Estimate space for serialized ParamListInfo. */
- param_len = EstimateParamListSpace(estate->es_param_list_info);
- shm_toc_estimate_chunk(&pcxt->estimator, param_len);
+ paramlistinfo_len = EstimateParamListSpace(estate->es_param_list_info);
+ shm_toc_estimate_chunk(&pcxt->estimator, paramlistinfo_len);
shm_toc_estimate_keys(&pcxt->estimator, 1);
/*
@@ -511,6 +646,7 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
/* Store fixed-size state. */
fpes = shm_toc_allocate(pcxt->toc, sizeof(FixedParallelExecutorState));
fpes->tuples_needed = tuples_needed;
+ fpes->param_exec = InvalidDsaPointer;
shm_toc_insert(pcxt->toc, PARALLEL_KEY_EXECUTOR_FIXED, fpes);
/* Store query string */
@@ -524,9 +660,9 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
shm_toc_insert(pcxt->toc, PARALLEL_KEY_PLANNEDSTMT, pstmt_space);
/* Store serialized ParamListInfo. */
- param_space = shm_toc_allocate(pcxt->toc, param_len);
- shm_toc_insert(pcxt->toc, PARALLEL_KEY_PARAMS, param_space);
- SerializeParamList(estate->es_param_list_info, ¶m_space);
+ paramlistinfo_space = shm_toc_allocate(pcxt->toc, paramlistinfo_len);
+ shm_toc_insert(pcxt->toc, PARALLEL_KEY_PARAMLISTINFO, paramlistinfo_space);
+ SerializeParamList(estate->es_param_list_info, ¶mlistinfo_space);
/* Allocate space for each worker's BufferUsage; no need to initialize. */
bufusage_space = shm_toc_allocate(pcxt->toc,
@@ -577,13 +713,25 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers,
pei->area = dsa_create_in_place(area_space, dsa_minsize,
LWTRANCHE_PARALLEL_QUERY_DSA,
pcxt->seg);
- }
- /*
- * Make the area available to executor nodes running in the leader. See
- * also ParallelQueryMain which makes it available to workers.
- */
- estate->es_query_dsa = pei->area;
+ /*
+ * Make the area available to executor nodes running in the leader.
+ * See also ParallelQueryMain which makes it available to workers.
+ */
+ estate->es_query_dsa = pei->area;
+
+ /*
+ * Serialize parameters, if any, using DSA storage. We don't dare use
+ * the main parallel query DSM for this because we might relaunch
+ * workers after the values have changed (and thus the amount of
+ * storage required has changed).
+ */
+ if (!bms_is_empty(sendParams))
+ {
+ pei->param_exec = SerializeParamExecParams(estate, sendParams);
+ fpes->param_exec = pei->param_exec;
+ }
+ }
/*
* Give parallel-aware nodes a chance to initialize their shared data.
@@ -640,16 +788,39 @@ ExecParallelCreateReaders(ParallelExecutorInfo *pei)
*/
void
ExecParallelReinitialize(PlanState *planstate,
- ParallelExecutorInfo *pei)
+ ParallelExecutorInfo *pei,
+ Bitmapset *sendParams)
{
+ EState *estate = planstate->state;
+ FixedParallelExecutorState *fpes;
+
/* Old workers must already be shut down */
Assert(pei->finished);
+ /* Force parameters we're going to pass to workers to be evaluated. */
+ ExecEvalParamExecParams(sendParams, estate);
+
ReinitializeParallelDSM(pei->pcxt);
pei->tqueue = ExecParallelSetupTupleQueues(pei->pcxt, true);
pei->reader = NULL;
pei->finished = false;
+ fpes = shm_toc_lookup(pei->pcxt->toc, PARALLEL_KEY_EXECUTOR_FIXED, false);
+
+ /* Free any serialized parameters from the last round. */
+ if (DsaPointerIsValid(fpes->param_exec))
+ {
+ dsa_free(estate->es_query_dsa, fpes->param_exec);
+ fpes->param_exec = InvalidDsaPointer;
+ }
+
+ /* Serialize current parameter values if required. */
+ if (!bms_is_empty(sendParams))
+ {
+ pei->param_exec = SerializeParamExecParams(estate, sendParams);
+ fpes->param_exec = pei->param_exec;
+ }
+
/* Traverse plan tree and let each child node reset associated state. */
ExecParallelReInitializeDSM(planstate, pei->pcxt);
}
@@ -831,6 +1002,12 @@ ExecParallelFinish(ParallelExecutorInfo *pei)
void
ExecParallelCleanup(ParallelExecutorInfo *pei)
{
+ /* Free any serialized parameters. */
+ if (DsaPointerIsValid(pei->param_exec))
+ {
+ dsa_free(pei->area, pei->param_exec);
+ pei->param_exec = InvalidDsaPointer;
+ }
if (pei->area != NULL)
{
dsa_detach(pei->area);
@@ -882,7 +1059,7 @@ ExecParallelGetQueryDesc(shm_toc *toc, DestReceiver *receiver,
pstmt = (PlannedStmt *) stringToNode(pstmtspace);
/* Reconstruct ParamListInfo. */
- paramspace = shm_toc_lookup(toc, PARALLEL_KEY_PARAMS, false);
+ paramspace = shm_toc_lookup(toc, PARALLEL_KEY_PARAMLISTINFO, false);
paramLI = RestoreParamList(¶mspace);
/*
@@ -1046,6 +1223,14 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
/* Special executor initialization steps for parallel workers */
queryDesc->planstate->state->es_query_dsa = area;
+ if (DsaPointerIsValid(fpes->param_exec))
+ {
+ char *paramexec_space;
+
+ paramexec_space = dsa_get_address(area, fpes->param_exec);
+ RestoreParamExecParams(paramexec_space, queryDesc->estate);
+
+ }
ExecParallelInitializeWorker(queryDesc->planstate, toc);
/* Pass down any tuple bound */
diff --git a/src/backend/executor/nodeGather.c b/src/backend/executor/nodeGather.c
index 0298c65..07c62d2 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -160,11 +160,13 @@ ExecGather(PlanState *pstate)
if (!node->pei)
node->pei = ExecInitParallelPlan(node->ps.lefttree,
estate,
+ gather->initParam,
gather->num_workers,
node->tuples_needed);
else
ExecParallelReinitialize(node->ps.lefttree,
- node->pei);
+ node->pei,
+ gather->initParam);
/*
* Register backend workers. We might not get as many as we
diff --git a/src/backend/executor/nodeGatherMerge.c b/src/backend/executor/nodeGatherMerge.c
index 7206ab9..7dd655c 100644
--- a/src/backend/executor/nodeGatherMerge.c
+++ b/src/backend/executor/nodeGatherMerge.c
@@ -203,11 +203,13 @@ ExecGatherMerge(PlanState *pstate)
if (!node->pei)
node->pei = ExecInitParallelPlan(node->ps.lefttree,
estate,
+ gm->initParam,
gm->num_workers,
node->tuples_needed);
else
ExecParallelReinitialize(node->ps.lefttree,
- node->pei);
+ node->pei,
+ gm->initParam);
/* Try to launch workers. */
pcxt = node->pei->pcxt;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 76e7545..d9ff8a7 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -364,6 +364,7 @@ _copyGather(const Gather *from)
COPY_SCALAR_FIELD(rescan_param);
COPY_SCALAR_FIELD(single_copy);
COPY_SCALAR_FIELD(invisible);
+ COPY_BITMAPSET_FIELD(initParam);
return newnode;
}
@@ -391,6 +392,7 @@ _copyGatherMerge(const GatherMerge *from)
COPY_POINTER_FIELD(sortOperators, from->numCols * sizeof(Oid));
COPY_POINTER_FIELD(collations, from->numCols * sizeof(Oid));
COPY_POINTER_FIELD(nullsFirst, from->numCols * sizeof(bool));
+ COPY_BITMAPSET_FIELD(initParam);
return newnode;
}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index dc35df9..c97ee24 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -487,6 +487,7 @@ _outGather(StringInfo str, const Gather *node)
WRITE_INT_FIELD(rescan_param);
WRITE_BOOL_FIELD(single_copy);
WRITE_BOOL_FIELD(invisible);
+ WRITE_BITMAPSET_FIELD(initParam);
}
static void
@@ -517,6 +518,8 @@ _outGatherMerge(StringInfo str, const GatherMerge *node)
appendStringInfoString(str, " :nullsFirst");
for (i = 0; i < node->numCols; i++)
appendStringInfo(str, " %s", booltostr(node->nullsFirst[i]));
+
+ WRITE_BITMAPSET_FIELD(initParam);
}
static void
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 593658d..7eb67fc0 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2172,6 +2172,7 @@ _readGather(void)
READ_INT_FIELD(rescan_param);
READ_BOOL_FIELD(single_copy);
READ_BOOL_FIELD(invisible);
+ READ_BITMAPSET_FIELD(initParam);
READ_DONE();
}
@@ -2193,6 +2194,7 @@ _readGatherMerge(void)
READ_OID_ARRAY(sortOperators, local_node->numCols);
READ_OID_ARRAY(collations, local_node->numCols);
READ_BOOL_ARRAY(nullsFirst, local_node->numCols);
+ READ_BITMAPSET_FIELD(initParam);
READ_DONE();
}
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 9c74e39..d445477 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -6279,6 +6279,7 @@ make_gather(List *qptlist,
node->rescan_param = rescan_param;
node->single_copy = single_copy;
node->invisible = false;
+ node->initParam = NULL;
return node;
}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 4c00a14..f6b8bbf 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -377,6 +377,14 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
{
Gather *gather = makeNode(Gather);
+ /*
+ * If there are any initPlans attached to the formerly-top plan node,
+ * move them up to the Gather node; same as we do for Material node in
+ * materialize_finished_plan.
+ */
+ gather->plan.initPlan = top_plan->initPlan;
+ top_plan->initPlan = NIL;
+
gather->plan.targetlist = top_plan->targetlist;
gather->plan.qual = NIL;
gather->plan.lefttree = top_plan;
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index fa9a3f0..28a7f7e 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -107,6 +107,7 @@ static Node *fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context);
static bool fix_scan_expr_walker(Node *node, fix_scan_expr_context *context);
static void set_join_references(PlannerInfo *root, Join *join, int rtoffset);
static void set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset);
+static void set_param_references(PlannerInfo *root, Plan *plan);
static Node *convert_combining_aggrefs(Node *node, void *context);
static void set_dummy_tlist_references(Plan *plan, int rtoffset);
static indexed_tlist *build_tlist_index(List *tlist);
@@ -632,7 +633,10 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
case T_Gather:
case T_GatherMerge:
- set_upper_references(root, plan, rtoffset);
+ {
+ set_upper_references(root, plan, rtoffset);
+ set_param_references(root, plan);
+ }
break;
case T_Hash:
@@ -1782,6 +1786,51 @@ set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset)
}
/*
+ * set_param_references
+ * Initialize the initParam list in Gather or Gather merge node such that
+ * it contains reference of all the params that needs to be evaluated
+ * before execution of the node. It contains the initplan params that are
+ * being passed to the plan nodes below it.
+ */
+static void
+set_param_references(PlannerInfo *root, Plan *plan)
+{
+ Assert(IsA(plan, Gather) || IsA(plan, GatherMerge));
+
+ if (plan->lefttree->extParam)
+ {
+ PlannerInfo *proot;
+ Bitmapset *initSetParam = NULL;
+ ListCell *l;
+
+ for (proot = root; proot != NULL; proot = proot->parent_root)
+ {
+ foreach(l, proot->init_plans)
+ {
+ SubPlan *initsubplan = (SubPlan *) lfirst(l);
+ ListCell *l2;
+
+ foreach(l2, initsubplan->setParam)
+ {
+ initSetParam = bms_add_member(initSetParam, lfirst_int(l2));
+ }
+ }
+ }
+
+ /*
+ * Remember the list of all external initplan params that are used by
+ * the children of Gather or Gather merge node.
+ */
+ if (IsA(plan, Gather))
+ ((Gather *) plan)->initParam =
+ bms_intersect(plan->lefttree->extParam, initSetParam);
+ else
+ ((GatherMerge *) plan)->initParam =
+ bms_intersect(plan->lefttree->extParam, initSetParam);
+ }
+}
+
+/*
* Recursively scan an expression tree and convert Aggrefs to the proper
* intermediate form for combining aggregates. This means (1) replacing each
* one's argument list with a single argument that is the original Aggref
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 66e098f..d14ef31 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -1087,6 +1087,8 @@ bool
is_parallel_safe(PlannerInfo *root, Node *node)
{
max_parallel_hazard_context context;
+ PlannerInfo *proot;
+ ListCell *l;
/*
* Even if the original querytree contained nothing unsafe, we need to
@@ -1101,6 +1103,25 @@ is_parallel_safe(PlannerInfo *root, Node *node)
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_RESTRICTED;
context.safe_param_ids = NIL;
+
+ /*
+ * The params that refer to the same or parent query level are considered
+ * parallel-safe. The idea is that we compute such params at Gather or
+ * Gather Merge node and pass their value to workers.
+ */
+ for (proot = root; proot != NULL; proot = proot->parent_root)
+ {
+ foreach(l, proot->init_plans)
+ {
+ SubPlan *initsubplan = (SubPlan *) lfirst(l);
+ ListCell *l2;
+
+ foreach(l2, initsubplan->setParam)
+ context.safe_param_ids = lcons_int(lfirst_int(l2),
+ context.safe_param_ids);
+ }
+ }
+
return !max_parallel_hazard_walker(node, &context);
}
@@ -1225,7 +1246,8 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
* We can't pass Params to workers at the moment either, so they are also
* parallel-restricted, unless they are PARAM_EXTERN Params or are
* PARAM_EXEC Params listed in safe_param_ids, meaning they could be
- * generated within the worker.
+ * either generated within the worker or can be computed in master and
+ * then their value can be passed to the worker.
*/
else if (IsA(node, Param))
{
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 78d2247..5bbb63a 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -609,6 +609,7 @@ extern ExprEvalOp ExecEvalStepOp(ExprState *state, ExprEvalStep *op);
*/
extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
+extern void ExecEvalParamExecParams(Bitmapset *params, EState *estate);
extern void ExecEvalParamExtern(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
extern void ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op);
diff --git a/src/include/executor/execParallel.h b/src/include/executor/execParallel.h
index e1b3e7a..99a13f3 100644
--- a/src/include/executor/execParallel.h
+++ b/src/include/executor/execParallel.h
@@ -28,6 +28,7 @@ typedef struct ParallelExecutorInfo
BufferUsage *buffer_usage; /* points to bufusage area in DSM */
SharedExecutorInstrumentation *instrumentation; /* optional */
dsa_area *area; /* points to DSA area in DSM */
+ dsa_pointer param_exec; /* serialized PARAM_EXEC parameters */
bool finished; /* set true by ExecParallelFinish */
/* These two arrays have pcxt->nworkers_launched entries: */
shm_mq_handle **tqueue; /* tuple queues for worker output */
@@ -35,12 +36,13 @@ typedef struct ParallelExecutorInfo
} ParallelExecutorInfo;
extern ParallelExecutorInfo *ExecInitParallelPlan(PlanState *planstate,
- EState *estate, int nworkers, int64 tuples_needed);
+ EState *estate, Bitmapset *sendParam, int nworkers,
+ int64 tuples_needed);
extern void ExecParallelCreateReaders(ParallelExecutorInfo *pei);
extern void ExecParallelFinish(ParallelExecutorInfo *pei);
extern void ExecParallelCleanup(ParallelExecutorInfo *pei);
extern void ExecParallelReinitialize(PlanState *planstate,
- ParallelExecutorInfo *pei);
+ ParallelExecutorInfo *pei, Bitmapset *sendParam);
extern void ParallelQueryMain(dsm_segment *seg, shm_toc *toc);
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index a127682..9b38d44 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -841,6 +841,8 @@ typedef struct Gather
int rescan_param; /* ID of Param that signals a rescan, or -1 */
bool single_copy; /* don't execute plan more than once */
bool invisible; /* suppress EXPLAIN display (for testing)? */
+ Bitmapset *initParam; /* param id's of initplans which are referred
+ * at gather or one of it's child node */
} Gather;
/* ------------
@@ -858,6 +860,8 @@ typedef struct GatherMerge
Oid *sortOperators; /* OIDs of operators to sort them by */
Oid *collations; /* OIDs of collations */
bool *nullsFirst; /* NULLS FIRST/LAST directions */
+ Bitmapset *initParam; /* param id's of initplans which are referred
+ * at gather merge or one of it's child node */
} GatherMerge;
/* ----------------
diff --git a/src/test/regress/expected/select_parallel.out b/src/test/regress/expected/select_parallel.out
index 06aeddd..d1d5b22 100644
--- a/src/test/regress/expected/select_parallel.out
+++ b/src/test/regress/expected/select_parallel.out
@@ -202,6 +202,41 @@ explain (costs off)
(4 rows)
alter table tenk2 reset (parallel_workers);
+-- test parallel plan for a query containing initplan.
+set enable_indexscan = off;
+set enable_indexonlyscan = off;
+set enable_bitmapscan = off;
+alter table tenk2 set (parallel_workers = 2);
+explain (costs off)
+ select count(*) from tenk1
+ where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+ QUERY PLAN
+------------------------------------------------------
+ Aggregate
+ InitPlan 1 (returns $2)
+ -> Finalize Aggregate
+ -> Gather
+ Workers Planned: 2
+ -> Partial Aggregate
+ -> Parallel Seq Scan on tenk2
+ -> Gather
+ Workers Planned: 4
+ Params Evaluated: $2
+ -> Parallel Seq Scan on tenk1
+ Filter: (unique1 = $2)
+(12 rows)
+
+select count(*) from tenk1
+ where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+ count
+-------
+ 1
+(1 row)
+
+reset enable_indexscan;
+reset enable_indexonlyscan;
+reset enable_bitmapscan;
+alter table tenk2 reset (parallel_workers);
-- test parallel index scans.
set enable_seqscan to off;
set enable_bitmapscan to off;
diff --git a/src/test/regress/sql/select_parallel.sql b/src/test/regress/sql/select_parallel.sql
index b701b35..bb4e34a 100644
--- a/src/test/regress/sql/select_parallel.sql
+++ b/src/test/regress/sql/select_parallel.sql
@@ -74,6 +74,23 @@ explain (costs off)
(select ten from tenk2);
alter table tenk2 reset (parallel_workers);
+-- test parallel plan for a query containing initplan.
+set enable_indexscan = off;
+set enable_indexonlyscan = off;
+set enable_bitmapscan = off;
+alter table tenk2 set (parallel_workers = 2);
+
+explain (costs off)
+ select count(*) from tenk1
+ where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+select count(*) from tenk1
+ where tenk1.unique1 = (Select max(tenk2.unique1) from tenk2);
+
+reset enable_indexscan;
+reset enable_indexonlyscan;
+reset enable_bitmapscan;
+alter table tenk2 reset (parallel_workers);
+
-- test parallel index scans.
set enable_seqscan to off;
set enable_bitmapscan to off;
On Thu, Nov 16, 2017 at 3:34 PM, Amit Kapila <amit.kapila16@gmail.com> wrote:
On Wed, Nov 15, 2017 at 12:25 AM, Robert Haas <robertmhaas@gmail.com> wrote:
On Tue, Nov 14, 2017 at 11:00 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Yeah, I'm sure it is. I have a vague recollection that there might be
existing regression test cases exercising such things, though I won't
swear to that. The "orderstest" bit in subselect.sql looks like it
might be meant to do that...I agree that such cases can cause a problem with fixed memory.
I'm able to come up with a test case to produce the problem.
regression[107494]=# select ten,count(*) from tenk1 a where a.ten in (select
b.ten from tenk1 b where (select a.ten from tenk1 c where c.ten =
a.ten limit 1) = b.ten limit 1) group by a.ten;
QUERY PLAN
---------------------------------------------------
HashAggregate
Group Key: a.ten
-> Seq Scan on tenk1 a
Filter: (SubPlan 2)
SubPlan 2
-> Limit
InitPlan 1 (returns $1)
-> Limit
-> Seq Scan on tenk1 c
Filter: (ten = a.ten)
-> Seq Scan on tenk1 b
Filter: ($1 = ten)
In the above case, Subplan 2 returns the same value of a.ten that is
used in initplan as a reference. So, this query is nothing but select
ten,count(*) from tenk1 group by a.ten. The output should be 10 rows
each row having a count=1000.
By enforcing parallelism, I got the following plan:
QUERY PLAN
------------------------------------------------------
HashAggregate
Group Key: a.ten
-> Seq Scan on tenk1 a
Filter: (SubPlan 2)
SubPlan 2
-> Limit
InitPlan 1 (returns $1)
-> Limit
-> Seq Scan on tenk1 c
Filter: (ten = a.ten)
-> Gather
Workers Planned: 2
Params Evaluated: $1
-> Parallel Seq Scan on tenk1 b
Filter: ($1 = ten)
When I set parallel_leader_participation to off, I get the incorrect
result as follows:
ten | count
------------
0 | 1000
(1 row)
In the above case, the initplan gets evaluated only once from
ExecInitParallelPlan path. Hence, we see the incorrect result.
Here's an updated patch that attempts to work around this problem using DSA.
There were a couple of problems with your changes: 1. BufferUsage *buffer_usage; /* points to bufusage area in DSM */ + dsa_handle param_exec; /* serialized PARAM_EXEC parameters */ @@ -35,12 +36,13 @@ typedef struct ParallelExecutorInfo } ParallelExecutorInfo;This should be dsa_pointer, otherwise, the value returned by
SerializeParamExecParams will get truncated.2. In ExecParallelReinitialize(), we need to evaluate the params
before serializing them.3. I think we should free the dsa pointer at the end of the gather.
Attached patch fixes the mentioned problems.
It could use a test case that actually tickles the new logic in
ExecParallelReinitialize ... this is mostly just to show the concept.Thanks, it was quite helpful.
I've tested the above-mentioned scenario with this patch and it is
working fine. Also, I've created a text column named 'vartext',
inserted some random length texts(max length 100) and tweaked the
above query as follows:
select ten,count(*) from tenk1 a where a.ten in (select
b.ten from tenk1 b where (select a.vartext from tenk1 c where c.ten =
a.ten limit 1) = b.vartext limit 1) group by a.ten;
This query is equivalent to select ten,count(*) from tenk1 group by
a.ten. It also produced the expected result without throwing any
error.
--
Thanks & Regards,
Kuntal Ghosh
EnterpriseDB: http://www.enterprisedb.com
On Thu, Nov 16, 2017 at 5:23 AM, Kuntal Ghosh
<kuntalghosh.2007@gmail.com> wrote:
I've tested the above-mentioned scenario with this patch and it is
working fine. Also, I've created a text column named 'vartext',
inserted some random length texts(max length 100) and tweaked the
above query as follows:
select ten,count(*) from tenk1 a where a.ten in (select
b.ten from tenk1 b where (select a.vartext from tenk1 c where c.ten =
a.ten limit 1) = b.vartext limit 1) group by a.ten;
This query is equivalent to select ten,count(*) from tenk1 group by
a.ten. It also produced the expected result without throwing any
error.
Great! I have committed the patch; thanks for testing.
As I said in the commit message, there's a lot more work that could be
done here. I think we should consider trying to revise this whole
system so that instead of serializing the values and passing them to
the workers, we allocate an array of slots where each slot has a Datum
flag, an isnull flag, and a dsa_pointer (which maybe could be union'd
to the Datum?). If we're passing a parameter by value, we could just
store it in the Datum field; if it's null, we just set isnull. If
it's being passed by reference, we dsa_allocate() space for it, copy
it into that space, and then store the dsa_pointer.
The advantage of this is that it would be possible to imagine the
contents of a slot changing while parallelism is running, which
doesn't really work with the current serialized-blob representation.
That would in turn allow us to imagine letting parallel-safe InitPlans
being evaluated by the first participant that needs the value rather
than before launching workers, which would be good, not only because
of the possibility of deferring work for InitPlans attached at or
above the Gather but also because it could be used for InitPlans below
the Gather (as long as they don't depend on any parameters computed
below the Gather). I thought about trying to refactor this patch to
use a slot concept like what I just mentioned even before committing
it, but after poking at it a bit I decided that was going to require
too much new development to justify cramming it into the same patch.
Still, it seems like a good idea to do it at some point (when we have
lots of free time?).
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On Thu, Nov 16, 2017 at 10:44 PM, Robert Haas <robertmhaas@gmail.com> wrote:
On Thu, Nov 16, 2017 at 5:23 AM, Kuntal Ghosh
<kuntalghosh.2007@gmail.com> wrote:I've tested the above-mentioned scenario with this patch and it is
working fine. Also, I've created a text column named 'vartext',
inserted some random length texts(max length 100) and tweaked the
above query as follows:
select ten,count(*) from tenk1 a where a.ten in (select
b.ten from tenk1 b where (select a.vartext from tenk1 c where c.ten =
a.ten limit 1) = b.vartext limit 1) group by a.ten;
This query is equivalent to select ten,count(*) from tenk1 group by
a.ten. It also produced the expected result without throwing any
error.Great! I have committed the patch; thanks for testing.
Thanks.
As I said in the commit message, there's a lot more work that could be
done here. I think we should consider trying to revise this whole
system so that instead of serializing the values and passing them to
the workers, we allocate an array of slots where each slot has a Datum
flag, an isnull flag, and a dsa_pointer (which maybe could be union'd
to the Datum?). If we're passing a parameter by value, we could just
store it in the Datum field; if it's null, we just set isnull. If
it's being passed by reference, we dsa_allocate() space for it, copy
it into that space, and then store the dsa_pointer.The advantage of this is that it would be possible to imagine the
contents of a slot changing while parallelism is running, which
doesn't really work with the current serialized-blob representation.
That would in turn allow us to imagine letting parallel-safe InitPlans
being evaluated by the first participant that needs the value rather
than before launching workers, which would be good, not only because
of the possibility of deferring work for InitPlans attached at or
above the Gather but also because it could be used for InitPlans below
the Gather (as long as they don't depend on any parameters computed
below the Gather).
That would be cool, but I think here finding whether it is dependent
on any parameter computed below gather could be tricky.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com