Restructure creation of run-time pruning steps.
Previously, gen_partprune_steps() always built executor pruning steps using all suitable clauses, including those containing PARAM_EXEC Params. This meant that the pruning steps were only completely safe for executor run-time (scan start) pruning. To prune at executor startup, we had to ignore the steps involving exec Params. But this doesn't really work in general, since there may be logic changes needed as well --- for example, pruning according to the last operator's btree strategy is the wrong thing if we're not applying that operator. The rules embodied in gen_partprune_steps() and its minions are sufficiently complicated that tracking their incremental effects in other logic seems quite impractical. Short of a complete redesign, the only safe fix seems to be to run gen_partprune_steps() twice, once to create executor startup pruning steps and then again for run-time pruning steps. We can save a few cycles however by noting during the first scan whether we rejected any clauses because they involved exec Params --- if not, we don't need to do the second scan. In support of this, refactor the internal APIs in partprune.c to make more use of passing information in the GeneratePruningStepsContext struct, rather than as separate arguments. This is, I hope, the last piece of our response to a bug report from Alan Jackson. Back-patch to v11 where this code came in. Discussion: https://postgr.es/m/FAD28A83-AC73-489E-A058-2681FA31D648@tvsquared.com
This commit is contained in:
parent
51948c4e1f
commit
592d5d75be
@ -78,6 +78,10 @@ static char *ExecBuildSlotPartitionKeyDescription(Relation rel,
|
||||
bool *isnull,
|
||||
int maxfieldlen);
|
||||
static List *adjust_partition_tlist(List *tlist, TupleConversionMap *map);
|
||||
static void ExecInitPruningContext(PartitionPruneContext *context,
|
||||
Oid reloid,
|
||||
List *pruning_steps,
|
||||
PlanState *planstate);
|
||||
static void find_matching_subplans_recurse(PartitionPruningData *prunedata,
|
||||
PartitionedRelPruningData *pprune,
|
||||
bool initial_prune,
|
||||
@ -1525,18 +1529,13 @@ ExecCreatePartitionPruneState(PlanState *planstate,
|
||||
{
|
||||
PartitionedRelPruneInfo *pinfo = lfirst_node(PartitionedRelPruneInfo, lc2);
|
||||
PartitionedRelPruningData *pprune = &prunedata->partrelprunedata[j];
|
||||
PartitionPruneContext *context = &pprune->context;
|
||||
PartitionDesc partdesc;
|
||||
PartitionKey partkey;
|
||||
int partnatts;
|
||||
int n_steps;
|
||||
ListCell *lc3;
|
||||
|
||||
/*
|
||||
* We must copy the subplan_map rather than pointing directly to
|
||||
* the plan's version, as we may end up making modifications to it
|
||||
* later.
|
||||
*/
|
||||
pprune->nparts = pinfo->nparts;
|
||||
pprune->subplan_map = palloc(sizeof(int) * pinfo->nparts);
|
||||
memcpy(pprune->subplan_map, pinfo->subplan_map,
|
||||
sizeof(int) * pinfo->nparts);
|
||||
@ -1548,76 +1547,28 @@ ExecCreatePartitionPruneState(PlanState *planstate,
|
||||
pprune->present_parts = bms_copy(pinfo->present_parts);
|
||||
|
||||
/*
|
||||
* We need to hold a pin on the partitioned table's relcache entry
|
||||
* so that we can rely on its copies of the table's partition key
|
||||
* and partition descriptor. We need not get a lock though; one
|
||||
* should have been acquired already by InitPlan or
|
||||
* ExecLockNonLeafAppendTables.
|
||||
* Initialize pruning contexts as needed.
|
||||
*/
|
||||
context->partrel = relation_open(pinfo->reloid, NoLock);
|
||||
|
||||
partkey = RelationGetPartitionKey(context->partrel);
|
||||
partdesc = RelationGetPartitionDesc(context->partrel);
|
||||
n_steps = list_length(pinfo->pruning_steps);
|
||||
|
||||
context->strategy = partkey->strategy;
|
||||
context->partnatts = partnatts = partkey->partnatts;
|
||||
context->nparts = pinfo->nparts;
|
||||
context->boundinfo = partdesc->boundinfo;
|
||||
context->partcollation = partkey->partcollation;
|
||||
context->partsupfunc = partkey->partsupfunc;
|
||||
|
||||
/* We'll look up type-specific support functions as needed */
|
||||
context->stepcmpfuncs = (FmgrInfo *)
|
||||
palloc0(sizeof(FmgrInfo) * n_steps * partnatts);
|
||||
|
||||
context->ppccontext = CurrentMemoryContext;
|
||||
context->planstate = planstate;
|
||||
|
||||
/* Initialize expression state for each expression we need */
|
||||
context->exprstates = (ExprState **)
|
||||
palloc0(sizeof(ExprState *) * n_steps * partnatts);
|
||||
foreach(lc3, pinfo->pruning_steps)
|
||||
pprune->initial_pruning_steps = pinfo->initial_pruning_steps;
|
||||
if (pinfo->initial_pruning_steps)
|
||||
{
|
||||
PartitionPruneStepOp *step = (PartitionPruneStepOp *) lfirst(lc3);
|
||||
ListCell *lc4;
|
||||
int keyno;
|
||||
|
||||
/* not needed for other step kinds */
|
||||
if (!IsA(step, PartitionPruneStepOp))
|
||||
continue;
|
||||
|
||||
Assert(list_length(step->exprs) <= partnatts);
|
||||
|
||||
keyno = 0;
|
||||
foreach(lc4, step->exprs)
|
||||
{
|
||||
Expr *expr = (Expr *) lfirst(lc4);
|
||||
|
||||
/* not needed for Consts */
|
||||
if (!IsA(expr, Const))
|
||||
{
|
||||
int stateidx = PruneCxtStateIdx(partnatts,
|
||||
step->step.step_id,
|
||||
keyno);
|
||||
|
||||
context->exprstates[stateidx] =
|
||||
ExecInitExpr(expr, context->planstate);
|
||||
}
|
||||
keyno++;
|
||||
}
|
||||
ExecInitPruningContext(&pprune->initial_context,
|
||||
pinfo->reloid,
|
||||
pinfo->initial_pruning_steps,
|
||||
planstate);
|
||||
/* Record whether initial pruning is needed at any level */
|
||||
prunestate->do_initial_prune = true;
|
||||
}
|
||||
pprune->exec_pruning_steps = pinfo->exec_pruning_steps;
|
||||
if (pinfo->exec_pruning_steps)
|
||||
{
|
||||
ExecInitPruningContext(&pprune->exec_context,
|
||||
pinfo->reloid,
|
||||
pinfo->exec_pruning_steps,
|
||||
planstate);
|
||||
/* Record whether exec pruning is needed at any level */
|
||||
prunestate->do_exec_prune = true;
|
||||
}
|
||||
|
||||
/* Array is not modified at runtime, so just point to plan's copy */
|
||||
context->exprhasexecparam = pinfo->hasexecparam;
|
||||
|
||||
pprune->pruning_steps = pinfo->pruning_steps;
|
||||
pprune->do_initial_prune = pinfo->do_initial_prune;
|
||||
pprune->do_exec_prune = pinfo->do_exec_prune;
|
||||
|
||||
/* Record if pruning would be useful at any level */
|
||||
prunestate->do_initial_prune |= pinfo->do_initial_prune;
|
||||
prunestate->do_exec_prune |= pinfo->do_exec_prune;
|
||||
|
||||
/*
|
||||
* Accumulate the IDs of all PARAM_EXEC Params affecting the
|
||||
@ -1654,7 +1605,90 @@ ExecDestroyPartitionPruneState(PartitionPruneState *prunestate)
|
||||
int j;
|
||||
|
||||
for (j = 0; j < prunedata->num_partrelprunedata; j++)
|
||||
relation_close(pprune[j].context.partrel, NoLock);
|
||||
{
|
||||
if (pprune[j].initial_pruning_steps)
|
||||
relation_close(pprune[j].initial_context.partrel, NoLock);
|
||||
if (pprune[j].exec_pruning_steps)
|
||||
relation_close(pprune[j].exec_context.partrel, NoLock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize a PartitionPruneContext for the given list of pruning steps.
|
||||
*/
|
||||
static void
|
||||
ExecInitPruningContext(PartitionPruneContext *context,
|
||||
Oid reloid,
|
||||
List *pruning_steps,
|
||||
PlanState *planstate)
|
||||
{
|
||||
PartitionKey partkey;
|
||||
PartitionDesc partdesc;
|
||||
int n_steps;
|
||||
int partnatts;
|
||||
ListCell *lc;
|
||||
|
||||
/*
|
||||
* We need to hold a pin on the partitioned table's relcache entry
|
||||
* so that we can rely on its copies of the table's partition key
|
||||
* and partition descriptor. We need not get a lock though; one
|
||||
* should have been acquired already by InitPlan or
|
||||
* ExecLockNonLeafAppendTables.
|
||||
*/
|
||||
context->partrel = relation_open(reloid, NoLock);
|
||||
|
||||
partkey = RelationGetPartitionKey(context->partrel);
|
||||
partdesc = RelationGetPartitionDesc(context->partrel);
|
||||
|
||||
n_steps = list_length(pruning_steps);
|
||||
|
||||
context->strategy = partkey->strategy;
|
||||
context->partnatts = partnatts = partkey->partnatts;
|
||||
context->nparts = partdesc->nparts;
|
||||
context->boundinfo = partdesc->boundinfo;
|
||||
context->partcollation = partkey->partcollation;
|
||||
context->partsupfunc = partkey->partsupfunc;
|
||||
|
||||
/* We'll look up type-specific support functions as needed */
|
||||
context->stepcmpfuncs = (FmgrInfo *)
|
||||
palloc0(sizeof(FmgrInfo) * n_steps * partnatts);
|
||||
|
||||
context->ppccontext = CurrentMemoryContext;
|
||||
context->planstate = planstate;
|
||||
|
||||
/* Initialize expression state for each expression we need */
|
||||
context->exprstates = (ExprState **)
|
||||
palloc0(sizeof(ExprState *) * n_steps * partnatts);
|
||||
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) <= partnatts);
|
||||
|
||||
keyno = 0;
|
||||
foreach(lc2, step->exprs)
|
||||
{
|
||||
Expr *expr = (Expr *) lfirst(lc2);
|
||||
|
||||
/* not needed for Consts */
|
||||
if (!IsA(expr, Const))
|
||||
{
|
||||
int stateidx = PruneCxtStateIdx(partnatts,
|
||||
step->step.step_id,
|
||||
keyno);
|
||||
|
||||
context->exprstates[stateidx] =
|
||||
ExecInitExpr(expr, context->planstate);
|
||||
}
|
||||
keyno++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1702,7 +1736,8 @@ ExecFindInitialMatchingSubPlans(PartitionPruneState *prunestate, int nsubplans)
|
||||
find_matching_subplans_recurse(prunedata, pprune, true, &result);
|
||||
|
||||
/* Expression eval may have used space in node's ps_ExprContext too */
|
||||
ResetExprContext(pprune->context.planstate->ps_ExprContext);
|
||||
if (pprune->initial_pruning_steps)
|
||||
ResetExprContext(pprune->initial_context.planstate->ps_ExprContext);
|
||||
}
|
||||
|
||||
MemoryContextSwitchTo(oldcontext);
|
||||
@ -1769,7 +1804,7 @@ ExecFindInitialMatchingSubPlans(PartitionPruneState *prunestate, int nsubplans)
|
||||
for (j = prunedata->num_partrelprunedata - 1; j >= 0; j--)
|
||||
{
|
||||
PartitionedRelPruningData *pprune = &prunedata->partrelprunedata[j];
|
||||
int nparts = pprune->context.nparts;
|
||||
int nparts = pprune->nparts;
|
||||
int k;
|
||||
|
||||
/* We just rebuild present_parts from scratch */
|
||||
@ -1854,7 +1889,8 @@ ExecFindMatchingSubPlans(PartitionPruneState *prunestate)
|
||||
find_matching_subplans_recurse(prunedata, pprune, false, &result);
|
||||
|
||||
/* Expression eval may have used space in node's ps_ExprContext too */
|
||||
ResetExprContext(pprune->context.planstate->ps_ExprContext);
|
||||
if (pprune->exec_pruning_steps)
|
||||
ResetExprContext(pprune->exec_context.planstate->ps_ExprContext);
|
||||
}
|
||||
|
||||
MemoryContextSwitchTo(oldcontext);
|
||||
@ -1890,15 +1926,15 @@ find_matching_subplans_recurse(PartitionPruningData *prunedata,
|
||||
check_stack_depth();
|
||||
|
||||
/* Only prune if pruning would be useful at this level. */
|
||||
if (initial_prune ? pprune->do_initial_prune : pprune->do_exec_prune)
|
||||
if (initial_prune && pprune->initial_pruning_steps)
|
||||
{
|
||||
PartitionPruneContext *context = &pprune->context;
|
||||
|
||||
/* Set whether we can evaluate PARAM_EXEC Params or not */
|
||||
context->evalexecparams = !initial_prune;
|
||||
|
||||
partset = get_matching_partitions(context,
|
||||
pprune->pruning_steps);
|
||||
partset = get_matching_partitions(&pprune->initial_context,
|
||||
pprune->initial_pruning_steps);
|
||||
}
|
||||
else if (!initial_prune && pprune->exec_pruning_steps)
|
||||
{
|
||||
partset = get_matching_partitions(&pprune->exec_context,
|
||||
pprune->exec_pruning_steps);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1203,6 +1203,8 @@ _copyPartitionedRelPruneInfo(const PartitionedRelPruneInfo *from)
|
||||
COPY_SCALAR_FIELD(do_initial_prune);
|
||||
COPY_SCALAR_FIELD(do_exec_prune);
|
||||
COPY_BITMAPSET_FIELD(execparamids);
|
||||
COPY_NODE_FIELD(initial_pruning_steps);
|
||||
COPY_NODE_FIELD(exec_pruning_steps);
|
||||
|
||||
return newnode;
|
||||
}
|
||||
|
@ -1052,6 +1052,8 @@ _outPartitionedRelPruneInfo(StringInfo str, const PartitionedRelPruneInfo *node)
|
||||
WRITE_BOOL_FIELD(do_initial_prune);
|
||||
WRITE_BOOL_FIELD(do_exec_prune);
|
||||
WRITE_BITMAPSET_FIELD(execparamids);
|
||||
WRITE_NODE_FIELD(initial_pruning_steps);
|
||||
WRITE_NODE_FIELD(exec_pruning_steps);
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -2375,6 +2375,8 @@ _readPartitionedRelPruneInfo(void)
|
||||
READ_BOOL_FIELD(do_initial_prune);
|
||||
READ_BOOL_FIELD(do_exec_prune);
|
||||
READ_BITMAPSET_FIELD(execparamids);
|
||||
READ_NODE_FIELD(initial_pruning_steps);
|
||||
READ_NODE_FIELD(exec_pruning_steps);
|
||||
|
||||
READ_DONE();
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -93,28 +93,30 @@ typedef struct PartitionTupleRouting
|
||||
* PartitionedRelPruneInfo (see plannodes.h); though note that here,
|
||||
* subpart_map contains indexes into PartitionPruningData.partrelprunedata[].
|
||||
*
|
||||
* nparts Length of subplan_map[] and subpart_map[].
|
||||
* subplan_map Subplan index by partition index, or -1.
|
||||
* subpart_map Subpart index by partition index, or -1.
|
||||
* present_parts A Bitmapset of the partition indexes that we
|
||||
* have subplans or subparts for.
|
||||
* context Contains the context details required to call
|
||||
* the partition pruning code.
|
||||
* pruning_steps List of PartitionPruneSteps used to
|
||||
* perform the actual pruning.
|
||||
* do_initial_prune true if pruning should be performed during
|
||||
* executor startup (for this partitioning level).
|
||||
* do_exec_prune true if pruning should be performed during
|
||||
* executor run (for this partitioning level).
|
||||
* initial_pruning_steps List of PartitionPruneSteps used to
|
||||
* perform executor startup pruning.
|
||||
* exec_pruning_steps List of PartitionPruneSteps used to
|
||||
* perform per-scan pruning.
|
||||
* initial_context If initial_pruning_steps isn't NIL, contains
|
||||
* the details needed to execute those steps.
|
||||
* exec_context If exec_pruning_steps isn't NIL, contains
|
||||
* the details needed to execute those steps.
|
||||
*/
|
||||
typedef struct PartitionedRelPruningData
|
||||
{
|
||||
int nparts;
|
||||
int *subplan_map;
|
||||
int *subpart_map;
|
||||
Bitmapset *present_parts;
|
||||
PartitionPruneContext context;
|
||||
List *pruning_steps;
|
||||
bool do_initial_prune;
|
||||
bool do_exec_prune;
|
||||
List *initial_pruning_steps;
|
||||
List *exec_pruning_steps;
|
||||
PartitionPruneContext initial_context;
|
||||
PartitionPruneContext exec_context;
|
||||
} PartitionedRelPruningData;
|
||||
|
||||
/*
|
||||
|
@ -1105,20 +1105,21 @@ typedef struct PartitionedRelPruneInfo
|
||||
{
|
||||
NodeTag type;
|
||||
Oid reloid; /* OID of partition rel for this level */
|
||||
List *pruning_steps; /* List of PartitionPruneStep, see below */
|
||||
List *pruning_steps; /* NOT USED anymore */
|
||||
Bitmapset *present_parts; /* Indexes of all partitions which subplans or
|
||||
* subparts are present for. */
|
||||
int nparts; /* Length of subplan_map[] and subpart_map[] */
|
||||
int nexprs; /* Length of hasexecparam[] */
|
||||
int nexprs; /* Length of hasexecparam[] (now always 0) */
|
||||
int *subplan_map; /* subplan index by partition index, or -1 */
|
||||
int *subpart_map; /* subpart index by partition index, or -1 */
|
||||
bool *hasexecparam; /* true if corresponding pruning_step contains
|
||||
* any PARAM_EXEC Params. */
|
||||
bool *hasexecparam; /* NOT USED anymore */
|
||||
bool do_initial_prune; /* true if pruning should be performed
|
||||
* during executor startup. */
|
||||
bool do_exec_prune; /* true if pruning should be performed during
|
||||
* executor run. */
|
||||
Bitmapset *execparamids; /* All PARAM_EXEC Param IDs in pruning_steps */
|
||||
Bitmapset *execparamids; /* All PARAM_EXEC Param IDs in exec_pruning_steps */
|
||||
List *initial_pruning_steps; /* List of PartitionPruneStep */
|
||||
List *exec_pruning_steps; /* List of PartitionPruneStep */
|
||||
} PartitionedRelPruneInfo;
|
||||
|
||||
/*
|
||||
|
@ -43,10 +43,6 @@
|
||||
* exprstates Array of ExprStates, indexed as per PruneCtxStateIdx; one
|
||||
* for each partition key in each pruning step. Allocated if
|
||||
* planstate is non-NULL, otherwise NULL.
|
||||
* exprhasexecparam Array of bools, each true if corresponding 'exprstate'
|
||||
* expression contains any PARAM_EXEC Params. (Can be NULL
|
||||
* if planstate is NULL.)
|
||||
* evalexecparams True if it's safe to evaluate PARAM_EXEC Params.
|
||||
*/
|
||||
typedef struct PartitionPruneContext
|
||||
{
|
||||
@ -61,8 +57,6 @@ typedef struct PartitionPruneContext
|
||||
MemoryContext ppccontext;
|
||||
PlanState *planstate;
|
||||
ExprState **exprstates;
|
||||
bool *exprhasexecparam;
|
||||
bool evalexecparams;
|
||||
} PartitionPruneContext;
|
||||
|
||||
/*
|
||||
|
@ -3085,6 +3085,44 @@ select * from mc3p where a < 3 and abs(b) = 1;
|
||||
Filter: ((a < 3) AND (abs(b) = 1))
|
||||
(7 rows)
|
||||
|
||||
--
|
||||
-- Check that pruning with composite range partitioning works correctly when
|
||||
-- a combination of runtime parameters is specified, not all of whose values
|
||||
-- are available at the same time
|
||||
--
|
||||
-- Note: this test doesn't actually prove much in v11, for lack of a way
|
||||
-- to force use of a generic plan.
|
||||
--
|
||||
prepare ps1 as
|
||||
select * from mc3p where a = $1 and abs(b) < (select 3);
|
||||
explain (analyze, costs off, summary off, timing off)
|
||||
execute ps1(1);
|
||||
QUERY PLAN
|
||||
-------------------------------------------------
|
||||
Append (actual rows=1 loops=1)
|
||||
InitPlan 1 (returns $0)
|
||||
-> Result (actual rows=1 loops=1)
|
||||
-> Seq Scan on mc3p1 (actual rows=1 loops=1)
|
||||
Filter: ((a = 1) AND (abs(b) < $0))
|
||||
(5 rows)
|
||||
|
||||
deallocate ps1;
|
||||
prepare ps2 as
|
||||
select * from mc3p where a <= $1 and abs(b) < (select 3);
|
||||
explain (analyze, costs off, summary off, timing off)
|
||||
execute ps2(1);
|
||||
QUERY PLAN
|
||||
-------------------------------------------------
|
||||
Append (actual rows=2 loops=1)
|
||||
InitPlan 1 (returns $0)
|
||||
-> Result (actual rows=1 loops=1)
|
||||
-> Seq Scan on mc3p0 (actual rows=1 loops=1)
|
||||
Filter: ((a <= 1) AND (abs(b) < $0))
|
||||
-> Seq Scan on mc3p1 (actual rows=1 loops=1)
|
||||
Filter: ((a <= 1) AND (abs(b) < $0))
|
||||
(7 rows)
|
||||
|
||||
deallocate ps2;
|
||||
drop table mc3p;
|
||||
-- Ensure runtime pruning works with initplans params with boolean types
|
||||
create table boolvalues (value bool not null);
|
||||
|
@ -774,6 +774,25 @@ insert into mc3p values (0, 1, 1), (1, 1, 1), (2, 1, 1);
|
||||
explain (analyze, costs off, summary off, timing off)
|
||||
select * from mc3p where a < 3 and abs(b) = 1;
|
||||
|
||||
--
|
||||
-- Check that pruning with composite range partitioning works correctly when
|
||||
-- a combination of runtime parameters is specified, not all of whose values
|
||||
-- are available at the same time
|
||||
--
|
||||
-- Note: this test doesn't actually prove much in v11, for lack of a way
|
||||
-- to force use of a generic plan.
|
||||
--
|
||||
prepare ps1 as
|
||||
select * from mc3p where a = $1 and abs(b) < (select 3);
|
||||
explain (analyze, costs off, summary off, timing off)
|
||||
execute ps1(1);
|
||||
deallocate ps1;
|
||||
prepare ps2 as
|
||||
select * from mc3p where a <= $1 and abs(b) < (select 3);
|
||||
explain (analyze, costs off, summary off, timing off)
|
||||
execute ps2(1);
|
||||
deallocate ps2;
|
||||
|
||||
drop table mc3p;
|
||||
|
||||
-- Ensure runtime pruning works with initplans params with boolean types
|
||||
|
Loading…
x
Reference in New Issue
Block a user