From 0efa0c3244839587ea7a6e81e0fa6fa31f6dac10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=80=E6=8C=83?= Date: Mon, 28 Sep 2020 03:40:35 +0800 Subject: [PATCH v1] Allow planner prune partitionn with stable Expr --- src/backend/partitioning/partprune.c | 99 ++++++++++++++----- src/test/regress/expected/partition_prune.out | 27 +++-- 2 files changed, 83 insertions(+), 43 deletions(-) diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 6268623d56..7e183343fe 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -603,7 +603,7 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, * and create a list of "partition pruning steps". * * 'target' tells whether to generate pruning steps for planning (use - * immutable clauses only), or for executor startup (use any allowable + * immutable or stable clauses only), or for executor startup (use any allowable * clause except ones containing PARAM_EXEC Params), or for executor * per-scan pruning (use any allowable clause). * @@ -739,6 +739,51 @@ get_matching_partitions(PartitionPruneContext *context, List *pruning_steps) */ results = (PruneStepResult **) palloc0(num_steps * sizeof(PruneStepResult *)); + + if (context->planstate == NULL) + { + /* + * This is still in PARTTARGET_PLANNER state, initialize expression + * state for each expression we need + * XXX: it is recreated in ExecInitPruningContext since the CurrentMemoryContext + * looks not good and context->ppcontext fails as well. + **/ + context->exprstates = (ExprState **) + palloc0(sizeof(ExprState *) * num_steps * context->nparts); + foreach(lc, pruning_steps) + { + PartitionPruneStepOp *step = (PartitionPruneStepOp *) lfirst(lc); + ListCell *lc2; + int keyno; + + /* not needed for other step kinds */ + if (!IsA(step, PartitionPruneStepOp)) + continue; + + Assert(list_length(step->exprs) <= context->partnatts); + + keyno = 0; + foreach(lc2, step->exprs) + { + Expr *expr = (Expr *) lfirst(lc2); + + /* not needed for Consts && Param for PARTTARGET_PLANNER stage*/ + if (!IsA(expr, Const) && !IsA(expr, Param)) + { + int stateidx = PruneCxtStateIdx(context->partnatts, + step->step.step_id, + keyno); + + context->exprstates[stateidx] = + /* XXX: context->planstate is NULL at this stage, but + * looks everything is well per testing. + */ + ExecInitExpr(expr, context->planstate); + } + keyno++; + } + } + } foreach(lc, pruning_steps) { PartitionPruneStep *step = lfirst(lc); @@ -1845,30 +1890,29 @@ match_clause_to_partition_key(GeneratePruningStepsContext *context, */ if (!IsA(expr, Const)) { + bool is_volatile = contain_volatile_functions((Node *) expr); Bitmapset *paramids; - /* - * When pruning in the planner, we only support pruning using - * comparisons to constants. We cannot prune on the basis of + * When pruning in the planner, we support pruning using + * comparisons to constants or non volatile exprs. We cannot prune on the basis of * anything that's not immutable. (Note that has_mutable_arg and * has_exec_param do not get set for this target value.) */ - if (context->target == PARTTARGET_PLANNER) - return PARTCLAUSE_UNSUPPORTED; /* - * We can never prune using an expression that contains Vars. + * And we must reject anything containing a volatile function. + * Stable functions are OK though. */ - if (contain_var_clause((Node *) expr)) + if (is_volatile) return PARTCLAUSE_UNSUPPORTED; /* - * And we must reject anything containing a volatile function. - * Stable functions are OK though. + * We can never prune using an expression that contains Vars. */ - if (contain_volatile_functions((Node *) expr)) + if (contain_var_clause((Node *) expr)) return PARTCLAUSE_UNSUPPORTED; + /* * See if there are any exec Params. If so, we can only use this * expression during per-scan pruning. @@ -1895,13 +1939,6 @@ match_clause_to_partition_key(GeneratePruningStepsContext *context, if (op_volatile(opno) != PROVOLATILE_IMMUTABLE) { context->has_mutable_op = true; - - /* - * When pruning in the planner, we cannot prune with mutable - * operators. - */ - if (context->target == PARTTARGET_PLANNER) - return PARTCLAUSE_UNSUPPORTED; } /* @@ -3257,6 +3294,8 @@ perform_pruning_base_step(PartitionPruneContext *context, */ for (keyno = 0; keyno < context->partnatts; keyno++) { + /* during PARTTARGET_PLANNER state, we will skip the Param expr */ + bool skip_param = lc1 != NULL && context->planstate == NULL && IsA(lfirst(lc1), Param); /* * For hash partitioning, it is possible that values of some keys are * not provided in operator clauses, but instead the planner found @@ -3272,7 +3311,7 @@ perform_pruning_base_step(PartitionPruneContext *context, if (keyno > nvalues && context->strategy == PARTITION_STRATEGY_RANGE) break; - if (lc1 != NULL) + if (lc1 != NULL && !skip_param) { Expr *expr; Datum datum; @@ -3583,15 +3622,21 @@ partkey_datum_from_expr(PartitionPruneContext *context, { ExprState *exprstate; ExprContext *ectx; - - /* - * We should never see a non-Const in a step unless we're running in - * the executor. - */ - Assert(context->planstate != NULL); - exprstate = context->exprstates[stateidx]; - ectx = context->planstate->ps_ExprContext; + if (context->planstate == NULL) + { + /* This happens on plan stage, so it must be an immutable or stable Expr */ + Assert(!contain_volatile_functions((Node *)expr)); + /* XXX: I don't know how to choose ExprContext for this purpose, + * just do it so */ + ectx = makeNode(ExprContext); + ectx->ecxt_per_tuple_memory = context->ppccontext; + } + else + { + ectx = context->planstate->ps_ExprContext; + } + *value = ExecEvalExprSwitchContext(exprstate, ectx, isnull); } } diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out index 50d2a7e4b9..75bb086538 100644 --- a/src/test/regress/expected/partition_prune.out +++ b/src/test/regress/expected/partition_prune.out @@ -1864,13 +1864,11 @@ begin; create function list_part_fn(int) returns int as $$ begin return $1; end;$$ language plpgsql stable; -- Ensure pruning works using a stable function containing no Vars explain (analyze, costs off, summary off, timing off) select * from list_part where a = list_part_fn(1); - QUERY PLAN ------------------------------------------------------------------- - Append (actual rows=1 loops=1) - Subplans Removed: 3 - -> Seq Scan on list_part1 list_part_1 (actual rows=1 loops=1) - Filter: (a = list_part_fn(1)) -(4 rows) + QUERY PLAN +---------------------------------------------------------- + Seq Scan on list_part1 list_part (actual rows=1 loops=1) + Filter: (a = list_part_fn(1)) +(2 rows) -- Ensure pruning does not take place when the function has a Var parameter explain (analyze, costs off, summary off, timing off) select * from list_part where a = list_part_fn(a); @@ -2911,23 +2909,20 @@ select * from stable_qual_pruning where a < localtimestamp; QUERY PLAN -------------------------------------------------------------------------------------- Append (actual rows=0 loops=1) - Subplans Removed: 1 -> Seq Scan on stable_qual_pruning1 stable_qual_pruning_1 (actual rows=0 loops=1) Filter: (a < LOCALTIMESTAMP) -> Seq Scan on stable_qual_pruning2 stable_qual_pruning_2 (actual rows=0 loops=1) Filter: (a < LOCALTIMESTAMP) -(6 rows) +(5 rows) -- timestamp < timestamptz comparison is only stable, not immutable explain (analyze, costs off, summary off, timing off) select * from stable_qual_pruning where a < '2000-02-01'::timestamptz; - QUERY PLAN --------------------------------------------------------------------------------------- - Append (actual rows=0 loops=1) - Subplans Removed: 2 - -> Seq Scan on stable_qual_pruning1 stable_qual_pruning_1 (actual rows=0 loops=1) - Filter: (a < 'Tue Feb 01 00:00:00 2000 PST'::timestamp with time zone) -(4 rows) + QUERY PLAN +------------------------------------------------------------------------------ + Seq Scan on stable_qual_pruning1 stable_qual_pruning (actual rows=0 loops=1) + Filter: (a < 'Tue Feb 01 00:00:00 2000 PST'::timestamp with time zone) +(2 rows) -- check ScalarArrayOp cases explain (analyze, costs off, summary off, timing off) -- 2.21.0