diff --git a/src/backend/executor/nodeAppend.c b/src/backend/executor/nodeAppend.c index 7a3dd2e..401ed5d 100644 --- a/src/backend/executor/nodeAppend.c +++ b/src/backend/executor/nodeAppend.c @@ -60,6 +60,9 @@ #include "executor/execdebug.h" #include "executor/nodeAppend.h" #include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "optimizer/partprune.h" +#include "utils/memutils.h" /* Shared state for parallel-aware Append. */ struct ParallelAppendState @@ -76,12 +79,25 @@ struct ParallelAppendState bool pa_finished[FLEXIBLE_ARRAY_MEMBER]; }; +struct PartitionPruneContextCache +{ + PartitionPruneContext *context; + PartitionPruneContextCache *subcache; +}; + #define INVALID_SUBPLAN_INDEX -1 static TupleTableSlot *ExecAppend(PlanState *pstate); static bool choose_next_subplan_locally(AppendState *node); static bool choose_next_subplan_for_leader(AppendState *node); static bool choose_next_subplan_for_worker(AppendState *node); +static void set_valid_runtime_subplans(AppendState *node); +static void set_valid_runtime_subplans_recurse(AppendState *node, + PartitionPruneInfo *pinfo, + PartitionPruneContextCache *ctxcache, + Bitmapset **validsubplans); +static void mark_invalid_subplans_as_finished(AppendState *node); + /* ---------------------------------------------------------------- * ExecInitAppend @@ -127,6 +143,34 @@ ExecInitAppend(Append *node, EState *estate, int eflags) appendstate->ps.ExecProcNode = ExecAppend; appendstate->appendplans = appendplanstates; appendstate->as_nplans = nplans; + appendstate->as_valid_subplans = NULL; + appendstate->part_prune_params = NULL; /* determined later */ + appendstate->part_prune_info = node->part_prune_info; + appendstate->contextcache = NULL; /* populate this as needed below */ + + if (node->part_prune_info) + { + /* + * When run-time partition pruning is enabled we make calls to a query + * planner function to determine which partitions will match. The + * planner is not too careful about freeing memory, so we'll ensure we + * call the function in a temporary memory context to avoid any memory + * leaking in the executor's memory context. + */ + appendstate->prune_context = + AllocSetContextCreate(CurrentMemoryContext, + "Partition Prune", + ALLOCSET_DEFAULT_SIZES); + } + else + { + /* + * When run-time partition pruning is not enabled we can just mark + * all subplans as valid. + */ + appendstate->as_valid_subplans = bms_add_range(NULL, 0, nplans - 1); + appendstate->prune_context = NULL; + } /* * Initialize result tuple type and slot. @@ -149,18 +193,14 @@ ExecInitAppend(Append *node, EState *estate, int eflags) /* * Miscellaneous initialization * - * Append plans don't have expression contexts because they never call - * ExecQual or ExecProject. + * create expression context for node */ + ExecAssignExprContext(estate, &appendstate->ps); + appendstate->ps.ps_ProjInfo = NULL; - /* - * Parallel-aware append plans must choose the first subplan to execute by - * looking at shared memory, but non-parallel-aware append plans can - * always start with the first subplan. - */ - appendstate->as_whichplan = - appendstate->ps.plan->parallel_aware ? INVALID_SUBPLAN_INDEX : 0; + /* Let choose_next_subplan_* function handle setting the first subplan */ + appendstate->as_whichplan = INVALID_SUBPLAN_INDEX; /* If parallel-aware, this will be overridden later. */ appendstate->choose_next_subplan = choose_next_subplan_locally; @@ -251,6 +291,18 @@ ExecReScanAppend(AppendState *node) { int i; + /* + * If any of the parameters being used for partition pruning have changed, + * then we'd better unset the valid subplans so that they can reselected + * for the new parameter values. + */ + if (node->part_prune_info && + bms_overlap(node->ps.chgParam, node->part_prune_params)) + { + bms_free(node->as_valid_subplans); + node->as_valid_subplans = NULL; + } + for (i = 0; i < node->as_nplans; i++) { PlanState *subnode = node->appendplans[i]; @@ -270,8 +322,8 @@ ExecReScanAppend(AppendState *node) ExecReScan(subnode); } - node->as_whichplan = - node->ps.plan->parallel_aware ? INVALID_SUBPLAN_INDEX : 0; + /* Let choose_next_subplan_* function handle setting the first subplan */ + node->as_whichplan = INVALID_SUBPLAN_INDEX; } /* ---------------------------------------------------------------- @@ -360,22 +412,35 @@ static bool choose_next_subplan_locally(AppendState *node) { int whichplan = node->as_whichplan; + int nextplan; - /* We should never see INVALID_SUBPLAN_INDEX in this case. */ - Assert(whichplan >= 0 && whichplan <= node->as_nplans); - - if (ScanDirectionIsForward(node->ps.state->es_direction)) + /* + * If first call then have the bms member function choose the first valid + * subplan by initializing whichplan to -1. If there happen to be no + * valid subplans then the bms member function will handle that by + * returning a negative number which will allow us to exit returning a + * false value. + */ + if (whichplan == INVALID_SUBPLAN_INDEX) { - if (whichplan >= node->as_nplans - 1) - return false; - node->as_whichplan++; + if (node->as_valid_subplans == NULL) + set_valid_runtime_subplans(node); + + whichplan = -1; } + + /* Ensure whichplan is within the expected range */ + Assert(whichplan >= -1 && whichplan <= node->as_nplans); + + if (ScanDirectionIsForward(node->ps.state->es_direction)) + nextplan = bms_next_member(node->as_valid_subplans, whichplan); else - { - if (whichplan <= 0) - return false; - node->as_whichplan--; - } + nextplan = bms_prev_member(node->as_valid_subplans, whichplan); + + if (nextplan < 0) + return false; + + node->as_whichplan = nextplan; return true; } @@ -408,6 +473,17 @@ choose_next_subplan_for_leader(AppendState *node) { /* Start with last subplan. */ node->as_whichplan = node->as_nplans - 1; + + /* + * If we've yet to determine the valid subplans for these parameters + * then do so now. If run-time pruning is disabled then the valid + * subplans will always be set to all subplans. + */ + if (node->as_valid_subplans == NULL) + { + set_valid_runtime_subplans(node); + mark_invalid_subplans_as_finished(node); + } } /* Loop until we find a subplan to execute. */ @@ -460,6 +536,17 @@ choose_next_subplan_for_worker(AppendState *node) if (node->as_whichplan != INVALID_SUBPLAN_INDEX) node->as_pstate->pa_finished[node->as_whichplan] = true; + /* + * If we've yet to determine the valid subplans for these parameters then + * do so now. If run-time pruning is disabled then the valid subplans + * will always be set to all subplans. + */ + else if (node->as_valid_subplans == NULL) + { + set_valid_runtime_subplans(node); + mark_invalid_subplans_as_finished(node); + } + /* If all the plans are already done, we have nothing to do */ if (pstate->pa_next_plan == INVALID_SUBPLAN_INDEX) { @@ -525,3 +612,210 @@ choose_next_subplan_for_worker(AppendState *node) return true; } + +/* + * set_valid_runtime_subplans + * Determine which subset of subplan nodes we need to scan based on + * the details stored in node's 'part_prune_info'. All subplans which + * provably cannot possibly have matching records are eliminated and the + * remainder are set in the AppendState's 'as_valid_subplans' variable. + */ +static void +set_valid_runtime_subplans(AppendState *node) +{ + MemoryContext oldcontext; + Bitmapset *validsubplans = NULL; + + /* Should never be called when already set */ + Assert(node->as_valid_subplans == NULL); + + if (!node->contextcache) + node->contextcache = palloc0(sizeof(PartitionPruneContextCache)); + + /* + * Switch to a temp context to avoid leaking memory in the + * executor's memory context. + */ + oldcontext = MemoryContextSwitchTo(node->prune_context); + + + set_valid_runtime_subplans_recurse(node, node->part_prune_info, + node->contextcache, + &validsubplans); + + MemoryContextSwitchTo(oldcontext); + + /* Move to the correct memory context */ + node->as_valid_subplans = bms_copy(validsubplans); + + MemoryContextReset(node->prune_context); +} + +static void +set_valid_runtime_subplans_recurse(AppendState *node, + PartitionPruneInfo *pinfo, + PartitionPruneContextCache *ctxcache, + Bitmapset **validsubplans) +{ + PartitionPruneContext *context; + Bitmapset *partset; + int i; + + check_stack_depth(); + + /* + * If the PartitionPruneContext has not yet been initialized for this rel + * yet, then do that now. + */ + if (!ctxcache->context) + { + PartitionDesc partdesc; + Relation rel; + PartitionKey partkey; + ListCell *lc; + int i; + MemoryContext oldContext; + + oldContext = MemoryContextSwitchTo(node->ps.state->es_query_cxt); + + ctxcache->context = context = palloc(sizeof(PartitionPruneContext)); + ctxcache->subcache = palloc0(sizeof(PartitionPruneContextCache) * + pinfo->nparts); + + rel = relation_open(pinfo->parentoid, NoLock); + + partkey = RelationGetPartitionKey(rel); + partdesc = RelationGetPartitionDesc(rel); + + context->relid = pinfo->relid; + context->strategy = partkey->strategy; + context->partnatts = partkey->partnatts; + context->partkeys = palloc(sizeof(Expr *) * context->partnatts); + + lc = list_head(partkey->partexprs); + + for (i = 0; i < context->partnatts; i++) + { + AttrNumber attno = partkey->partattrs[i]; + + if (attno != InvalidAttrNumber) + { + Assert(attno > 0); + + context->partkeys[i] = (Expr *) makeVar(pinfo->relid, + attno, + partkey->parttypid[i], + partkey->parttypmod[i], + partkey->parttypcoll[i], + 0); + } + else + { + if (lc == NULL) + elog(ERROR, "wrong number of partition key expressions"); + + context->partkeys[i] = (Expr *) lfirst(lc); + lc = lnext(lc); + } + } + + context->parttypid = partkey->parttypid; + context->partopfamily = partkey->partopfamily; + context->partcollation = partkey->partcollation; + context->partsupfunc = partkey->partsupfunc; + context->nparts = pinfo->nparts; + context->boundinfo = partition_bounds_copy(partdesc->boundinfo, partkey); + + if (OidIsValid(get_default_oid_from_partdesc(partdesc))) + context->has_default_part = true; + else + context->has_default_part = false; + + context->partition_qual = RelationGetPartitionQual(rel); + + context->prmlist = node->ps.state->es_param_list_info; + context->econtext = node->ps.ps_ExprContext; + context->paramids = NULL; + + generate_partition_clauses(context, pinfo->prunequal); + + node->part_prune_params = bms_add_members(node->part_prune_params, + context->paramids); + + relation_close(rel, NoLock); + + MemoryContextSwitchTo(oldContext); + } + else + context = ctxcache->context; + + /* + * Detect if any impossibilities were discovered during + * generate_partition_clauses + */ + if (context->clauseinfo->constfalse) + { + bms_free(*validsubplans); + *validsubplans = NULL; + return; + } + + /* Determine which partition indexes we need to scan */ + partset = get_partitions_from_clauses(context); + + /* Translate partset into subnode indexes */ + i = -1; + while ((i = bms_next_member(partset, i)) >= 0) + { + if (pinfo->subnodeindex[i] >= 0) + *validsubplans = bms_add_member(*validsubplans, + pinfo->subnodeindex[i]); + else if (pinfo->subpartindex[i] != NULL) + set_valid_runtime_subplans_recurse(node, pinfo->subpartindex[i], + &ctxcache->subcache[i], + validsubplans); + else + { + /* + * If this happens then we're somehow missing an Append subnode. + * This shouldn't happen and could only happen if a more + * restrictive clause list was used for partition elimination + * during planning than was used here. + */ + elog(ERROR, "partition missing from Append subplans"); + } + } + + bms_free(partset); +} + +/* + * mark_invalid_subplans_as_finished + * Marks the ParallelAppendState's pa_finished as true for each invalid + * subplan. + * + * This function should only be called for parallel Append with run-time + * pruning enabled. + */ +static void +mark_invalid_subplans_as_finished(AppendState *node) +{ + int i; + + /* Only valid to call this while in parallel Append mode */ + Assert(node->as_pstate); + + /* Shouldn't have been called when run-time pruning is not enabled */ + Assert(node->part_prune_info != NULL); + + /* Nothing to do if all plans are valid */ + if (bms_num_members(node->as_valid_subplans) == node->as_nplans) + return; + + /* Mark all non-valid plans as finished */ + for (i = 0; i < node->as_nplans; i++) + { + if (!bms_is_member(i, node->as_valid_subplans)) + node->as_pstate->pa_finished[i] = true; + } +} diff --git a/src/backend/nodes/bitmapset.c b/src/backend/nodes/bitmapset.c index edcd19a..3578c8f 100644 --- a/src/backend/nodes/bitmapset.c +++ b/src/backend/nodes/bitmapset.c @@ -58,6 +58,9 @@ * rightmost_one_pos[x] gives the bit number (0-7) of the rightmost one bit * in a nonzero byte value x. The entry for x=0 is never used. * + * leftmost_ons_pos[x] gives the bit number (0-7) of the leftmost one bit in a + * nonzero byte value x. The entry for x=0 is never used. + * * number_of_ones[x] gives the number of one-bits (0-8) in a byte value x. * * We could make these tables larger and reduce the number of iterations @@ -84,6 +87,25 @@ static const uint8 rightmost_one_pos[256] = { 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0 }; +static const uint8 leftmost_one_pos[256] = { + 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 +}; + static const uint8 number_of_ones[256] = { 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, @@ -1089,6 +1111,79 @@ bms_next_member(const Bitmapset *a, int prevbit) } /* + * bms_prev_member - find prev member of a set + * + * Returns largest member less than "prevbit", or -2 if there is none. + * "prevbit" must NOT be more than one above the highest possible bit that can + * be set at the Bitmapset at its current size. + * + * To ease finding the highest set bit for the initial loop, the special + * prevbit value of -1 can be passed to have the function find the highest + * valued member in the set. + * + * This is intended as support for iterating through the members of a set in + * reverse. The typical pattern is + * + * x = -1; + * while ((x = bms_prev_member(inputset, x)) >= 0) + * process member x; + * + * Notice that when there are no more members, we return -2, not -1 as you + * might expect. The rationale for that is to allow distinguishing the + * loop-not-started state (x == -1) from the loop-completed state (x == -2). + * It makes no difference in simple loop usage, but complex iteration logic + * might need such an ability. + */ + +int +bms_prev_member(const Bitmapset *a, int prevbit) +{ + int wordnum; + int ushiftbits; + bitmapword mask; + + /* + * If set is NULL or if there are no more bits to the right then we've + * nothing to do. + */ + if (a == NULL || prevbit == 0) + return -2; + + /* transform -1 to the highest possible bit we could have set */ + if (prevbit == -1) + prevbit = a->nwords * BITS_PER_BITMAPWORD - 1; + else + prevbit--; + + ushiftbits = BITS_PER_BITMAPWORD - (BITNUM(prevbit) + 1); + mask = (~(bitmapword) 0) >> ushiftbits; + for (wordnum = WORDNUM(prevbit); wordnum >= 0; wordnum--) + { + bitmapword w = a->words[wordnum]; + + /* mask out bits left of prevbit */ + w &= mask; + + if (w != 0) + { + int result; + int shift = 24; + result = wordnum * BITS_PER_BITMAPWORD; + + while ((w >> shift) == 0) + shift -= 8; + + result += shift + leftmost_one_pos[(w >> shift) & 255]; + return result; + } + + /* in subsequent words, consider all bits */ + mask = (~(bitmapword) 0); + } + return -2; +} + +/* * bms_hash_value - compute a hash key for a Bitmapset * * Note: we must ensure that any two bitmapsets that are bms_equal() will diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index d844b8b..1dc7651 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -244,6 +244,7 @@ _copyAppend(const Append *from) COPY_NODE_FIELD(partitioned_rels); COPY_NODE_FIELD(appendplans); COPY_SCALAR_FIELD(first_partial_plan); + COPY_NODE_FIELD(part_prune_info); return newnode; } @@ -2151,6 +2152,33 @@ _copyPartitionClauseInfo(const PartitionClauseInfo *from) return newnode; } +static PartitionPruneInfo * +_copyPartitionPruneInfo(const PartitionPruneInfo *from) +{ + PartitionPruneInfo *newnode = makeNode(PartitionPruneInfo); + int i; + + COPY_SCALAR_FIELD(relid); + COPY_SCALAR_FIELD(parentoid); + COPY_NODE_FIELD(prunequal); + COPY_SCALAR_FIELD(nparts); + COPY_POINTER_FIELD(subnodeindex, from->nparts * sizeof(int)); + COPY_POINTER_FIELD(subpartindex, from->nparts * + sizeof(PartitionPruneInfo *)); + + /* + * The above copied the entire array, but we still need to create copies + * of each PartitionPruneInfo contained in that array. + */ + for (i = 0; i < from->nparts; i++) + { + if (newnode->subpartindex[i] != NULL) + COPY_NODE_FIELD(subpartindex[i]); + } + + return newnode; +} + /* **************************************************************** * relation.h copy functions * @@ -5049,6 +5077,9 @@ copyObjectImpl(const void *from) case T_PlaceHolderInfo: retval = _copyPlaceHolderInfo(from); break; + case T_PartitionPruneInfo: + retval = _copyPartitionPruneInfo(from); + break; /* * VALUE NODES diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index d40429a..8a02887 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -1587,7 +1587,7 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, * if we have zero or one live subpath due to constraint exclusion.) */ if (subpaths_valid) - add_path(rel, (Path *) create_append_path(rel, subpaths, NIL, + add_path(rel, (Path *) create_append_path(root, rel, subpaths, NIL, NULL, 0, false, partitioned_rels, -1)); @@ -1629,8 +1629,8 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, Assert(parallel_workers > 0); /* Generate a partial append path. */ - appendpath = create_append_path(rel, NIL, partial_subpaths, NULL, - parallel_workers, + appendpath = create_append_path(root, rel, NIL, partial_subpaths, + NULL, parallel_workers, enable_parallel_append, partitioned_rels, -1); @@ -1678,7 +1678,7 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, max_parallel_workers_per_gather); Assert(parallel_workers > 0); - appendpath = create_append_path(rel, pa_nonpartial_subpaths, + appendpath = create_append_path(root, rel, pa_nonpartial_subpaths, pa_partial_subpaths, NULL, parallel_workers, true, partitioned_rels, partial_rows); @@ -1734,7 +1734,7 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, if (subpaths_valid) add_path(rel, (Path *) - create_append_path(rel, subpaths, NIL, + create_append_path(root, rel, subpaths, NIL, required_outer, 0, false, partitioned_rels, -1)); } @@ -2000,7 +2000,7 @@ set_dummy_rel_pathlist(RelOptInfo *rel) rel->pathlist = NIL; rel->partial_pathlist = NIL; - add_path(rel, (Path *) create_append_path(rel, NIL, NIL, NULL, + add_path(rel, (Path *) create_append_path(NULL, rel, NIL, NIL, NULL, 0, false, NIL, -1)); /* diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 3f1c1b3..2e289d4 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -1230,7 +1230,7 @@ mark_dummy_rel(RelOptInfo *rel) rel->partial_pathlist = NIL; /* Set up the dummy path */ - add_path(rel, (Path *) create_append_path(rel, NIL, NIL, NULL, + add_path(rel, (Path *) create_append_path(NULL, rel, NIL, NIL, NULL, 0, false, NIL, -1)); /* Set or update cheapest_total_path and related fields */ diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index da0cc7f..d82fee7 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -29,6 +29,7 @@ #include "nodes/nodeFuncs.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" +#include "optimizer/partprune.h" #include "optimizer/paths.h" #include "optimizer/placeholder.h" #include "optimizer/plancat.h" @@ -204,7 +205,8 @@ static NamedTuplestoreScan *make_namedtuplestorescan(List *qptlist, List *qpqual static WorkTableScan *make_worktablescan(List *qptlist, List *qpqual, Index scanrelid, int wtParam); static Append *make_append(List *appendplans, int first_partial_plan, - List *tlist, List *partitioned_rels); + List *tlist, List *partitioned_rels, + PartitionPruneInfo *partpruneinfo); static RecursiveUnion *make_recursive_union(List *tlist, Plan *lefttree, Plan *righttree, @@ -1022,6 +1024,8 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path) List *tlist = build_path_tlist(root, &best_path->path); List *subplans = NIL; ListCell *subpaths; + RelOptInfo *rel = best_path->path.parent; + PartitionPruneInfo *pinfo = NULL; /* * The subpaths list could be empty, if every child was proven empty by @@ -1059,6 +1063,41 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path) subplans = lappend(subplans, subplan); } + + if (best_path->trypartitionprune) + { + List *prunequal; + + /* Not for join rels */ + Assert(bms_membership(rel->relids) == BMS_SINGLETON); + + prunequal = + extract_actual_clauses(best_path->path.parent->baserestrictinfo, + false); + + if (best_path->path.param_info) + { + + List *prmquals = best_path->path.param_info->ppi_clauses; + + prmquals = extract_actual_clauses(prmquals, false); + prmquals = (List *) replace_nestloop_params(root, + (Node *) prmquals); + + prunequal = list_concat(prunequal, prmquals); + } + + /* + * If any quals exist that could possibly be useful to use for + * performing further partition pruning during execution, then + * we'll generate a PartitionPruneInfo to store these quals and + * allow translation of partition indexes into subpath indexes. + */ + if (prunequal != NIL) + pinfo = make_partition_pruneinfo(root, best_path->path.parent, + best_path->partitioned_rels, + best_path->subpaths, prunequal); + } /* * XXX ideally, if there's just one child, we'd not bother to generate an * Append node but just return the single child. At the moment this does @@ -1067,7 +1106,8 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path) */ plan = make_append(subplans, best_path->first_partial_path, - tlist, best_path->partitioned_rels); + tlist, best_path->partitioned_rels, + pinfo); copy_generic_path_info(&plan->plan, (Path *) best_path); @@ -5320,7 +5360,8 @@ make_foreignscan(List *qptlist, static Append * make_append(List *appendplans, int first_partial_plan, - List *tlist, List *partitioned_rels) + List *tlist, List *partitioned_rels, + PartitionPruneInfo *partpruneinfo) { Append *node = makeNode(Append); Plan *plan = &node->plan; @@ -5332,7 +5373,7 @@ make_append(List *appendplans, int first_partial_plan, node->partitioned_rels = partitioned_rels; node->appendplans = appendplans; node->first_partial_plan = first_partial_plan; - + node->part_prune_info = partpruneinfo; return node; } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 646d118..f720e8d 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -3717,7 +3717,8 @@ create_grouping_paths(PlannerInfo *root, paths = lappend(paths, path); } path = (Path *) - create_append_path(grouped_rel, + create_append_path(root, + grouped_rel, paths, NIL, NULL, diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index f01119e..146e202 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -593,7 +593,7 @@ generate_union_path(SetOperationStmt *op, PlannerInfo *root, /* * Append the child results together. */ - path = (Path *) create_append_path(result_rel, pathlist, NIL, + path = (Path *) create_append_path(root, result_rel, pathlist, NIL, NULL, 0, false, NIL, -1); /* We have to manually jam the right tlist into the path; ick */ path->pathtarget = create_pathtarget(root, tlist); @@ -705,7 +705,7 @@ generate_nonunion_path(SetOperationStmt *op, PlannerInfo *root, /* * Append the child results together. */ - path = (Path *) create_append_path(result_rel, pathlist, NIL, + path = (Path *) create_append_path(root, result_rel, pathlist, NIL, NULL, 0, false, NIL, -1); /* We have to manually jam the right tlist into the path; ick */ diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 0c1f239..dccbcc1 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -2473,6 +2473,25 @@ eval_const_expressions(PlannerInfo *root, Node *node) } /*-------------------- + * eval_const_expressions_from_list + * + * This is similar to eval_const_expression except that it takes ParamListInfo + * argument instead of PlannerInfo to create the context. + */ +Node * +eval_const_expressions_from_list(ParamListInfo prmlist, Node *node) +{ + eval_const_expressions_context context; + + context.boundParams = prmlist; /* bound Params */ + context.root = NULL; + context.active_fns = NIL; /* nothing being recursively simplified */ + context.case_val = NULL; /* no CASE being examined */ + context.estimate = false; /* safe transformations only */ + return eval_const_expressions_mutator(node, &context); +} + +/*-------------------- * estimate_expression_value * * This function attempts to estimate the value of an expression for diff --git a/src/backend/optimizer/util/partprune.c b/src/backend/optimizer/util/partprune.c index 71a7b7b..683dd5d 100644 --- a/src/backend/optimizer/util/partprune.c +++ b/src/backend/optimizer/util/partprune.c @@ -56,12 +56,16 @@ #include "catalog/pg_operator.h" #include "catalog/pg_opfamily.h" #include "catalog/pg_type.h" +#include "executor/nodeSubplan.h" +#include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/clauses.h" #include "optimizer/partprune.h" +#include "optimizer/pathnode.h" #include "optimizer/planner.h" #include "optimizer/predtest.h" +#include "optimizer/prep.h" #include "parser/parse_coerce.h" #include "parser/parsetree.h" #include "rewrite/rewriteManip.h" @@ -101,14 +105,15 @@ static Bitmapset *get_partitions_from_or_args(PartitionPruneContext *context, List *or_args); static void remove_redundant_clauses(PartitionPruneContext *context, List **minimalclauses); -static bool partition_cmp_args(Oid parttypid, Oid partopfamily, - PartClause *pc, PartClause *leftarg, PartClause *rightarg, - bool *result); +static bool partition_cmp_args(PartitionPruneContext *context, Oid parttypid, + Oid partopfamily, PartClause *pc, PartClause *leftarg, + PartClause *rightarg, bool *result); static bool extract_bounding_datums(PartitionPruneContext *context, List **minimalclauses, PartScanKeyInfo *keys); static PartOpStrategy partition_op_strategy(char part_strategy, PartClause *pc, bool *incl); -static bool partkey_datum_from_expr(Oid parttypid, Expr *expr, Datum *value); +static bool partkey_datum_from_expr(PartitionPruneContext *context, Oid parttypid, + Expr *expr, Datum *value); /* * prune_append_rel_partitions @@ -154,6 +159,9 @@ prune_append_rel_partitions(PlannerInfo *root, RelOptInfo *rel) context.boundinfo = rel->boundinfo; context.has_default_part = rel->has_default_part; context.partition_qual = rel->partition_qual; + context.prmlist = NULL; + context.econtext = NULL; + context.paramids = NULL; /* process clauses; context.clauseinfo will be set */ generate_partition_clauses(&context, clauses); @@ -487,6 +495,10 @@ extract_partition_clauses(PartitionPruneContext *context, List *clauses) pc->inputcollid = opclause->inputcollid; pc->value = valueexpr; + if (IsA(valueexpr, Param)) + context->paramids = bms_add_member(context->paramids, + ((Param *) valueexpr)->paramid); + /* * We don't turn a <> operator clause into a key right away. * Instead, the caller will hand over such clauses to @@ -641,6 +653,11 @@ extract_partition_clauses(PartitionPruneContext *context, List *clauses) leftop, rightop, InvalidOid, saop_coll); + + if (IsA(rightop, Param)) + context->paramids = bms_add_member(context->paramids, + ((Param *) rightop)->paramid); + elem_clauses = lappend(elem_clauses, elem_clause); } @@ -891,7 +908,8 @@ remove_redundant_clauses(PartitionPruneContext *context, if (hash_clause == NULL) hash_clause = pc; /* check if another clause would contradict the one we have */ - else if (partition_cmp_args(context->parttypid[i], + else if (partition_cmp_args(context, + context->parttypid[i], context->partopfamily[i], pc, pc, hash_clause, &test_result)) @@ -948,7 +966,8 @@ remove_redundant_clauses(PartitionPruneContext *context, * then because 7 < 5 is false, we leave a < 5 where it is and * effectively discard a < 7 as being redundant. */ - if (partition_cmp_args(context->parttypid[i], + if (partition_cmp_args(context, + context->parttypid[i], context->partopfamily[i], pc, pc, btree_clauses[s], &test_result)) @@ -1005,7 +1024,8 @@ remove_redundant_clauses(PartitionPruneContext *context, * eq clause is a = 3, then because 3 < 5, we no longer need * a < 5, because a = 3 is more restrictive. */ - if (partition_cmp_args(context->parttypid[i], + if (partition_cmp_args(context, + context->parttypid[i], context->partopfamily[i], chk, eq, chk, &test_result)) @@ -1036,7 +1056,8 @@ remove_redundant_clauses(PartitionPruneContext *context, PartClause *lt = btree_clauses[BTLessStrategyNumber - 1], *le = btree_clauses[BTLessEqualStrategyNumber - 1]; - if (partition_cmp_args(context->parttypid[i], + if (partition_cmp_args(context, + context->parttypid[i], context->partopfamily[i], le, lt, le, &test_result)) @@ -1055,7 +1076,8 @@ remove_redundant_clauses(PartitionPruneContext *context, PartClause *gt = btree_clauses[BTGreaterStrategyNumber - 1], *ge = btree_clauses[BTGreaterEqualStrategyNumber - 1]; - if (partition_cmp_args(context->parttypid[i], + if (partition_cmp_args(context, + context->parttypid[i], context->partopfamily[i], ge, gt, ge, &test_result)) @@ -1093,9 +1115,9 @@ remove_redundant_clauses(PartitionPruneContext *context, * incompatible with the operator. */ static bool -partition_cmp_args(Oid parttypid, Oid partopfamily, - PartClause *pc, PartClause *leftarg, PartClause *rightarg, - bool *result) +partition_cmp_args(PartitionPruneContext *context, Oid parttypid, + Oid partopfamily, PartClause *pc, PartClause *leftarg, + PartClause *rightarg, bool *result) { Datum left_value; Datum right_value; @@ -1106,10 +1128,12 @@ partition_cmp_args(Oid parttypid, Oid partopfamily, * Try to extract an actual value from each arg. This may fail if the * value is unknown in this context, in which case we cannot compare. */ - if (!partkey_datum_from_expr(parttypid, leftarg->value, &left_value)) + if (!partkey_datum_from_expr(context, parttypid, leftarg->value, + &left_value)) return false; - if (!partkey_datum_from_expr(parttypid, rightarg->value, &right_value)) + if (!partkey_datum_from_expr(context, parttypid, rightarg->value, + &right_value)) return false; /* @@ -1232,12 +1256,16 @@ extract_bounding_datums(PartitionPruneContext *context, case PART_OP_EQUAL: Assert(incl); if (need_next_eq && - partkey_datum_from_expr(context->parttypid[i], value, + partkey_datum_from_expr(context, + context->parttypid[i], + value, &keys->eqkeys[i])) keys->n_eqkeys++; if (need_next_max && - partkey_datum_from_expr(context->parttypid[i], value, + partkey_datum_from_expr(context, + context->parttypid[i], + value, &keys->maxkeys[i])) { keys->n_maxkeys++; @@ -1245,7 +1273,9 @@ extract_bounding_datums(PartitionPruneContext *context, } if (need_next_min && - partkey_datum_from_expr(context->parttypid[i], value, + partkey_datum_from_expr(context, + context->parttypid[i], + value, &keys->minkeys[i])) { keys->n_minkeys++; @@ -1255,7 +1285,9 @@ extract_bounding_datums(PartitionPruneContext *context, case PART_OP_LESS: if (need_next_max && - partkey_datum_from_expr(context->parttypid[i], value, + partkey_datum_from_expr(context, + context->parttypid[i], + value, &keys->maxkeys[i])) { keys->n_maxkeys++; @@ -1267,7 +1299,9 @@ extract_bounding_datums(PartitionPruneContext *context, case PART_OP_GREATER: if (need_next_min && - partkey_datum_from_expr(context->parttypid[i], value, + partkey_datum_from_expr(context, + context->parttypid[i], + value, &keys->minkeys[i])) { keys->n_minkeys++; @@ -1314,8 +1348,8 @@ extract_bounding_datums(PartitionPruneContext *context, PartClause *pc = (PartClause *) lfirst(lc); Datum datum; - if (partkey_datum_from_expr(context->parttypid[0], pc->value, - &datum)) + if (partkey_datum_from_expr(context, context->parttypid[0], + pc->value, &datum)) keys->ne_datums[i++] = datum; } keys->n_ne_datums = i; @@ -1391,7 +1425,8 @@ partition_op_strategy(char part_strategy, PartClause *pc, bool *incl) * set. True is returned otherwise. */ static bool -partkey_datum_from_expr(Oid parttypid, Expr *expr, Datum *value) +partkey_datum_from_expr(PartitionPruneContext *context, Oid parttypid, + Expr *expr, Datum *value) { Oid exprtype = exprType((Node *) expr); @@ -1429,11 +1464,177 @@ partkey_datum_from_expr(Oid parttypid, Expr *expr, Datum *value) * Add more expression types here as needed to support the requirements * of the higher-level code. */ - if (IsA(expr, Const)) + switch (nodeTag(expr)) { - *value = ((Const *) expr)->constvalue; - return true; + case T_Const: + *value = ((Const *) expr)->constvalue; + return true; + + case T_Param: + switch (((Param *) expr)->paramkind) + { + case PARAM_EXTERN: + if (context->prmlist) + { + Node *node; + Param *param = (Param *) expr; + ParamListInfo prmlist = context->prmlist; + + node = eval_const_expressions_from_list(prmlist, + (Node *) param); + if (IsA(node, Const)) + { + *value = ((Const *) node)->constvalue; + return true; + } + } + + case PARAM_EXEC: + if (context->econtext) + { + Param *param = (Param *) expr; + ParamExecData *prm; + ExprContext *econtext = context->econtext; + + prm = &(econtext->ecxt_param_exec_vals[param->paramid]); + if (unlikely(prm->execPlan != NULL)) + { + ExecSetParamPlan((SubPlanState *) prm->execPlan, + econtext); + Assert(prm->execPlan == NULL); + } + *value = prm->value; + return true; + } + } } return false; } + +/* + * make_partition_pruneinfo + * Build PartitionPruneInfo tree to allow the output of + * get_partitions_from_clauses to be translated into + * 'subpaths' indexes. This is required in order to allow + * us to perform any further partition pruning during execution. + */ +PartitionPruneInfo * +make_partition_pruneinfo(PlannerInfo *root, RelOptInfo *rel, + List *partition_rels, List *subpaths, + List *prunequal) +{ + PartitionPruneInfo *pinfo; + AppendRelInfo *appinfo; + RangeTblEntry *rte; + ListCell *lc; + int i; + int partidx; + int nparts = rel->nparts; + + check_stack_depth(); + + rte = root->simple_rte_array[rel->relid]; + + pinfo = makeNode(PartitionPruneInfo); + pinfo->relid = rel->relid; + pinfo->parentoid = rte->relid; + pinfo->prunequal = prunequal; + pinfo->nparts = nparts; + pinfo->subnodeindex = (int *) palloc(sizeof(int) * nparts); + pinfo->subpartindex = (PartitionPruneInfo **) + palloc0(sizeof(PartitionPruneInfo *) * nparts); + /* + * -1 represents a partition that has been pruned. Set them all to this + * initially. We'll determine the subpath index for the non-pruned + * ones below. + */ + for (i = 0; i < nparts; i++) + pinfo->subnodeindex[i] = -1; + + i = -1; + foreach(lc, subpaths) + { + Path *path = (Path *) lfirst(lc); + + i++; /* track subnode index */ + + /* Find the AppendRelInfo for the Append child */ + appinfo = find_childrel_appendrelinfo(root, path->parent); + + /* + * Skip subpaths which belong to relations not directly parented by + * rel. We'll process any we skip here below when looping through + * partition_rels + */ + if (appinfo->parent_relid != rel->relid) + continue; + + /* Determine the element in part_rel which belongs to this subpath. */ + for (partidx = 0; partidx < nparts; partidx++) + { + if (rel->part_rels[partidx]->relid != appinfo->child_relid) + continue; + + /* found it! Save the subnode index */ + pinfo->subnodeindex[partidx] = i; + break; + } + } + + /* + * Some of the relations returned by get_partitions_from_clauses may be + * other partitioned tables. Unlike the case above, these won't be + * subpaths of the Append. To handle these we must create a + * sub-PartitionPruneInfo to allow us to determine if subnodes which + * belong to sub-partitioned tables are required during partition pruning. + */ + foreach(lc, partition_rels) + { + Index rti = lfirst_int(lc); + RelOptInfo *subpart = find_base_rel(root, rti); + + /* + * partition_rels contains the rti of the base relation being queried. + * We only care about sub-partition parents here, so skip this. + */ + if (subpart->reloptkind == RELOPT_BASEREL) + continue; + + appinfo = find_childrel_appendrelinfo(root, subpart); + + /* + * We only want to deal with sub-partition parents that are directly + * below rel. We'll deal with any we skip here later in a recursive + * call which is made below. + */ + if (appinfo->parent_relid != rel->relid) + continue; + + /* + * Handle sub-partition parents by building a sub-PartitionPruneInfo. + */ + for (partidx = 0; partidx < nparts; partidx++) + { + List *subprunequal; + + if (rel->part_rels[partidx]->relid != appinfo->child_relid) + continue; + + /* Adjust the prune qual to be compatible with this subpartition */ + subprunequal = (List *) adjust_appendrel_attrs(root, + (Node *) prunequal, + 1, + &appinfo); + + pinfo->subpartindex[partidx] = make_partition_pruneinfo(root, + subpart, + partition_rels, + subpaths, + subprunequal); + break; + } + } + + return pinfo; +} diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index fe3b458..f3ab0d9 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -1210,7 +1210,8 @@ create_tidscan_path(PlannerInfo *root, RelOptInfo *rel, List *tidquals, * Note that we must handle subpaths = NIL, representing a dummy access path. */ AppendPath * -create_append_path(RelOptInfo *rel, +create_append_path(PlannerInfo *root, + RelOptInfo *rel, List *subpaths, List *partial_subpaths, Relids required_outer, int parallel_workers, bool parallel_aware, @@ -1224,8 +1225,36 @@ create_append_path(RelOptInfo *rel, pathnode->path.pathtype = T_Append; pathnode->path.parent = rel; pathnode->path.pathtarget = rel->reltarget; - pathnode->path.param_info = get_appendrel_parampathinfo(rel, - required_outer); + pathnode->trypartitionprune = false; + + /* + * When generating an Append path for a partitioned table we'll try to + * enable additional partition pruning at run-time. Useful pruning quals + * may be in parameterized path quals, so we'll go all the way and + * generate the qual list for the Append's parameterized paths. We need + * only bother trying this for RELOPT_BASEREL rels, as + * RELOPT_OTHER_MEMBER_REL's Append paths are merged into the base rel's + * Append subpaths. + */ + if (rel->reloptkind == RELOPT_BASEREL && root) + { + RangeTblEntry *rte = planner_rt_fetch(rel->relid, root); + + if (rte->rtekind == RTE_RELATION && + rte->relkind == RELKIND_PARTITIONED_TABLE) + { + pathnode->path.param_info = get_baserel_parampathinfo(root, + rel, + required_outer); + pathnode->trypartitionprune = true; + } + else + pathnode->path.param_info = get_appendrel_parampathinfo(rel, + required_outer); + } + else + pathnode->path.param_info = get_appendrel_parampathinfo(rel, + required_outer); pathnode->path.parallel_aware = parallel_aware; pathnode->path.parallel_safe = rel->consider_parallel; pathnode->path.parallel_workers = parallel_workers; @@ -3567,7 +3596,7 @@ reparameterize_path(PlannerInfo *root, Path *path, i++; } return (Path *) - create_append_path(rel, childpaths, partialpaths, + create_append_path(root, rel, childpaths, partialpaths, required_outer, apath->path.parallel_workers, apath->path.parallel_aware, diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h index 0dd6bd3..27d70fc 100644 --- a/src/include/catalog/partition.h +++ b/src/include/catalog/partition.h @@ -16,6 +16,7 @@ #include "fmgr.h" #include "executor/tuptable.h" #include "nodes/execnodes.h" +#include "nodes/relation.h" #include "parser/parse_node.h" #include "utils/rel.h" @@ -70,6 +71,11 @@ typedef struct PartitionPruneContext /* Information about matched clauses */ PartitionClauseInfo *clauseinfo; + ParamListInfo prmlist; + ExprContext *econtext; + + /* ParamIds of clauses being used to determine partitions */ + Bitmapset *paramids; } PartitionPruneContext; /* diff --git a/src/include/nodes/bitmapset.h b/src/include/nodes/bitmapset.h index 67e8920..b6f1a9e 100644 --- a/src/include/nodes/bitmapset.h +++ b/src/include/nodes/bitmapset.h @@ -99,6 +99,7 @@ extern Bitmapset *bms_join(Bitmapset *a, Bitmapset *b); /* support for iterating through the integer elements of a set: */ extern int bms_first_member(Bitmapset *a); extern int bms_next_member(const Bitmapset *a, int prevbit); +extern int bms_prev_member(const Bitmapset *a, int prevbit); /* support for hashtables using Bitmapsets as keys: */ extern uint32 bms_hash_value(const Bitmapset *a); diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index a953820..7db3a79 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -1010,11 +1010,14 @@ typedef struct ModifyTableState * * nplans how many plans are in the array * whichplan which plan is being executed (0 .. n-1) + * valid_subplans for runtime pruning, valid appendplans indexes to scan * ---------------- */ struct AppendState; typedef struct AppendState AppendState; +struct PartitionPruneContextCache; +typedef struct PartitionPruneContextCache PartitionPruneContextCache; struct ParallelAppendState; typedef struct ParallelAppendState ParallelAppendState; @@ -1026,6 +1029,11 @@ struct AppendState int as_whichplan; ParallelAppendState *as_pstate; /* parallel coordination info */ Size pstate_len; /* size of parallel coordination info */ + Bitmapset *as_valid_subplans; /* mask of non-pruned subplans */ + Bitmapset *part_prune_params; /* ParamIds useful for partition pruning */ + PartitionPruneInfo *part_prune_info; /* details for partition pruning */ + PartitionPruneContextCache *contextcache; /* cache of prune contexts */ + MemoryContext prune_context; /* used when calling planner pruning code */ bool (*choose_next_subplan) (AppendState *); }; diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 81d223c..1ca7d64 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -192,6 +192,7 @@ typedef enum NodeTag T_OnConflictExpr, T_PartitionClauseInfo, T_IntoClause, + T_PartitionPruneInfo, /* * TAGS FOR EXPRESSION STATE NODES (execnodes.h) diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index f2e19ea..a3aeb95 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -250,6 +250,13 @@ typedef struct Append List *partitioned_rels; List *appendplans; int first_partial_plan; + + /* + * Mapping details for run-time subplan pruning. This allows translation + * from partition numbers into subplan indexes. This is set to NULL when + * run-time subplan pruning is disabled. + */ + PartitionPruneInfo *part_prune_info; } Append; /* ---------------- diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 6cfb876..9f538d2 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -1546,4 +1546,26 @@ typedef struct PartitionClauseInfo bool constfalse; } PartitionClauseInfo; +/*---------- + * PartitionPruneInfo - Allows pruning of Append subplans + * + * Here we store mapping details to allow translation of a partitioned table's + * id number into an Append node's subplan index. This structure is used + * to recursively search for all subplan nodes when there are sub-partitioned + * tables in the Append plan. + *---------- + */ +typedef struct PartitionPruneInfo +{ + NodeTag type; + int relid; /* relation index of parent partition rel */ + Oid parentoid; /* Oid of parent partition rel */ + List *prunequal; /* qual list for pruning partitions */ + int nparts; /* length of the following arrays */ + int *subnodeindex; /* subnode index indexed by partition id */ + + /* sub-PartitionPruneInfo indexed by partition id */ + struct PartitionPruneInfo **subpartindex; +} PartitionPruneInfo; + #endif /* PRIMNODES_H */ diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 5579940..4d17f9a 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -1294,6 +1294,8 @@ typedef struct AppendPath /* Index of first partial path in subpaths */ int first_partial_path; + + bool trypartitionprune; /* Attempt to enable partition pruning? */ } AppendPath; #define IS_DUMMY_PATH(p) \ diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h index 3c2f549..bedffc4 100644 --- a/src/include/optimizer/clauses.h +++ b/src/include/optimizer/clauses.h @@ -80,6 +80,9 @@ extern void CommuteRowCompareExpr(RowCompareExpr *clause); extern Node *eval_const_expressions(PlannerInfo *root, Node *node); +extern Node *eval_const_expressions_from_list(ParamListInfo prmlist, + Node *node); + extern Node *estimate_expression_value(PlannerInfo *root, Node *node); extern Query *inline_set_returning_function(PlannerInfo *root, diff --git a/src/include/optimizer/partprune.h b/src/include/optimizer/partprune.h index 5c0d469..57701e4 100644 --- a/src/include/optimizer/partprune.h +++ b/src/include/optimizer/partprune.h @@ -22,4 +22,9 @@ extern void generate_partition_clauses(PartitionPruneContext *context, List *clauses); extern Bitmapset *get_partitions_from_clauses(PartitionPruneContext *context); +extern PartitionPruneInfo *make_partition_pruneinfo(PlannerInfo *root, + RelOptInfo *rel, + List *partition_rels, List *subpaths, + List *prunequal); + #endif /* PARTPRUNE_H */ diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index ef7173f..bde1858 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -64,7 +64,7 @@ extern BitmapOrPath *create_bitmap_or_path(PlannerInfo *root, List *bitmapquals); extern TidPath *create_tidscan_path(PlannerInfo *root, RelOptInfo *rel, List *tidquals, Relids required_outer); -extern AppendPath *create_append_path(RelOptInfo *rel, +extern AppendPath *create_append_path(PlannerInfo *root, RelOptInfo *rel, List *subpaths, List *partial_subpaths, Relids required_outer, int parallel_workers, bool parallel_aware, diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out index bc9ff38..478820f 100644 --- a/src/test/regress/expected/partition_prune.out +++ b/src/test/regress/expected/partition_prune.out @@ -1419,3 +1419,959 @@ explain (costs off) select * from rlp where a = 15 and b <> 'ab' and b <> 'cd' a (5 rows) drop table lp, coll_pruning, rlp, mc3p, mc2p, boolpart, hp, rp; +-- +-- Test runtime partition pruning +-- +create table ab (a int not null, b int not null) partition by list (a); +create table ab_a2 partition of ab for values in(2) partition by list (b); +create table ab_a2_b1 partition of ab_a2 for values in (1); +create table ab_a2_b2 partition of ab_a2 for values in (2); +create table ab_a2_b3 partition of ab_a2 for values in (3); +create table ab_a1 partition of ab for values in(1) partition by list (b); +create table ab_a1_b1 partition of ab_a1 for values in (1); +create table ab_a1_b2 partition of ab_a1 for values in (2); +create table ab_a1_b3 partition of ab_a1 for values in (3); +create table ab_a3 partition of ab for values in(3) partition by list (b); +create table ab_a3_b1 partition of ab_a3 for values in (1); +create table ab_a3_b2 partition of ab_a3 for values in (2); +create table ab_a3_b3 partition of ab_a3 for values in (3); +prepare ab_q1 (int, int, int) as +select * from ab where a between $1 and $2 and b <= $3; +-- Execute query 5 times to allow choose_custom_plan +-- to start considering a generic plan. +execute ab_q1 (1, 8, 3); + a | b +---+--- +(0 rows) + +execute ab_q1 (1, 8, 3); + a | b +---+--- +(0 rows) + +execute ab_q1 (1, 8, 3); + a | b +---+--- +(0 rows) + +execute ab_q1 (1, 8, 3); + a | b +---+--- +(0 rows) + +execute ab_q1 (1, 8, 3); + a | b +---+--- +(0 rows) + +explain (analyze, costs off, summary off, timing off) execute ab_q1 (2, 2, 3); + QUERY PLAN +--------------------------------------------------------- + Append (actual rows=0 loops=1) + -> Seq Scan on ab_a1_b1 (never executed) + Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) + -> Seq Scan on ab_a1_b2 (never executed) + Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) + -> Seq Scan on ab_a1_b3 (never executed) + Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) + -> Seq Scan on ab_a2_b1 (actual rows=0 loops=1) + Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) + -> Seq Scan on ab_a2_b2 (actual rows=0 loops=1) + Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) + -> Seq Scan on ab_a2_b3 (actual rows=0 loops=1) + Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) + -> Seq Scan on ab_a3_b1 (never executed) + Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) + -> Seq Scan on ab_a3_b2 (never executed) + Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) + -> Seq Scan on ab_a3_b3 (never executed) + Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) +(19 rows) + +explain (analyze, costs off, summary off, timing off) execute ab_q1 (1, 2, 3); + QUERY PLAN +--------------------------------------------------------- + Append (actual rows=0 loops=1) + -> Seq Scan on ab_a1_b1 (actual rows=0 loops=1) + Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) + -> Seq Scan on ab_a1_b2 (actual rows=0 loops=1) + Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) + -> Seq Scan on ab_a1_b3 (actual rows=0 loops=1) + Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) + -> Seq Scan on ab_a2_b1 (actual rows=0 loops=1) + Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) + -> Seq Scan on ab_a2_b2 (actual rows=0 loops=1) + Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) + -> Seq Scan on ab_a2_b3 (actual rows=0 loops=1) + Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) + -> Seq Scan on ab_a3_b1 (never executed) + Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) + -> Seq Scan on ab_a3_b2 (never executed) + Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) + -> Seq Scan on ab_a3_b3 (never executed) + Filter: ((a >= $1) AND (a <= $2) AND (b <= $3)) +(19 rows) + +deallocate ab_q1; +-- runtime pruning after optimizer pruning +prepare ab_q1 (int, int) as +select a from ab where a between $1 and $2 and b < 3; +-- Execute query 5 times to allow choose_custom_plan +-- to start considering a generic plan. +execute ab_q1 (1, 8); + a +--- +(0 rows) + +execute ab_q1 (1, 8); + a +--- +(0 rows) + +execute ab_q1 (1, 8); + a +--- +(0 rows) + +execute ab_q1 (1, 8); + a +--- +(0 rows) + +execute ab_q1 (1, 8); + a +--- +(0 rows) + +explain (analyze, costs off, summary off, timing off) execute ab_q1 (2, 2); + QUERY PLAN +------------------------------------------------------- + Append (actual rows=0 loops=1) + -> Seq Scan on ab_a1_b1 (never executed) + Filter: ((a >= $1) AND (a <= $2) AND (b < 3)) + -> Seq Scan on ab_a1_b2 (never executed) + Filter: ((a >= $1) AND (a <= $2) AND (b < 3)) + -> Seq Scan on ab_a2_b1 (actual rows=0 loops=1) + Filter: ((a >= $1) AND (a <= $2) AND (b < 3)) + -> Seq Scan on ab_a2_b2 (actual rows=0 loops=1) + Filter: ((a >= $1) AND (a <= $2) AND (b < 3)) + -> Seq Scan on ab_a3_b1 (never executed) + Filter: ((a >= $1) AND (a <= $2) AND (b < 3)) + -> Seq Scan on ab_a3_b2 (never executed) + Filter: ((a >= $1) AND (a <= $2) AND (b < 3)) +(13 rows) + +explain (analyze, costs off, summary off, timing off) execute ab_q1 (2, 4); + QUERY PLAN +------------------------------------------------------- + Append (actual rows=0 loops=1) + -> Seq Scan on ab_a1_b1 (never executed) + Filter: ((a >= $1) AND (a <= $2) AND (b < 3)) + -> Seq Scan on ab_a1_b2 (never executed) + Filter: ((a >= $1) AND (a <= $2) AND (b < 3)) + -> Seq Scan on ab_a2_b1 (actual rows=0 loops=1) + Filter: ((a >= $1) AND (a <= $2) AND (b < 3)) + -> Seq Scan on ab_a2_b2 (actual rows=0 loops=1) + Filter: ((a >= $1) AND (a <= $2) AND (b < 3)) + -> Seq Scan on ab_a3_b1 (actual rows=0 loops=1) + Filter: ((a >= $1) AND (a <= $2) AND (b < 3)) + -> Seq Scan on ab_a3_b2 (actual rows=0 loops=1) + Filter: ((a >= $1) AND (a <= $2) AND (b < 3)) +(13 rows) + +-- parallel append +prepare ab_q2 (int, int) as +select avg(a) from ab where a between $1 and $2 and b < 4; +-- encourage use of parallel plans +set parallel_setup_cost = 0; +set parallel_tuple_cost = 0; +set min_parallel_table_scan_size = 0; +set max_parallel_workers_per_gather = 2; +-- Execute query 5 times to allow choose_custom_plan +-- to start considering a generic plan. +execute ab_q2 (1, 8); + avg +----- + +(1 row) + +execute ab_q2 (1, 8); + avg +----- + +(1 row) + +execute ab_q2 (1, 8); + avg +----- + +(1 row) + +execute ab_q2 (1, 8); + avg +----- + +(1 row) + +execute ab_q2 (1, 8); + avg +----- + +(1 row) + +explain (analyze, costs off, summary off, timing off) execute ab_q2 (2, 2); + QUERY PLAN +------------------------------------------------------------------------------- + Finalize Aggregate (actual rows=1 loops=1) + -> Gather (actual rows=3 loops=1) + Workers Planned: 2 + Workers Launched: 2 + -> Partial Aggregate (actual rows=1 loops=3) + -> Parallel Append (actual rows=0 loops=3) + -> Parallel Seq Scan on ab_a1_b1 (never executed) + Filter: ((a >= $1) AND (a <= $2) AND (b < 4)) + -> Parallel Seq Scan on ab_a1_b2 (never executed) + Filter: ((a >= $1) AND (a <= $2) AND (b < 4)) + -> Parallel Seq Scan on ab_a1_b3 (never executed) + Filter: ((a >= $1) AND (a <= $2) AND (b < 4)) + -> Parallel Seq Scan on ab_a2_b1 (actual rows=0 loops=1) + Filter: ((a >= $1) AND (a <= $2) AND (b < 4)) + -> Parallel Seq Scan on ab_a2_b2 (actual rows=0 loops=1) + Filter: ((a >= $1) AND (a <= $2) AND (b < 4)) + -> Parallel Seq Scan on ab_a2_b3 (actual rows=0 loops=1) + Filter: ((a >= $1) AND (a <= $2) AND (b < 4)) + -> Parallel Seq Scan on ab_a3_b1 (never executed) + Filter: ((a >= $1) AND (a <= $2) AND (b < 4)) + -> Parallel Seq Scan on ab_a3_b2 (never executed) + Filter: ((a >= $1) AND (a <= $2) AND (b < 4)) + -> Parallel Seq Scan on ab_a3_b3 (never executed) + Filter: ((a >= $1) AND (a <= $2) AND (b < 4)) +(24 rows) + +-- Test run-time pruning with IN lists. +prepare ab_q3 (int, int, int) as +select avg(a) from ab where a in($1,$2,$3) and b < 4; +-- Execute query 5 times to allow choose_custom_plan +-- to start considering a generic plan. +execute ab_q3 (1, 2, 3); + avg +----- + +(1 row) + +execute ab_q3 (1, 2, 3); + avg +----- + +(1 row) + +execute ab_q3 (1, 2, 3); + avg +----- + +(1 row) + +execute ab_q3 (1, 2, 3); + avg +----- + +(1 row) + +execute ab_q3 (1, 2, 3); + avg +----- + +(1 row) + +explain (analyze, costs off, summary off, timing off) execute ab_q3 (1, 1, 1); + QUERY PLAN +------------------------------------------------------------------------------- + Finalize Aggregate (actual rows=1 loops=1) + -> Gather (actual rows=3 loops=1) + Workers Planned: 2 + Workers Launched: 2 + -> Partial Aggregate (actual rows=1 loops=3) + -> Parallel Append (actual rows=0 loops=3) + -> Parallel Seq Scan on ab_a1_b1 (actual rows=0 loops=1) + Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) + -> Parallel Seq Scan on ab_a1_b2 (actual rows=0 loops=1) + Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) + -> Parallel Seq Scan on ab_a1_b3 (actual rows=0 loops=1) + Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) + -> Parallel Seq Scan on ab_a2_b1 (never executed) + Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) + -> Parallel Seq Scan on ab_a2_b2 (never executed) + Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) + -> Parallel Seq Scan on ab_a2_b3 (never executed) + Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) + -> Parallel Seq Scan on ab_a3_b1 (never executed) + Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) + -> Parallel Seq Scan on ab_a3_b2 (never executed) + Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) + -> Parallel Seq Scan on ab_a3_b3 (never executed) + Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) +(24 rows) + +explain (analyze, costs off, summary off, timing off) execute ab_q3 (2, 3, 3); + QUERY PLAN +------------------------------------------------------------------------------- + Finalize Aggregate (actual rows=1 loops=1) + -> Gather (actual rows=3 loops=1) + Workers Planned: 2 + Workers Launched: 2 + -> Partial Aggregate (actual rows=1 loops=3) + -> Parallel Append (actual rows=0 loops=3) + -> Parallel Seq Scan on ab_a1_b1 (never executed) + Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) + -> Parallel Seq Scan on ab_a1_b2 (never executed) + Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) + -> Parallel Seq Scan on ab_a1_b3 (never executed) + Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) + -> Parallel Seq Scan on ab_a2_b1 (actual rows=0 loops=1) + Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) + -> Parallel Seq Scan on ab_a2_b2 (actual rows=0 loops=1) + Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) + -> Parallel Seq Scan on ab_a2_b3 (actual rows=0 loops=1) + Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) + -> Parallel Seq Scan on ab_a3_b1 (actual rows=0 loops=1) + Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) + -> Parallel Seq Scan on ab_a3_b2 (actual rows=0 loops=1) + Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) + -> Parallel Seq Scan on ab_a3_b3 (actual rows=0 loops=1) + Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) +(24 rows) + +-- try some params whose values do not belong to any partition +explain (analyze, costs off, summary off, timing off) execute ab_q3 (33, 44, 55); + QUERY PLAN +------------------------------------------------------------------------------- + Finalize Aggregate (actual rows=1 loops=1) + -> Gather (actual rows=3 loops=1) + Workers Planned: 2 + Workers Launched: 2 + -> Partial Aggregate (actual rows=1 loops=3) + -> Parallel Append (actual rows=0 loops=3) + -> Parallel Seq Scan on ab_a1_b1 (never executed) + Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) + -> Parallel Seq Scan on ab_a1_b2 (never executed) + Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) + -> Parallel Seq Scan on ab_a1_b3 (never executed) + Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) + -> Parallel Seq Scan on ab_a2_b1 (never executed) + Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) + -> Parallel Seq Scan on ab_a2_b2 (never executed) + Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) + -> Parallel Seq Scan on ab_a2_b3 (never executed) + Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) + -> Parallel Seq Scan on ab_a3_b1 (never executed) + Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) + -> Parallel Seq Scan on ab_a3_b2 (never executed) + Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) + -> Parallel Seq Scan on ab_a3_b3 (never executed) + Filter: ((b < 4) AND (a = ANY (ARRAY[$1, $2, $3]))) +(24 rows) + +-- test parallel Append with IN list and parameterized nested loops +create table lprt_a (a int not null); +-- insert some values we won't find in ab +insert into lprt_a select 0 from generate_series(1,100); +-- and insert some values that we should find. +insert into lprt_a values(1),(1); +analyze lprt_a; +create index ab_a2_b1_a_idx on ab_a2_b1 (a); +create index ab_a2_b2_a_idx on ab_a2_b2 (a); +create index ab_a2_b3_a_idx on ab_a2_b3 (a); +create index ab_a1_b1_a_idx on ab_a1_b1 (a); +create index ab_a1_b2_a_idx on ab_a1_b2 (a); +create index ab_a1_b3_a_idx on ab_a1_b3 (a); +create index ab_a3_b1_a_idx on ab_a3_b1 (a); +create index ab_a3_b2_a_idx on ab_a3_b2 (a); +create index ab_a3_b3_a_idx on ab_a3_b3 (a); +set enable_hashjoin = 0; +set enable_mergejoin = 0; +prepare ab_q4 (int, int, int) as +select avg(ab.a) from ab inner join lprt_a a on ab.a = a.a where a.a in($1,$2,$3); +execute ab_q4 (1, 2, 3); + avg +----- + +(1 row) + +execute ab_q4 (1, 2, 3); + avg +----- + +(1 row) + +execute ab_q4 (1, 2, 3); + avg +----- + +(1 row) + +execute ab_q4 (1, 2, 3); + avg +----- + +(1 row) + +execute ab_q4 (1, 2, 3); + avg +----- + +(1 row) + +explain (analyze, costs off, summary off, timing off) execute ab_q4 (0, 0, 1); + QUERY PLAN +-------------------------------------------------------------------------------------------------------- + Finalize Aggregate (actual rows=1 loops=1) + -> Gather (actual rows=2 loops=1) + Workers Planned: 1 + Workers Launched: 1 + -> Partial Aggregate (actual rows=1 loops=2) + -> Nested Loop (actual rows=0 loops=2) + -> Parallel Seq Scan on lprt_a a (actual rows=51 loops=2) + Filter: (a = ANY ('{0,0,1}'::integer[])) + -> Append (actual rows=0 loops=102) + -> Index Only Scan using ab_a1_b1_a_idx on ab_a1_b1 (actual rows=0 loops=2) + Index Cond: (a = a.a) + Heap Fetches: 0 + -> Index Only Scan using ab_a1_b2_a_idx on ab_a1_b2 (actual rows=0 loops=2) + Index Cond: (a = a.a) + Heap Fetches: 0 + -> Index Only Scan using ab_a1_b3_a_idx on ab_a1_b3 (actual rows=0 loops=2) + Index Cond: (a = a.a) + Heap Fetches: 0 + -> Index Only Scan using ab_a2_b1_a_idx on ab_a2_b1 (never executed) + Index Cond: (a = a.a) + Heap Fetches: 0 + -> Index Only Scan using ab_a2_b2_a_idx on ab_a2_b2 (never executed) + Index Cond: (a = a.a) + Heap Fetches: 0 + -> Index Only Scan using ab_a2_b3_a_idx on ab_a2_b3 (never executed) + Index Cond: (a = a.a) + Heap Fetches: 0 + -> Index Only Scan using ab_a3_b1_a_idx on ab_a3_b1 (never executed) + Index Cond: (a = a.a) + Heap Fetches: 0 + -> Index Only Scan using ab_a3_b2_a_idx on ab_a3_b2 (never executed) + Index Cond: (a = a.a) + Heap Fetches: 0 + -> Index Only Scan using ab_a3_b3_a_idx on ab_a3_b3 (never executed) + Index Cond: (a = a.a) + Heap Fetches: 0 +(36 rows) + +insert into lprt_a values(3),(3); +explain (analyze, costs off, summary off, timing off) execute ab_q4 (1, 0, 3); + QUERY PLAN +-------------------------------------------------------------------------------------------------------- + Finalize Aggregate (actual rows=1 loops=1) + -> Gather (actual rows=2 loops=1) + Workers Planned: 1 + Workers Launched: 1 + -> Partial Aggregate (actual rows=1 loops=2) + -> Nested Loop (actual rows=0 loops=2) + -> Parallel Seq Scan on lprt_a a (actual rows=52 loops=2) + Filter: (a = ANY ('{1,0,3}'::integer[])) + -> Append (actual rows=0 loops=104) + -> Index Only Scan using ab_a1_b1_a_idx on ab_a1_b1 (actual rows=0 loops=2) + Index Cond: (a = a.a) + Heap Fetches: 0 + -> Index Only Scan using ab_a1_b2_a_idx on ab_a1_b2 (actual rows=0 loops=2) + Index Cond: (a = a.a) + Heap Fetches: 0 + -> Index Only Scan using ab_a1_b3_a_idx on ab_a1_b3 (actual rows=0 loops=2) + Index Cond: (a = a.a) + Heap Fetches: 0 + -> Index Only Scan using ab_a2_b1_a_idx on ab_a2_b1 (never executed) + Index Cond: (a = a.a) + Heap Fetches: 0 + -> Index Only Scan using ab_a2_b2_a_idx on ab_a2_b2 (never executed) + Index Cond: (a = a.a) + Heap Fetches: 0 + -> Index Only Scan using ab_a2_b3_a_idx on ab_a2_b3 (never executed) + Index Cond: (a = a.a) + Heap Fetches: 0 + -> Index Only Scan using ab_a3_b1_a_idx on ab_a3_b1 (actual rows=0 loops=2) + Index Cond: (a = a.a) + Heap Fetches: 0 + -> Index Only Scan using ab_a3_b2_a_idx on ab_a3_b2 (actual rows=0 loops=2) + Index Cond: (a = a.a) + Heap Fetches: 0 + -> Index Only Scan using ab_a3_b3_a_idx on ab_a3_b3 (actual rows=0 loops=2) + Index Cond: (a = a.a) + Heap Fetches: 0 +(36 rows) + +explain (analyze, costs off, summary off, timing off) execute ab_q4 (1, 0, 0); + QUERY PLAN +-------------------------------------------------------------------------------------------------------- + Finalize Aggregate (actual rows=1 loops=1) + -> Gather (actual rows=2 loops=1) + Workers Planned: 1 + Workers Launched: 1 + -> Partial Aggregate (actual rows=1 loops=2) + -> Nested Loop (actual rows=0 loops=2) + -> Parallel Seq Scan on lprt_a a (actual rows=51 loops=2) + Filter: (a = ANY ('{1,0,0}'::integer[])) + Rows Removed by Filter: 1 + -> Append (actual rows=0 loops=102) + -> Index Only Scan using ab_a1_b1_a_idx on ab_a1_b1 (actual rows=0 loops=2) + Index Cond: (a = a.a) + Heap Fetches: 0 + -> Index Only Scan using ab_a1_b2_a_idx on ab_a1_b2 (actual rows=0 loops=2) + Index Cond: (a = a.a) + Heap Fetches: 0 + -> Index Only Scan using ab_a1_b3_a_idx on ab_a1_b3 (actual rows=0 loops=2) + Index Cond: (a = a.a) + Heap Fetches: 0 + -> Index Only Scan using ab_a2_b1_a_idx on ab_a2_b1 (never executed) + Index Cond: (a = a.a) + Heap Fetches: 0 + -> Index Only Scan using ab_a2_b2_a_idx on ab_a2_b2 (never executed) + Index Cond: (a = a.a) + Heap Fetches: 0 + -> Index Only Scan using ab_a2_b3_a_idx on ab_a2_b3 (never executed) + Index Cond: (a = a.a) + Heap Fetches: 0 + -> Index Only Scan using ab_a3_b1_a_idx on ab_a3_b1 (never executed) + Index Cond: (a = a.a) + Heap Fetches: 0 + -> Index Only Scan using ab_a3_b2_a_idx on ab_a3_b2 (never executed) + Index Cond: (a = a.a) + Heap Fetches: 0 + -> Index Only Scan using ab_a3_b3_a_idx on ab_a3_b3 (never executed) + Index Cond: (a = a.a) + Heap Fetches: 0 +(37 rows) + +delete from lprt_a where a = 1; +explain (analyze, costs off, summary off, timing off) execute ab_q4 (1, 0, 0); + QUERY PLAN +------------------------------------------------------------------------------------------------- + Finalize Aggregate (actual rows=1 loops=1) + -> Gather (actual rows=2 loops=1) + Workers Planned: 1 + Workers Launched: 1 + -> Partial Aggregate (actual rows=1 loops=2) + -> Nested Loop (actual rows=0 loops=2) + -> Parallel Seq Scan on lprt_a a (actual rows=50 loops=2) + Filter: (a = ANY ('{1,0,0}'::integer[])) + Rows Removed by Filter: 1 + -> Append (actual rows=0 loops=100) + -> Index Only Scan using ab_a1_b1_a_idx on ab_a1_b1 (never executed) + Index Cond: (a = a.a) + Heap Fetches: 0 + -> Index Only Scan using ab_a1_b2_a_idx on ab_a1_b2 (never executed) + Index Cond: (a = a.a) + Heap Fetches: 0 + -> Index Only Scan using ab_a1_b3_a_idx on ab_a1_b3 (never executed) + Index Cond: (a = a.a) + Heap Fetches: 0 + -> Index Only Scan using ab_a2_b1_a_idx on ab_a2_b1 (never executed) + Index Cond: (a = a.a) + Heap Fetches: 0 + -> Index Only Scan using ab_a2_b2_a_idx on ab_a2_b2 (never executed) + Index Cond: (a = a.a) + Heap Fetches: 0 + -> Index Only Scan using ab_a2_b3_a_idx on ab_a2_b3 (never executed) + Index Cond: (a = a.a) + Heap Fetches: 0 + -> Index Only Scan using ab_a3_b1_a_idx on ab_a3_b1 (never executed) + Index Cond: (a = a.a) + Heap Fetches: 0 + -> Index Only Scan using ab_a3_b2_a_idx on ab_a3_b2 (never executed) + Index Cond: (a = a.a) + Heap Fetches: 0 + -> Index Only Scan using ab_a3_b3_a_idx on ab_a3_b3 (never executed) + Index Cond: (a = a.a) + Heap Fetches: 0 +(37 rows) + +reset enable_hashjoin; +reset enable_mergejoin; +reset parallel_setup_cost; +reset parallel_tuple_cost; +reset min_parallel_table_scan_size; +reset max_parallel_workers_per_gather; +-- Test run-time partition pruning with an initplan +explain (analyze, costs off, summary off, timing off) +select * from ab where a = (select max(a) from lprt_a) and b = (select max(a)-1 from lprt_a); + QUERY PLAN +------------------------------------------------------------------------- + Append (actual rows=0 loops=1) + InitPlan 1 (returns $0) + -> Aggregate (actual rows=1 loops=1) + -> Seq Scan on lprt_a (actual rows=102 loops=1) + InitPlan 2 (returns $1) + -> Aggregate (actual rows=1 loops=1) + -> Seq Scan on lprt_a lprt_a_1 (actual rows=102 loops=1) + -> Bitmap Heap Scan on ab_a1_b1 (never executed) + Recheck Cond: (a = $0) + Filter: (b = $1) + -> Bitmap Index Scan on ab_a1_b1_a_idx (never executed) + Index Cond: (a = $0) + -> Bitmap Heap Scan on ab_a1_b2 (never executed) + Recheck Cond: (a = $0) + Filter: (b = $1) + -> Bitmap Index Scan on ab_a1_b2_a_idx (never executed) + Index Cond: (a = $0) + -> Bitmap Heap Scan on ab_a1_b3 (never executed) + Recheck Cond: (a = $0) + Filter: (b = $1) + -> Bitmap Index Scan on ab_a1_b3_a_idx (never executed) + Index Cond: (a = $0) + -> Bitmap Heap Scan on ab_a2_b1 (never executed) + Recheck Cond: (a = $0) + Filter: (b = $1) + -> Bitmap Index Scan on ab_a2_b1_a_idx (never executed) + Index Cond: (a = $0) + -> Bitmap Heap Scan on ab_a2_b2 (never executed) + Recheck Cond: (a = $0) + Filter: (b = $1) + -> Bitmap Index Scan on ab_a2_b2_a_idx (never executed) + Index Cond: (a = $0) + -> Bitmap Heap Scan on ab_a2_b3 (never executed) + Recheck Cond: (a = $0) + Filter: (b = $1) + -> Bitmap Index Scan on ab_a2_b3_a_idx (never executed) + Index Cond: (a = $0) + -> Bitmap Heap Scan on ab_a3_b1 (never executed) + Recheck Cond: (a = $0) + Filter: (b = $1) + -> Bitmap Index Scan on ab_a3_b1_a_idx (never executed) + Index Cond: (a = $0) + -> Bitmap Heap Scan on ab_a3_b2 (actual rows=0 loops=1) + Recheck Cond: (a = $0) + Filter: (b = $1) + -> Bitmap Index Scan on ab_a3_b2_a_idx (actual rows=0 loops=1) + Index Cond: (a = $0) + -> Bitmap Heap Scan on ab_a3_b3 (never executed) + Recheck Cond: (a = $0) + Filter: (b = $1) + -> Bitmap Index Scan on ab_a3_b3_a_idx (never executed) + Index Cond: (a = $0) +(52 rows) + +deallocate ab_q1; +deallocate ab_q2; +deallocate ab_q3; +deallocate ab_q4; +drop table ab, lprt_a; +-- join +create table tbl1(col1 int); +insert into tbl1 values (501), (505); +-- basic table +create table tprt (col1 int) partition by range (col1); +create table tprt_1 partition of tprt for values from (1) to (501); +create table tprt_2 partition of tprt for values from (501) to (1001); +create table tprt_3 partition of tprt for values from (1001) to (2001); +create table tprt_4 partition of tprt for values from (2001) to (3001); +create table tprt_5 partition of tprt for values from (3001) to (4001); +create table tprt_6 partition of tprt for values from (4001) to (5001); +create index tprt1_idx on tprt_1 (col1); +create index tprt2_idx on tprt_2 (col1); +create index tprt3_idx on tprt_3 (col1); +create index tprt4_idx on tprt_4 (col1); +create index tprt5_idx on tprt_5 (col1); +create index tprt6_idx on tprt_6 (col1); +insert into tprt values (10), (20), (501), (502), (505), (1001), (4500); +set enable_hashjoin = off; +set enable_mergejoin = off; +explain (analyze, costs off, summary off, timing off) +select * from tbl1 join tprt on tbl1.col1 > tprt.col1; + QUERY PLAN +------------------------------------------------------------------------------- + Nested Loop (actual rows=6 loops=1) + -> Seq Scan on tbl1 (actual rows=2 loops=1) + -> Append (actual rows=3 loops=2) + -> Index Only Scan using tprt1_idx on tprt_1 (actual rows=2 loops=2) + Index Cond: (col1 < tbl1.col1) + Heap Fetches: 4 + -> Index Only Scan using tprt2_idx on tprt_2 (actual rows=2 loops=1) + Index Cond: (col1 < tbl1.col1) + Heap Fetches: 2 + -> Index Only Scan using tprt3_idx on tprt_3 (never executed) + Index Cond: (col1 < tbl1.col1) + Heap Fetches: 0 + -> Index Only Scan using tprt4_idx on tprt_4 (never executed) + Index Cond: (col1 < tbl1.col1) + Heap Fetches: 0 + -> Index Only Scan using tprt5_idx on tprt_5 (never executed) + Index Cond: (col1 < tbl1.col1) + Heap Fetches: 0 + -> Index Only Scan using tprt6_idx on tprt_6 (never executed) + Index Cond: (col1 < tbl1.col1) + Heap Fetches: 0 +(21 rows) + +explain (analyze, costs off, summary off, timing off) +select * from tbl1 join tprt on tbl1.col1 = tprt.col1; + QUERY PLAN +------------------------------------------------------------------------------- + Nested Loop (actual rows=2 loops=1) + -> Seq Scan on tbl1 (actual rows=2 loops=1) + -> Append (actual rows=1 loops=2) + -> Index Only Scan using tprt1_idx on tprt_1 (never executed) + Index Cond: (col1 = tbl1.col1) + Heap Fetches: 0 + -> Index Only Scan using tprt2_idx on tprt_2 (actual rows=1 loops=2) + Index Cond: (col1 = tbl1.col1) + Heap Fetches: 2 + -> Index Only Scan using tprt3_idx on tprt_3 (never executed) + Index Cond: (col1 = tbl1.col1) + Heap Fetches: 0 + -> Index Only Scan using tprt4_idx on tprt_4 (never executed) + Index Cond: (col1 = tbl1.col1) + Heap Fetches: 0 + -> Index Only Scan using tprt5_idx on tprt_5 (never executed) + Index Cond: (col1 = tbl1.col1) + Heap Fetches: 0 + -> Index Only Scan using tprt6_idx on tprt_6 (never executed) + Index Cond: (col1 = tbl1.col1) + Heap Fetches: 0 +(21 rows) + +select tbl1.col1, tprt.col1 from tbl1 +inner join tprt on tbl1.col1 > tprt.col1 +order by tbl1.col1, tprt.col1; + col1 | col1 +------+------ + 501 | 10 + 501 | 20 + 505 | 10 + 505 | 20 + 505 | 501 + 505 | 502 +(6 rows) + +select tbl1.col1, tprt.col1 from tbl1 +inner join tprt on tbl1.col1 = tprt.col1 +order by tbl1.col1, tprt.col1; + col1 | col1 +------+------ + 501 | 501 + 505 | 505 +(2 rows) + +-- multiple partitions +insert into tbl1 values (1001), (1010), (1011); +explain (analyze, costs off, summary off, timing off) +select * from tbl1 inner join tprt on tbl1.col1 > tprt.col1; + QUERY PLAN +------------------------------------------------------------------------------- + Nested Loop (actual rows=23 loops=1) + -> Seq Scan on tbl1 (actual rows=5 loops=1) + -> Append (actual rows=5 loops=5) + -> Index Only Scan using tprt1_idx on tprt_1 (actual rows=2 loops=5) + Index Cond: (col1 < tbl1.col1) + Heap Fetches: 10 + -> Index Only Scan using tprt2_idx on tprt_2 (actual rows=3 loops=4) + Index Cond: (col1 < tbl1.col1) + Heap Fetches: 11 + -> Index Only Scan using tprt3_idx on tprt_3 (actual rows=1 loops=2) + Index Cond: (col1 < tbl1.col1) + Heap Fetches: 2 + -> Index Only Scan using tprt4_idx on tprt_4 (never executed) + Index Cond: (col1 < tbl1.col1) + Heap Fetches: 0 + -> Index Only Scan using tprt5_idx on tprt_5 (never executed) + Index Cond: (col1 < tbl1.col1) + Heap Fetches: 0 + -> Index Only Scan using tprt6_idx on tprt_6 (never executed) + Index Cond: (col1 < tbl1.col1) + Heap Fetches: 0 +(21 rows) + +explain (analyze, costs off, summary off, timing off) +select * from tbl1 inner join tprt on tbl1.col1 = tprt.col1; + QUERY PLAN +------------------------------------------------------------------------------- + Nested Loop (actual rows=3 loops=1) + -> Seq Scan on tbl1 (actual rows=5 loops=1) + -> Append (actual rows=1 loops=5) + -> Index Only Scan using tprt1_idx on tprt_1 (never executed) + Index Cond: (col1 = tbl1.col1) + Heap Fetches: 0 + -> Index Only Scan using tprt2_idx on tprt_2 (actual rows=1 loops=2) + Index Cond: (col1 = tbl1.col1) + Heap Fetches: 2 + -> Index Only Scan using tprt3_idx on tprt_3 (actual rows=0 loops=3) + Index Cond: (col1 = tbl1.col1) + Heap Fetches: 1 + -> Index Only Scan using tprt4_idx on tprt_4 (never executed) + Index Cond: (col1 = tbl1.col1) + Heap Fetches: 0 + -> Index Only Scan using tprt5_idx on tprt_5 (never executed) + Index Cond: (col1 = tbl1.col1) + Heap Fetches: 0 + -> Index Only Scan using tprt6_idx on tprt_6 (never executed) + Index Cond: (col1 = tbl1.col1) + Heap Fetches: 0 +(21 rows) + +select tbl1.col1, tprt.col1 from tbl1 +inner join tprt on tbl1.col1 > tprt.col1 +order by tbl1.col1, tprt.col1; + col1 | col1 +------+------ + 501 | 10 + 501 | 20 + 505 | 10 + 505 | 20 + 505 | 501 + 505 | 502 + 1001 | 10 + 1001 | 20 + 1001 | 501 + 1001 | 502 + 1001 | 505 + 1010 | 10 + 1010 | 20 + 1010 | 501 + 1010 | 502 + 1010 | 505 + 1010 | 1001 + 1011 | 10 + 1011 | 20 + 1011 | 501 + 1011 | 502 + 1011 | 505 + 1011 | 1001 +(23 rows) + +select tbl1.col1, tprt.col1 from tbl1 +inner join tprt on tbl1.col1 = tprt.col1 +order by tbl1.col1, tprt.col1; + col1 | col1 +------+------ + 501 | 501 + 505 | 505 + 1001 | 1001 +(3 rows) + +-- last partition +delete from tbl1; +insert into tbl1 values (4400); +explain (analyze, costs off, summary off, timing off) +select * from tbl1 join tprt on tbl1.col1 < tprt.col1; + QUERY PLAN +------------------------------------------------------------------------------- + Nested Loop (actual rows=1 loops=1) + -> Seq Scan on tbl1 (actual rows=1 loops=1) + -> Append (actual rows=1 loops=1) + -> Index Only Scan using tprt1_idx on tprt_1 (never executed) + Index Cond: (col1 > tbl1.col1) + Heap Fetches: 0 + -> Index Only Scan using tprt2_idx on tprt_2 (never executed) + Index Cond: (col1 > tbl1.col1) + Heap Fetches: 0 + -> Index Only Scan using tprt3_idx on tprt_3 (never executed) + Index Cond: (col1 > tbl1.col1) + Heap Fetches: 0 + -> Index Only Scan using tprt4_idx on tprt_4 (never executed) + Index Cond: (col1 > tbl1.col1) + Heap Fetches: 0 + -> Index Only Scan using tprt5_idx on tprt_5 (never executed) + Index Cond: (col1 > tbl1.col1) + Heap Fetches: 0 + -> Index Only Scan using tprt6_idx on tprt_6 (actual rows=1 loops=1) + Index Cond: (col1 > tbl1.col1) + Heap Fetches: 1 +(21 rows) + +select tbl1.col1, tprt.col1 from tbl1 +inner join tprt on tbl1.col1 < tprt.col1 +order by tbl1.col1, tprt.col1; + col1 | col1 +------+------ + 4400 | 4500 +(1 row) + +-- no matching partition +delete from tbl1; +insert into tbl1 values (10000); +explain (analyze, costs off, summary off, timing off) +select * from tbl1 join tprt on tbl1.col1 = tprt.col1; + QUERY PLAN +------------------------------------------------------------------------ + Nested Loop (actual rows=0 loops=1) + -> Seq Scan on tbl1 (actual rows=1 loops=1) + -> Append (actual rows=0 loops=1) + -> Index Only Scan using tprt1_idx on tprt_1 (never executed) + Index Cond: (col1 = tbl1.col1) + Heap Fetches: 0 + -> Index Only Scan using tprt2_idx on tprt_2 (never executed) + Index Cond: (col1 = tbl1.col1) + Heap Fetches: 0 + -> Index Only Scan using tprt3_idx on tprt_3 (never executed) + Index Cond: (col1 = tbl1.col1) + Heap Fetches: 0 + -> Index Only Scan using tprt4_idx on tprt_4 (never executed) + Index Cond: (col1 = tbl1.col1) + Heap Fetches: 0 + -> Index Only Scan using tprt5_idx on tprt_5 (never executed) + Index Cond: (col1 = tbl1.col1) + Heap Fetches: 0 + -> Index Only Scan using tprt6_idx on tprt_6 (never executed) + Index Cond: (col1 = tbl1.col1) + Heap Fetches: 0 +(21 rows) + +select tbl1.col1, tprt.col1 from tbl1 +inner join tprt on tbl1.col1 = tprt.col1 +order by tbl1.col1, tprt.col1; + col1 | col1 +------+------ +(0 rows) + +drop table tbl1, tprt; +-- test with columns defined in varying orders between each level +create table part_abc (a int not null, b int not null, c int not null) partition by list (a); +create table part_bac (b int not null, a int not null, c int not null) partition by list (b); +create table part_cab (c int not null, a int not null, b int not null) partition by list (c); +create table part_abc_p1 (a int not null, b int not null, c int not null); +alter table part_abc attach partition part_bac for values in(1); +alter table part_bac attach partition part_cab for values in(2); +alter table part_cab attach partition part_abc_p1 for values in(3); +prepare part_abc_q1 (int, int, int) as +select * from part_abc where a = $1 and b = $2 and c = $3; +-- Execute query 5 times to allow choose_custom_plan +-- to start considering a generic plan. +execute part_abc_q1 (1, 2, 3); + a | b | c +---+---+--- +(0 rows) + +execute part_abc_q1 (1, 2, 3); + a | b | c +---+---+--- +(0 rows) + +execute part_abc_q1 (1, 2, 3); + a | b | c +---+---+--- +(0 rows) + +execute part_abc_q1 (1, 2, 3); + a | b | c +---+---+--- +(0 rows) + +execute part_abc_q1 (1, 2, 3); + a | b | c +---+---+--- +(0 rows) + +-- single partition should be scanned. +explain (analyze, costs off, summary off, timing off) execute part_abc_q1 (1, 2, 3); + QUERY PLAN +------------------------------------------------------- + Append (actual rows=0 loops=1) + -> Seq Scan on part_abc_p1 (actual rows=0 loops=1) + Filter: ((a = $1) AND (b = $2) AND (c = $3)) +(3 rows) + +deallocate part_abc_q1; +drop table part_abc; diff --git a/src/test/regress/sql/partition_prune.sql b/src/test/regress/sql/partition_prune.sql index b7c5abf..f66c193 100644 --- a/src/test/regress/sql/partition_prune.sql +++ b/src/test/regress/sql/partition_prune.sql @@ -228,3 +228,255 @@ explain (costs off) select * from lp where (a <> 'a' and a <> 'd') or a is null; explain (costs off) select * from rlp where a = 15 and b <> 'ab' and b <> 'cd' and b <> 'xy' and b is not null; drop table lp, coll_pruning, rlp, mc3p, mc2p, boolpart, hp, rp; + +-- +-- Test runtime partition pruning +-- +create table ab (a int not null, b int not null) partition by list (a); +create table ab_a2 partition of ab for values in(2) partition by list (b); +create table ab_a2_b1 partition of ab_a2 for values in (1); +create table ab_a2_b2 partition of ab_a2 for values in (2); +create table ab_a2_b3 partition of ab_a2 for values in (3); +create table ab_a1 partition of ab for values in(1) partition by list (b); +create table ab_a1_b1 partition of ab_a1 for values in (1); +create table ab_a1_b2 partition of ab_a1 for values in (2); +create table ab_a1_b3 partition of ab_a1 for values in (3); +create table ab_a3 partition of ab for values in(3) partition by list (b); +create table ab_a3_b1 partition of ab_a3 for values in (1); +create table ab_a3_b2 partition of ab_a3 for values in (2); +create table ab_a3_b3 partition of ab_a3 for values in (3); + +prepare ab_q1 (int, int, int) as +select * from ab where a between $1 and $2 and b <= $3; + +-- Execute query 5 times to allow choose_custom_plan +-- to start considering a generic plan. +execute ab_q1 (1, 8, 3); +execute ab_q1 (1, 8, 3); +execute ab_q1 (1, 8, 3); +execute ab_q1 (1, 8, 3); +execute ab_q1 (1, 8, 3); + +explain (analyze, costs off, summary off, timing off) execute ab_q1 (2, 2, 3); +explain (analyze, costs off, summary off, timing off) execute ab_q1 (1, 2, 3); + +deallocate ab_q1; + +-- runtime pruning after optimizer pruning +prepare ab_q1 (int, int) as +select a from ab where a between $1 and $2 and b < 3; + +-- Execute query 5 times to allow choose_custom_plan +-- to start considering a generic plan. +execute ab_q1 (1, 8); +execute ab_q1 (1, 8); +execute ab_q1 (1, 8); +execute ab_q1 (1, 8); +execute ab_q1 (1, 8); + +explain (analyze, costs off, summary off, timing off) execute ab_q1 (2, 2); +explain (analyze, costs off, summary off, timing off) execute ab_q1 (2, 4); + +-- parallel append +prepare ab_q2 (int, int) as +select avg(a) from ab where a between $1 and $2 and b < 4; + +-- encourage use of parallel plans +set parallel_setup_cost = 0; +set parallel_tuple_cost = 0; +set min_parallel_table_scan_size = 0; +set max_parallel_workers_per_gather = 2; + +-- Execute query 5 times to allow choose_custom_plan +-- to start considering a generic plan. +execute ab_q2 (1, 8); +execute ab_q2 (1, 8); +execute ab_q2 (1, 8); +execute ab_q2 (1, 8); +execute ab_q2 (1, 8); + +explain (analyze, costs off, summary off, timing off) execute ab_q2 (2, 2); + +-- Test run-time pruning with IN lists. +prepare ab_q3 (int, int, int) as +select avg(a) from ab where a in($1,$2,$3) and b < 4; + +-- Execute query 5 times to allow choose_custom_plan +-- to start considering a generic plan. +execute ab_q3 (1, 2, 3); +execute ab_q3 (1, 2, 3); +execute ab_q3 (1, 2, 3); +execute ab_q3 (1, 2, 3); +execute ab_q3 (1, 2, 3); + +explain (analyze, costs off, summary off, timing off) execute ab_q3 (1, 1, 1); +explain (analyze, costs off, summary off, timing off) execute ab_q3 (2, 3, 3); + +-- try some params whose values do not belong to any partition +explain (analyze, costs off, summary off, timing off) execute ab_q3 (33, 44, 55); + +-- test parallel Append with IN list and parameterized nested loops +create table lprt_a (a int not null); +-- insert some values we won't find in ab +insert into lprt_a select 0 from generate_series(1,100); + +-- and insert some values that we should find. +insert into lprt_a values(1),(1); + +analyze lprt_a; + +create index ab_a2_b1_a_idx on ab_a2_b1 (a); +create index ab_a2_b2_a_idx on ab_a2_b2 (a); +create index ab_a2_b3_a_idx on ab_a2_b3 (a); +create index ab_a1_b1_a_idx on ab_a1_b1 (a); +create index ab_a1_b2_a_idx on ab_a1_b2 (a); +create index ab_a1_b3_a_idx on ab_a1_b3 (a); +create index ab_a3_b1_a_idx on ab_a3_b1 (a); +create index ab_a3_b2_a_idx on ab_a3_b2 (a); +create index ab_a3_b3_a_idx on ab_a3_b3 (a); + +set enable_hashjoin = 0; +set enable_mergejoin = 0; + +prepare ab_q4 (int, int, int) as +select avg(ab.a) from ab inner join lprt_a a on ab.a = a.a where a.a in($1,$2,$3); +execute ab_q4 (1, 2, 3); +execute ab_q4 (1, 2, 3); +execute ab_q4 (1, 2, 3); +execute ab_q4 (1, 2, 3); +execute ab_q4 (1, 2, 3); + +explain (analyze, costs off, summary off, timing off) execute ab_q4 (0, 0, 1); + +insert into lprt_a values(3),(3); + +explain (analyze, costs off, summary off, timing off) execute ab_q4 (1, 0, 3); +explain (analyze, costs off, summary off, timing off) execute ab_q4 (1, 0, 0); + +delete from lprt_a where a = 1; + +explain (analyze, costs off, summary off, timing off) execute ab_q4 (1, 0, 0); + +reset enable_hashjoin; +reset enable_mergejoin; +reset parallel_setup_cost; +reset parallel_tuple_cost; +reset min_parallel_table_scan_size; +reset max_parallel_workers_per_gather; + +-- Test run-time partition pruning with an initplan +explain (analyze, costs off, summary off, timing off) +select * from ab where a = (select max(a) from lprt_a) and b = (select max(a)-1 from lprt_a); + +deallocate ab_q1; +deallocate ab_q2; +deallocate ab_q3; +deallocate ab_q4; + +drop table ab, lprt_a; + +-- join +create table tbl1(col1 int); +insert into tbl1 values (501), (505); + +-- basic table +create table tprt (col1 int) partition by range (col1); +create table tprt_1 partition of tprt for values from (1) to (501); +create table tprt_2 partition of tprt for values from (501) to (1001); +create table tprt_3 partition of tprt for values from (1001) to (2001); +create table tprt_4 partition of tprt for values from (2001) to (3001); +create table tprt_5 partition of tprt for values from (3001) to (4001); +create table tprt_6 partition of tprt for values from (4001) to (5001); + +create index tprt1_idx on tprt_1 (col1); +create index tprt2_idx on tprt_2 (col1); +create index tprt3_idx on tprt_3 (col1); +create index tprt4_idx on tprt_4 (col1); +create index tprt5_idx on tprt_5 (col1); +create index tprt6_idx on tprt_6 (col1); + +insert into tprt values (10), (20), (501), (502), (505), (1001), (4500); + +set enable_hashjoin = off; +set enable_mergejoin = off; + +explain (analyze, costs off, summary off, timing off) +select * from tbl1 join tprt on tbl1.col1 > tprt.col1; + +explain (analyze, costs off, summary off, timing off) +select * from tbl1 join tprt on tbl1.col1 = tprt.col1; + +select tbl1.col1, tprt.col1 from tbl1 +inner join tprt on tbl1.col1 > tprt.col1 +order by tbl1.col1, tprt.col1; + +select tbl1.col1, tprt.col1 from tbl1 +inner join tprt on tbl1.col1 = tprt.col1 +order by tbl1.col1, tprt.col1; + +-- multiple partitions +insert into tbl1 values (1001), (1010), (1011); +explain (analyze, costs off, summary off, timing off) +select * from tbl1 inner join tprt on tbl1.col1 > tprt.col1; + +explain (analyze, costs off, summary off, timing off) +select * from tbl1 inner join tprt on tbl1.col1 = tprt.col1; + +select tbl1.col1, tprt.col1 from tbl1 +inner join tprt on tbl1.col1 > tprt.col1 +order by tbl1.col1, tprt.col1; + +select tbl1.col1, tprt.col1 from tbl1 +inner join tprt on tbl1.col1 = tprt.col1 +order by tbl1.col1, tprt.col1; + +-- last partition +delete from tbl1; +insert into tbl1 values (4400); +explain (analyze, costs off, summary off, timing off) +select * from tbl1 join tprt on tbl1.col1 < tprt.col1; + +select tbl1.col1, tprt.col1 from tbl1 +inner join tprt on tbl1.col1 < tprt.col1 +order by tbl1.col1, tprt.col1; + +-- no matching partition +delete from tbl1; +insert into tbl1 values (10000); +explain (analyze, costs off, summary off, timing off) +select * from tbl1 join tprt on tbl1.col1 = tprt.col1; + +select tbl1.col1, tprt.col1 from tbl1 +inner join tprt on tbl1.col1 = tprt.col1 +order by tbl1.col1, tprt.col1; + +drop table tbl1, tprt; + +-- test with columns defined in varying orders between each level + +create table part_abc (a int not null, b int not null, c int not null) partition by list (a); +create table part_bac (b int not null, a int not null, c int not null) partition by list (b); +create table part_cab (c int not null, a int not null, b int not null) partition by list (c); +create table part_abc_p1 (a int not null, b int not null, c int not null); + +alter table part_abc attach partition part_bac for values in(1); +alter table part_bac attach partition part_cab for values in(2); +alter table part_cab attach partition part_abc_p1 for values in(3); + +prepare part_abc_q1 (int, int, int) as +select * from part_abc where a = $1 and b = $2 and c = $3; + +-- Execute query 5 times to allow choose_custom_plan +-- to start considering a generic plan. +execute part_abc_q1 (1, 2, 3); +execute part_abc_q1 (1, 2, 3); +execute part_abc_q1 (1, 2, 3); +execute part_abc_q1 (1, 2, 3); +execute part_abc_q1 (1, 2, 3); + +-- single partition should be scanned. +explain (analyze, costs off, summary off, timing off) execute part_abc_q1 (1, 2, 3); + +deallocate part_abc_q1; + +drop table part_abc;