diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c index 9606ff5..5fdbd72 100644 --- a/src/backend/catalog/partition.c +++ b/src/backend/catalog/partition.c @@ -34,11 +34,13 @@ #include "catalog/pg_type.h" #include "commands/tablecmds.h" #include "executor/executor.h" +#include "executor/nodeSubplan.h" #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "nodes/parsenodes.h" #include "optimizer/clauses.h" +#include "optimizer/pathnode.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/predtest.h" @@ -290,25 +292,33 @@ static uint64 compute_hash_value(PartitionKey key, Datum *values, bool *isnull); PG_FUNCTION_INFO_V1(satisfies_hash_partition); static Bitmapset *get_partitions_from_clauses_recurse(Relation relation, - int rt_index, List *clauses); + int rt_index, ParamListInfo prmlist, + ExprContext *econtext, List *clauses); static Bitmapset *get_partitions_from_ne_clauses(Relation relation, - List *ne_clauses); + ParamListInfo prmlist, + ExprContext *econtext, + List *ne_clauses); static Bitmapset *get_partitions_from_or_clause_args(Relation relation, - int rt_index, List *or_clause_args); + int rt_index, ParamListInfo prmlist, + ExprContext *econtext, List *or_clause_args); static int classify_partition_bounding_keys(Relation relation, List *clauses, int rt_index, - PartScanKeyInfo *keys, bool *constfalse, + PartScanKeyInfo *keys, ParamListInfo prmlist, + ExprContext *econtext, bool *constfalse, List **or_clauses, List **ne_clauses); static void remove_redundant_clauses(PartitionKey partkey, int partattoff, List *all_clauses, - List **result, bool *constfalse); + List **result, ParamListInfo prmlist, + ExprContext *econtext,bool *constfalse); static bool partition_cmp_args(PartitionKey key, int partattoff, PartClause *op, PartClause *leftarg, PartClause *rightarg, + ParamListInfo prmlist, ExprContext *econtext, bool *result); static PartOpStrategy partition_op_strategy(PartitionKey key, PartClause *op, bool *incl); static bool partkey_datum_from_expr(PartitionKey key, int partattoff, - Expr *expr, Datum *value); + Expr *expr, ParamListInfo prmlist, + ExprContext *econtext, Datum *value); static Bitmapset *get_partitions_for_keys(Relation rel, PartScanKeyInfo *keys); static Bitmapset *get_partitions_for_keys_hash(Relation rel, @@ -1695,6 +1705,7 @@ get_partition_qual_relid(Oid relid) */ Bitmapset * get_partitions_from_clauses(Relation relation, int rt_index, + ParamListInfo prmlist, ExprContext *econtext, List *partclauses) { Bitmapset *result; @@ -1724,16 +1735,134 @@ get_partitions_from_clauses(Relation relation, int rt_index, if (partition_bound_has_default(boundinfo)) { partconstr = (List *) expression_planner((Expr *) partconstr); - partclauses = list_concat(partclauses, partconstr); + partclauses = list_concat(list_copy(partclauses), partconstr); } } - result = get_partitions_from_clauses_recurse(relation, rt_index, - partclauses); + result = get_partitions_from_clauses_recurse(relation, rt_index, prmlist, + econtext, partclauses); return result; } +/* + * make_partition_pruneinfo + * Build PartitionPruneInfo tree to allow the output of + * get_partitions_from_clauses to be translated into + * 'subpaths' indexes + */ +PartitionPruneInfo * +make_partition_pruneinfo(PlannerInfo *root, RelOptInfo *rel, + List *partition_rels, List *subpaths) +{ + PartitionPruneInfo *pinfo; + AppendRelInfo *appinfo; + RangeTblEntry *rte; + ListCell *lc; + int i; + int partidx; + int nparts = rel->nparts; + + rte = root->simple_rte_array[rel->relid]; + + pinfo = makeNode(PartitionPruneInfo); + pinfo->parentoid = rte->relid; + pinfo->subnodeindex = (int *) palloc(sizeof(int) * nparts); + pinfo->subpartindex = (PartitionPruneInfo **) + palloc0(sizeof(PartitionPruneInfo *) * nparts); + pinfo->nparts = nparts; + + /* + * -1 represents a partition that's already 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 subpath which belong to relations not directly parented by + * rel. We'll process any we skip below when looping through + * partition_rels + */ + if (appinfo->parent_relid != rel->relid) + continue; + + /* + * Determine the element in part_appinfo which belongs to this + * subpath. + */ + for (partidx = 0; partidx < nparts; partidx++) + { + + if (rel->part_appinfos[partidx]->child_relid != + appinfo->child_relid) + continue; + + /* found it! Save the subnode index */ + pinfo->subnodeindex[partidx] = i; + break; + } + } + + /* + * Some of the tables 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++) + { + if (rel->part_appinfos[partidx]->child_relid != appinfo->child_relid) + continue; + + pinfo->subpartindex[partidx] = make_partition_pruneinfo(root, + subpart, + partition_rels, + subpaths); + break; + } + } + + return pinfo; +} + /* Module-local functions */ /* @@ -1745,6 +1874,8 @@ get_partitions_from_clauses(Relation relation, int rt_index, */ static Bitmapset * get_partitions_from_clauses_recurse(Relation relation, int rt_index, + ParamListInfo prmlist, + ExprContext *econtext, List *clauses) { PartitionDesc partdesc = RelationGetPartitionDesc(relation); @@ -1761,8 +1892,9 @@ get_partitions_from_clauses_recurse(Relation relation, int rt_index, * can work with. */ nkeys = classify_partition_bounding_keys(relation, clauses, rt_index, - &keys, &constfalse, - &or_clauses, &ne_clauses); + &keys, prmlist, econtext, + &constfalse, &or_clauses, + &ne_clauses); /* * classify_partition_bounding_keys() may have found clauses marked @@ -1796,7 +1928,10 @@ get_partitions_from_clauses_recurse(Relation relation, int rt_index, { Bitmapset *ne_clause_parts; - ne_clause_parts = get_partitions_from_ne_clauses(relation, ne_clauses); + ne_clause_parts = get_partitions_from_ne_clauses(relation, + prmlist, + econtext, + ne_clauses); /* * Clauses in ne_clauses are in conjunction with the clauses that gave @@ -1816,6 +1951,8 @@ get_partitions_from_clauses_recurse(Relation relation, int rt_index, Bitmapset *or_parts; or_parts = get_partitions_from_or_clause_args(relation, rt_index, + prmlist, + econtext, or->args); /* * Clauses in or_clauses are mutually conjunctive and also in @@ -1886,7 +2023,8 @@ count_partition_datums(Relation rel, int index) * ne_clauses. Only ever called if relation is a list partitioned table. */ static Bitmapset * -get_partitions_from_ne_clauses(Relation relation, List *ne_clauses) +get_partitions_from_ne_clauses(Relation relation, ParamListInfo prmlist, + ExprContext *econtext, List *ne_clauses) { ListCell *lc; Bitmapset *result, @@ -1921,7 +2059,8 @@ get_partitions_from_ne_clauses(Relation relation, List *ne_clauses) PartClause *pc = lfirst(lc); Datum datum; - if (partkey_datum_from_expr(partkey, 0, pc->constarg, &datum) && + if (partkey_datum_from_expr(partkey, 0, pc->constarg, prmlist, + econtext, &datum) && !datum_in_array(partkey, datum, exclude_datums, n_exclude_datums)) exclude_datums[n_exclude_datums++] = datum; } @@ -1989,6 +2128,8 @@ get_partitions_from_ne_clauses(Relation relation, List *ne_clauses) */ static Bitmapset * get_partitions_from_or_clause_args(Relation relation, int rt_index, + ParamListInfo prmlist, + ExprContext *econtext, List *or_clause_args) { ListCell *lc; @@ -2021,6 +2162,8 @@ get_partitions_from_or_clause_args(Relation relation, int rt_index, } arg_partset = get_partitions_from_clauses_recurse(relation, rt_index, + prmlist, + econtext, arg_clauses); /* @@ -2076,7 +2219,10 @@ get_partitions_from_or_clause_args(Relation relation, int rt_index, static int classify_partition_bounding_keys(Relation relation, List *clauses, int rt_index, - PartScanKeyInfo *keys, bool *constfalse, + PartScanKeyInfo *keys, + ParamListInfo prmlist, + ExprContext *econtext, + bool *constfalse, List **or_clauses, List **ne_clauses) { @@ -2504,7 +2650,7 @@ classify_partition_bounding_keys(Relation relation, List *clauses, { remove_redundant_clauses(partkey, i, keyclauses_all[i], &keyclauses[i], - constfalse); + prmlist, econtext, constfalse); if (*constfalse) return 0; } @@ -2567,11 +2713,13 @@ classify_partition_bounding_keys(Relation relation, List *clauses, Assert(incl); if (need_next_eq && partkey_datum_from_expr(partkey, i, constarg, + prmlist, econtext, &keys->eqkeys[i])) keys->n_eqkeys++; if (need_next_max && partkey_datum_from_expr(partkey, i, constarg, + prmlist, econtext, &keys->maxkeys[i])) { keys->n_maxkeys++; @@ -2580,6 +2728,7 @@ classify_partition_bounding_keys(Relation relation, List *clauses, if (need_next_min && partkey_datum_from_expr(partkey, i, constarg, + prmlist, econtext, &keys->minkeys[i])) { keys->n_minkeys++; @@ -2590,6 +2739,7 @@ classify_partition_bounding_keys(Relation relation, List *clauses, case PART_OP_LESS: if (need_next_max && partkey_datum_from_expr(partkey, i, constarg, + prmlist, econtext, &keys->maxkeys[i])) { keys->n_maxkeys++; @@ -2602,6 +2752,7 @@ classify_partition_bounding_keys(Relation relation, List *clauses, case PART_OP_GREATER: if (need_next_min && partkey_datum_from_expr(partkey, i, constarg, + prmlist, econtext, &keys->minkeys[i])) { keys->n_minkeys++; @@ -2695,7 +2846,8 @@ partition_op_strategy(PartitionKey key, PartClause *op, bool *incl) */ static bool partkey_datum_from_expr(PartitionKey key, int partattoff, - Expr *expr, Datum *value) + Expr *expr, ParamListInfo prmlist, ExprContext *econtext, + Datum *value) { Oid exprtype = exprType((Node *) expr); @@ -2737,6 +2889,43 @@ partkey_datum_from_expr(PartitionKey key, int partattoff, case T_Const: *value = ((Const *) expr)->constvalue; return true; + case T_Param: + + switch (((Param *) expr)->paramkind) + { + case PARAM_EXTERN: + if (prmlist) + { + Node *node; + Param *param = (Param *) expr; + node = eval_const_expressions_from_list(prmlist, + (Node *) param); + if (IsA(node, Const)) + { + *value = ((Const *) node)->constvalue; + return true; + } + } + + case PARAM_EXEC: + if (econtext) + { + Param *param = (Param *) expr; + ParamExecData *prm; + + prm = &(econtext->ecxt_param_exec_vals[param->paramid]); + if (unlikely(prm->execPlan != NULL)) + { + ExecSetParamPlan(prm->execPlan, econtext); + Assert(prm->execPlan == NULL); + } + *value = prm->value; + return true; + } + + default: + return false; + } default: return false; @@ -2755,6 +2944,7 @@ partkey_datum_from_expr(PartitionKey key, int partattoff, static void remove_redundant_clauses(PartitionKey partkey, int partattoff, List *all_clauses, List **result, + ParamListInfo prmlist, ExprContext *econtext, bool *constfalse) { PartClause *hash_clause, @@ -2798,7 +2988,7 @@ remove_redundant_clauses(PartitionKey partkey, int partattoff, /* check if another clause would contradict the one we have */ else if (partition_cmp_args(partkey, partattoff, cur, cur, hash_clause, - &test_result)) + prmlist, econtext, &test_result)) { if (!test_result) { @@ -2853,7 +3043,7 @@ remove_redundant_clauses(PartitionKey partkey, int partattoff, */ if (partition_cmp_args(partkey, partattoff, cur, cur, btree_clauses[s], - &test_result)) + prmlist, econtext, &test_result)) { /* cur is more restrictive, replace old key. */ if (test_result) @@ -2909,7 +3099,7 @@ remove_redundant_clauses(PartitionKey partkey, int partattoff, */ if (partition_cmp_args(partkey, partattoff, chk, eq, chk, - &test_result)) + prmlist, econtext, &test_result)) { if (!test_result) { @@ -2939,7 +3129,7 @@ remove_redundant_clauses(PartitionKey partkey, int partattoff, if (partition_cmp_args(partkey, partattoff, le, lt, le, - &test_result)) + prmlist, econtext, &test_result)) { if (test_result) btree_clauses[BTLessEqualStrategyNumber - 1] = NULL; @@ -2957,7 +3147,7 @@ remove_redundant_clauses(PartitionKey partkey, int partattoff, if (partition_cmp_args(partkey, partattoff, ge, gt, ge, - &test_result)) + prmlist, econtext, &test_result)) { if (test_result) btree_clauses[BTGreaterEqualStrategyNumber - 1] = NULL; @@ -2991,6 +3181,7 @@ remove_redundant_clauses(PartitionKey partkey, int partattoff, static bool partition_cmp_args(PartitionKey key, int partattoff, PartClause *op, PartClause *leftarg, PartClause *rightarg, + ParamListInfo prmlist, ExprContext *econtext, bool *result) { Oid partopfamily = key->partopfamily[partattoff]; @@ -3000,10 +3191,12 @@ partition_cmp_args(PartitionKey key, int partattoff, Assert(op->valid_cache && leftarg->valid_cache && rightarg->valid_cache); /* Get the constant values from the operands */ if (!partkey_datum_from_expr(key, partattoff, - leftarg->constarg, &leftarg_const)) + leftarg->constarg, prmlist, econtext, + &leftarg_const)) return false; if (!partkey_datum_from_expr(key, partattoff, - rightarg->constarg, &rightarg_const)) + rightarg->constarg, prmlist, econtext, + &rightarg_const)) return false; /* diff --git a/src/backend/executor/nodeAppend.c b/src/backend/executor/nodeAppend.c index 0e93713..8025e57 100644 --- a/src/backend/executor/nodeAppend.c +++ b/src/backend/executor/nodeAppend.c @@ -57,9 +57,11 @@ #include "postgres.h" +#include "parser/parsetree.h" #include "executor/execdebug.h" #include "executor/nodeAppend.h" #include "miscadmin.h" +#include "utils/memutils.h" /* Shared state for parallel-aware Append. */ struct ParallelAppendState @@ -82,6 +84,11 @@ 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, + Bitmapset **validsubplans); + /* ---------------------------------------------------------------- * ExecInitAppend @@ -127,21 +134,41 @@ ExecInitAppend(Append *node, EState *estate, int eflags) appendstate->ps.ExecProcNode = ExecAppend; appendstate->appendplans = appendplanstates; appendstate->as_nplans = nplans; + appendstate->prune_qual = node->plan.qual; + appendstate->part_prune_params = node->part_prune_params; + appendstate->part_prune_info = node->part_prune_info; /* * 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); /* - * append nodes still have Result slots, which hold pointers to tuples, so - * we have to initialize them. + * tuple table initialization */ ExecInitResultTupleSlot(estate, &appendstate->ps); /* + * The presence of a part_prune_info means that run-time pruning is + * enabled, so here we'll determine which subplans need to be scanned. + */ + if (node->part_prune_info) + { + appendstate->prune_context = + AllocSetContextCreate(CurrentMemoryContext, + "Partition Prune", + ALLOCSET_DEFAULT_SIZES); + + + set_valid_runtime_subplans(appendstate); + } + /* else, mark all subplans as requiring a scan */ + else + appendstate->as_valid_subplans = bms_add_range(NULL, 0, nplans - 1); + + /* * call ExecInitNode on each of the plans to be executed and save the * results into the array "appendplans". */ @@ -161,12 +188,22 @@ ExecInitAppend(Append *node, EState *estate, int eflags) 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. + * Parallel-aware append plans must choose the first valid subplan to + * execute by looking at shared memory, but non-parallel-aware append + * plans can always start with the first valid subplan. */ - appendstate->as_whichplan = - appendstate->ps.plan->parallel_aware ? INVALID_SUBPLAN_INDEX : 0; + if (appendstate->ps.plan->parallel_aware) + appendstate->as_whichplan = INVALID_SUBPLAN_INDEX; + else + { + /* Set the plan to the first valid subplan */ + appendstate->as_whichplan = bms_next_member( + appendstate->as_valid_subplans, -1); + + /* There mightn't be a single valid subplan. */ + if (appendstate->as_whichplan < 0) + appendstate->as_whichplan = INVALID_SUBPLAN_INDEX; + } /* If parallel-aware, this will be overridden later. */ appendstate->choose_next_subplan = choose_next_subplan_locally; @@ -257,6 +294,16 @@ ExecReScanAppend(AppendState *node) { int i; + /* + * The presence of a part_prune_info means that run-time pruning is + * enabled. If any of the parameters being used for partition pruning + * have changed, then we'd better redetermine which subplans we need to + * scan. + */ + if (node->part_prune_info && + bms_overlap(node->ps.chgParam, node->part_prune_params)) + set_valid_runtime_subplans(node); + for (i = 0; i < node->as_nplans; i++) { PlanState *subnode = node->appendplans[i]; @@ -276,8 +323,17 @@ ExecReScanAppend(AppendState *node) ExecReScan(subnode); } - node->as_whichplan = - node->ps.plan->parallel_aware ? INVALID_SUBPLAN_INDEX : 0; + if (node->ps.plan->parallel_aware) + node->as_whichplan = INVALID_SUBPLAN_INDEX; + else + { + /* Set the plan to the first valid subplan */ + node->as_whichplan = bms_next_member(node->as_valid_subplans, -1); + + /* There mightn't be a single valid subplan. */ + if (node->as_whichplan < 0) + node->as_whichplan = INVALID_SUBPLAN_INDEX; + } } /* ---------------------------------------------------------------- @@ -366,22 +422,23 @@ static bool choose_next_subplan_locally(AppendState *node) { int whichplan = node->as_whichplan; + int nextplan; + + /* Handle case when all subplans were pruned */ + if (whichplan == INVALID_SUBPLAN_INDEX) + return false; - /* We should never see INVALID_SUBPLAN_INDEX in this case. */ + /* Ensure whichplan is valid */ Assert(whichplan >= 0 && whichplan <= node->as_nplans); if (ScanDirectionIsForward(node->ps.state->es_direction)) - { - if (whichplan >= node->as_nplans - 1) - return false; - node->as_whichplan++; - } + 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; } @@ -399,6 +456,7 @@ choose_next_subplan_for_leader(AppendState *node) { ParallelAppendState *pstate = node->as_pstate; Append *append = (Append *) node->ps.plan; + Bitmapset *validplans = node->as_valid_subplans; /* Backward scan is not supported by parallel-aware plans */ Assert(ScanDirectionIsForward(node->ps.state->es_direction)); @@ -412,21 +470,31 @@ choose_next_subplan_for_leader(AppendState *node) } else { - /* Start with last subplan. */ - node->as_whichplan = node->as_nplans - 1; + /* Start with last valid subplan. */ + node->as_whichplan = bms_prev_member(validplans, -1); + + /* Bail if there are no valid plans */ + if (node->as_whichplan < 0) + { + pstate->pa_next_plan = INVALID_SUBPLAN_INDEX; + node->as_whichplan = INVALID_SUBPLAN_INDEX; + LWLockRelease(&pstate->pa_lock); + return false; + } } /* Loop until we find a subplan to execute. */ while (pstate->pa_finished[node->as_whichplan]) { - if (node->as_whichplan == 0) + node->as_whichplan = bms_prev_member(validplans, node->as_whichplan); + + if (node->as_whichplan < 0) { pstate->pa_next_plan = INVALID_SUBPLAN_INDEX; node->as_whichplan = INVALID_SUBPLAN_INDEX; LWLockRelease(&pstate->pa_lock); return false; } - node->as_whichplan--; } /* If non-partial, immediately mark as finished. */ @@ -457,6 +525,7 @@ choose_next_subplan_for_worker(AppendState *node) { ParallelAppendState *pstate = node->as_pstate; Append *append = (Append *) node->ps.plan; + Bitmapset *validplans = node->as_valid_subplans; /* Backward scan is not supported by parallel-aware plans */ Assert(ScanDirectionIsForward(node->ps.state->es_direction)); @@ -477,15 +546,26 @@ choose_next_subplan_for_worker(AppendState *node) /* Loop until we find a subplan to execute. */ while (pstate->pa_finished[pstate->pa_next_plan]) { - if (pstate->pa_next_plan < node->as_nplans - 1) + int nextplan; + + nextplan = bms_next_member(validplans, pstate->pa_next_plan); + if (nextplan >= 0) { - /* Advance to next plan. */ - pstate->pa_next_plan++; + /* Advance to next valid plan. */ + pstate->pa_next_plan = nextplan; } else if (append->first_partial_plan < node->as_nplans) { - /* Loop back to first partial plan. */ - pstate->pa_next_plan = append->first_partial_plan; + /* Loop back to first valid partial plan. */ + pstate->pa_next_plan = bms_next_member(validplans, + append->first_partial_plan - 1); + + /* + * Ensure there is a valid first partial plan, if not then + * arrange to bail out. + */ + if (pstate->pa_next_plan < 0) + pstate->pa_next_plan = node->as_whichplan; } else { @@ -503,11 +583,27 @@ choose_next_subplan_for_worker(AppendState *node) } /* Pick the plan we found, and advance pa_next_plan one more time. */ - node->as_whichplan = pstate->pa_next_plan++; - if (pstate->pa_next_plan >= node->as_nplans) + node->as_whichplan = pstate->pa_next_plan; + pstate->pa_next_plan = bms_next_member(validplans, pstate->pa_next_plan); + if (pstate->pa_next_plan < 0) { if (append->first_partial_plan < node->as_nplans) - pstate->pa_next_plan = append->first_partial_plan; + { + /* + * If we have any partial plans then let the next caller can work + * on the first valid partial plan. + */ + pstate->pa_next_plan = bms_next_member(validplans, + append->first_partial_plan - 1); + + /* + * If there are no valid partial plans then the next caller has + * nothing to do. + */ + if (pstate->pa_next_plan < 0) + pstate->pa_next_plan = INVALID_SUBPLAN_INDEX; + + } else { /* @@ -526,3 +622,84 @@ choose_next_subplan_for_worker(AppendState *node) return true; } + +/* + * get_valid_runtime_subplans + * Determine which subset of subplan nodes we need to scan based on + * AppendState's 'prune_qual'. 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; + + /* Free any previously set valid subplans */ + bms_free(node->as_valid_subplans); + + /* + * Since we're calling s planner function to determine which partitions + * are valid with the current parameters, we must switch to a temp memory + * context as the planner code may not be too careful about memory + * allocations. We may call this function many times over from + * ExecReScanAppend, so we certainly don't want any leaks from the + * Executor's context. + */ + oldContext = MemoryContextSwitchTo(node->prune_context); + + set_valid_runtime_subplans_recurse(node, node->part_prune_info, + &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, + Bitmapset **validsubplans) +{ + Bitmapset *partset; + Relation rel; + int i; + List *clauses = node->prune_qual; + + rel = relation_open(pinfo->parentoid, NoLock); + + /* Determine which partition indexes we need to scan */ + partset = get_partitions_from_clauses(rel, 1, + node->ps.state->es_param_list_info, + node->ps.ps_ExprContext, clauses); + + /* 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], + 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 subnodes"); + } + } + + bms_free(partset); + + relation_close(rel, NoLock); +} diff --git a/src/backend/nodes/bitmapset.c b/src/backend/nodes/bitmapset.c index ae30072..d382cde 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, @@ -1045,6 +1067,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 84d7171..f533cbe 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -243,6 +243,8 @@ _copyAppend(const Append *from) COPY_NODE_FIELD(partitioned_rels); COPY_NODE_FIELD(appendplans); COPY_SCALAR_FIELD(first_partial_plan); + COPY_BITMAPSET_FIELD(part_prune_params); + COPY_NODE_FIELD(part_prune_info); return newnode; } @@ -2126,6 +2128,31 @@ _copyOnConflictExpr(const OnConflictExpr *from) return newnode; } +static PartitionPruneInfo * +_copyPartitionPruneInfo(const PartitionPruneInfo *from) +{ + PartitionPruneInfo *newnode = makeNode(PartitionPruneInfo); + int i; + + COPY_SCALAR_FIELD(parentoid); + 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) + newnode->subpartindex[i] = + _copyPartitionPruneInfo(newnode->subpartindex[i]); + } + + return newnode; +} + /* **************************************************************** * relation.h copy functions * @@ -5007,6 +5034,9 @@ copyObjectImpl(const void *from) case T_OnConflictExpr: retval = _copyOnConflictExpr(from); break; + case T_PartitionPruneInfo: + retval = _copyPartitionPruneInfo(from); + break; /* * RELATION NODES diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index eeaf8fd..ee53c72 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -141,12 +141,6 @@ static void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, static List *get_append_rel_partitions(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); -static List *match_clauses_to_partkey(PlannerInfo *root, - RelOptInfo *rel, - List *clauses, - bool *contains_const, - bool *constfalse); - /* * make_one_rel @@ -877,6 +871,7 @@ get_append_rel_partitions(PlannerInfo *root, Relation parent; PartitionDesc partdesc; Bitmapset *partindexes; + Bitmapset *paramids = NULL; /* * Get the clauses that match the partition key, including information @@ -885,6 +880,7 @@ get_append_rel_partitions(PlannerInfo *root, */ partclauses = match_clauses_to_partkey(root, rel, list_copy(rel->baserestrictinfo), + ¶mids, &contains_const, &constfalse); @@ -892,6 +888,13 @@ get_append_rel_partitions(PlannerInfo *root, if (constfalse) return NIL; + /* + * Record any params found that we could use to further eliminate + * partitions during execution. + */ + rel->runtime_prune_params = bms_add_members(rel->runtime_prune_params, + paramids); + parent = heap_open(rte->relid, NoLock); partdesc = RelationGetPartitionDesc(parent); @@ -900,8 +903,8 @@ get_append_rel_partitions(PlannerInfo *root, * then use these to prune partitions. */ if (partclauses != NIL && contains_const) - partindexes = get_partitions_from_clauses(parent, rel->relid, - partclauses); + partindexes = get_partitions_from_clauses(parent, rel->relid, NULL, + NULL, partclauses); else { /* @@ -963,10 +966,11 @@ get_append_rel_partitions(PlannerInfo *root, * If the list contains a pseudo-constant RestrictInfo with constant false * value, *constfalse is set. */ -static List * +List * match_clauses_to_partkey(PlannerInfo *root, RelOptInfo *rel, List *clauses, + Bitmapset **paramids, bool *contains_const, bool *constfalse) { @@ -1030,6 +1034,7 @@ match_clauses_to_partkey(PlannerInfo *root, constfalse1; if (match_clauses_to_partkey(root, rel, list_make1(arg), + paramids, &contains_const1, &constfalse1) != NIL) { @@ -1154,8 +1159,11 @@ match_clauses_to_partkey(PlannerInfo *root, */ result = lappend(result, clause); - if (!*contains_const) - *contains_const = IsA(constexpr, Const); + if (IsA(constexpr, Const)) + *contains_const = true; + else if (IsA(constexpr, Param)) + *paramids = bms_add_member(*paramids, + ((Param *) constexpr)->paramid); } else if (IsA(clause, ScalarArrayOpExpr)) { @@ -1711,6 +1719,7 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, List *partitioned_rels = NIL; RangeTblEntry *rte; double partial_rows = -1; + bool trypartitionprune = false; /* * AppendPath generated for partitioned tables must record the RT indexes @@ -1735,7 +1744,19 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel, rte = planner_rt_fetch(rel->relid, root); if (rte->rtekind == RTE_RELATION && rte->relkind == RELKIND_PARTITIONED_TABLE) - partitioned_rels = rel->live_partitioned_rels; + { + partitioned_rels = rel->live_partitioned_rels; + + /* + * For base partitioned tables we'll try to see if we can perform + * any run-time partition pruning. We need to do a bit more work + * later in planning to ensure we can enable it, so this just + * allows a way to save doing that work in cases when we certainly + * can't enable it. + */ + if (rel->reloptkind == RELOPT_BASEREL) + trypartitionprune = true; + } } else if (rel->reloptkind == RELOPT_JOINREL && rel->part_scheme) { @@ -1907,9 +1928,10 @@ 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)); + partitioned_rels, -1, + trypartitionprune)); /* * Consider an append of unordered, unparameterized partial paths. Make @@ -1949,10 +1971,11 @@ 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); + partitioned_rels, -1, + trypartitionprune); /* * Make sure any subsequent partial paths use the same row count @@ -1998,10 +2021,11 @@ 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); + partitioned_rels, partial_rows, + trypartitionprune); add_partial_path(rel, (Path *) appendpath); } @@ -2054,9 +2078,10 @@ 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)); + partitioned_rels, -1, + trypartitionprune)); } } @@ -2319,8 +2344,8 @@ set_dummy_rel_pathlist(RelOptInfo *rel) rel->pathlist = NIL; rel->partial_pathlist = NIL; - add_path(rel, (Path *) create_append_path(rel, NIL, NIL, NULL, - 0, false, NIL, -1)); + add_path(rel, (Path *) create_append_path(NULL, rel, NIL, NIL, NULL, + 0, false, NIL, -1, false)); /* * We set the cheapest path immediately, to ensure that IS_DUMMY_REL() diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 5bd3031..15d1426 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -1232,8 +1232,8 @@ 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, - 0, false, NIL, -1)); + add_path(rel, (Path *) create_append_path(NULL, rel, NIL, NIL, NULL, + 0, false, NIL, -1, false)); /* Set or update cheapest_total_path and related fields */ set_cheapest(rel); diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 1a9fd82..0c2e7fe 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -204,7 +204,10 @@ 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, + Bitmapset *partpruneparams, + List *qual); static RecursiveUnion *make_recursive_union(List *tlist, Plan *lefttree, Plan *righttree, @@ -1016,6 +1019,9 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path) List *tlist = build_path_tlist(root, &best_path->path); List *subplans = NIL; ListCell *subpaths; + List *qual = NIL; + RelOptInfo *rel = best_path->path.parent; + PartitionPruneInfo *pinfo = NULL; /* * The subpaths list could be empty, if every child was proven empty by @@ -1053,6 +1059,49 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path) subplans = lappend(subplans, subplan); } + + if (best_path->trypartitionprune) + { + /* Not for join rels */ + Assert(bms_membership(rel->relids) == BMS_SINGLETON); + + qual = extract_actual_clauses(best_path->path.parent->baserestrictinfo, false); + + if (best_path->path.param_info) + { + List *prmquals = best_path->path.param_info->ppi_clauses; + bool contains_const; + bool constfalse; + + prmquals = extract_actual_clauses(prmquals, false); + prmquals = (List *) replace_nestloop_params(root, + (Node *) prmquals); + + qual = list_concat(qual, prmquals); + + /* + * So far, we only know about the pruning params for the base quals + * in rel, there may well be params matching partition keys in the + * parameterized path clause too, so we'll gather these now. We'll + * borrow match_clauses_to_partkey for this, although we only care + * about the parameter IDs and not any of the other outputs. + */ + (void) match_clauses_to_partkey(root, rel, prmquals, + &rel->runtime_prune_params, + &contains_const, &constfalse); + } + + /* + * If there are parameters matching the partition key then we'll now + * enable run-time partition pruning. There's no fancy big switch + * to enable it, we'll just make a PartitionPruneInfo and pass that + * along to the executor. It'll just make use of it when available. + */ + if (rel->runtime_prune_params) + pinfo = make_partition_pruneinfo(root, best_path->path.parent, + best_path->partitioned_rels, + best_path->subpaths); + } /* * 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 @@ -1061,7 +1110,10 @@ 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, + rel->runtime_prune_params, + qual); copy_generic_path_info(&plan->plan, (Path *) best_path); @@ -5308,19 +5360,23 @@ 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, + Bitmapset *partpruneparams, + List *qual) { Append *node = makeNode(Append); Plan *plan = &node->plan; plan->targetlist = tlist; - plan->qual = NIL; + plan->qual = qual; plan->lefttree = NULL; plan->righttree = NULL; node->partitioned_rels = partitioned_rels; node->appendplans = appendplans; node->first_partial_plan = first_partial_plan; - + node->part_prune_info = partpruneinfo; + node->part_prune_params = partpruneparams; return node; } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index ffdf9c5..6139a13 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -3678,14 +3678,16 @@ 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, 0, false, NIL, - -1); + -1, + false); path->pathtarget = target; } else diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index f87849e..d320883 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -590,8 +590,8 @@ generate_union_path(SetOperationStmt *op, PlannerInfo *root, /* * Append the child results together. */ - path = (Path *) create_append_path(result_rel, pathlist, NIL, - NULL, 0, false, NIL, -1); + path = (Path *) create_append_path(root, result_rel, pathlist, NIL, + NULL, 0, false, NIL, -1, false); /* We have to manually jam the right tlist into the path; ick */ path->pathtarget = create_pathtarget(root, tlist); @@ -702,8 +702,8 @@ generate_nonunion_path(SetOperationStmt *op, PlannerInfo *root, /* * Append the child results together. */ - path = (Path *) create_append_path(result_rel, pathlist, NIL, - NULL, 0, false, NIL, -1); + path = (Path *) create_append_path(root, result_rel, pathlist, NIL, + NULL, 0, false, NIL, -1, false); /* We have to manually jam the right tlist into the path; ick */ path->pathtarget = create_pathtarget(root, tlist); diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 93eb374..e4c9191 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -2470,6 +2470,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/pathnode.c b/src/backend/optimizer/util/pathnode.c index 2aee156..7f3dcbb 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -17,6 +17,7 @@ #include #include "miscadmin.h" +#include "catalog/partition.h" #include "nodes/nodeFuncs.h" #include "nodes/extensible.h" #include "optimizer/clauses.h" @@ -1210,11 +1211,13 @@ 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, - List *partitioned_rels, double rows) + List *partitioned_rels, double rows, + bool trypartitionprune) { AppendPath *pathnode = makeNode(AppendPath); ListCell *l; @@ -1224,8 +1227,20 @@ 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); + + /* + * When using run-time partition pruning we need the clause list details + * in the param_info. get_appendrel_parampathinfo does not do this, so + * when we're trying to enable run-time partition pruning we'll just call + * get_baserel_parampathinfo instead as it does what we need. + */ + if (trypartitionprune) + pathnode->path.param_info = get_baserel_parampathinfo(root, + 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; @@ -1248,6 +1263,8 @@ create_append_path(RelOptInfo *rel, append_startup_cost_compare); } pathnode->first_partial_path = list_length(subpaths); + pathnode->part_prune_params = rel->runtime_prune_params; + pathnode->trypartitionprune = trypartitionprune; pathnode->subpaths = list_concat(subpaths, partial_subpaths); foreach(l, subpaths) diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index dcfda1c..e673ea8 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -156,6 +156,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->boundinfo = NULL; rel->part_appinfos = NULL; rel->part_rels = NULL; + rel->runtime_prune_params = NULL; rel->partexprs = NULL; rel->nullable_partexprs = NULL; rel->live_part_appinfos = NIL; @@ -577,6 +578,7 @@ build_join_rel(PlannerInfo *root, joinrel->boundinfo = NULL; joinrel->part_appinfos = NULL; joinrel->part_rels = NULL; + joinrel->runtime_prune_params = NULL; joinrel->partexprs = NULL; joinrel->nullable_partexprs = NULL; @@ -745,6 +747,7 @@ build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel, joinrel->part_scheme = NULL; joinrel->part_appinfos = NULL; joinrel->part_rels = NULL; + joinrel->runtime_prune_params = NULL; joinrel->partexprs = NULL; joinrel->nullable_partexprs = NULL; diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h index 7a5ab45..9ff6685 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" @@ -73,5 +74,9 @@ extern int get_partition_for_tuple(Relation relation, Datum *values, /* For partition-pruning */ extern Bitmapset *get_partitions_from_clauses(Relation relation, int rt_index, + ParamListInfo prmlist, ExprContext *econtext, List *partclauses); +extern PartitionPruneInfo *make_partition_pruneinfo(PlannerInfo *root, + RelOptInfo *rel, + List *partition_rels, List *subpaths); #endif /* PARTITION_H */ diff --git a/src/include/nodes/bitmapset.h b/src/include/nodes/bitmapset.h index 3b62a97..b4ded9a 100644 --- a/src/include/nodes/bitmapset.h +++ b/src/include/nodes/bitmapset.h @@ -98,6 +98,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 c9a5279..4467bd3 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -1005,6 +1005,7 @@ 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 * ---------------- */ @@ -1019,8 +1020,13 @@ struct AppendState PlanState **appendplans; /* array of PlanStates for my inputs */ int as_nplans; int as_whichplan; + Bitmapset *as_valid_subplans; ParallelAppendState *as_pstate; /* parallel coordination info */ Size pstate_len; /* size of parallel coordination info */ + List *prune_qual; /* quals used for partition pruning */ + Bitmapset *part_prune_params; /* ParamIds useful for partition pruning */ + PartitionPruneInfo *part_prune_info; /* details for partition pruning */ + 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 c5b5115..b21ecfb 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -191,6 +191,7 @@ typedef enum NodeTag T_FromExpr, T_OnConflictExpr, 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 d763da6..cc57b1d 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -249,6 +249,14 @@ typedef struct Append List *partitioned_rels; List *appendplans; int first_partial_plan; + Bitmapset *part_prune_params; /* ParamIds used for partition pruning */ + + /* + * 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 074ae0a..cdb7d61 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -1506,4 +1506,24 @@ typedef struct OnConflictExpr List *exclRelTlist; /* tlist of the EXCLUDED pseudo relation */ } OnConflictExpr; +/*---------- + * 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; + Oid parentoid; /* Oid of parent partition rel */ + 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 9f0b657..7d83375 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -669,6 +669,9 @@ typedef struct RelOptInfo struct RelOptInfo **part_rels; /* Array of RelOptInfos of *all* * partitions, stored in the same order as * of bounds */ + Bitmapset *runtime_prune_params; /* Only valid for base partition rels. + * Stores ParamIds used for run-time + * pruning of partitions. */ List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ @@ -1295,6 +1298,10 @@ typedef struct AppendPath /* Index of first partial path in subpaths */ int first_partial_path; + + /* ParamIds useful for subpath elimination during execution, or NULL */ + Bitmapset *part_prune_params; + 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 1ef13a4..4fb48b1 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/pathnode.h b/src/include/optimizer/pathnode.h index f183aac..d153aa3 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -64,11 +64,12 @@ 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, - List *partitioned_rels, double rows); + List *partitioned_rels, double rows, + bool trypartitionprune); extern MergeAppendPath *create_merge_append_path(PlannerInfo *root, RelOptInfo *rel, List *subpaths, diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index ea886b6..b98d0b9 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -50,6 +50,12 @@ extern PGDLLIMPORT join_search_hook_type join_search_hook; extern RelOptInfo *make_one_rel(PlannerInfo *root, List *joinlist); extern void set_dummy_rel_pathlist(RelOptInfo *rel); +extern List *match_clauses_to_partkey(PlannerInfo *root, + RelOptInfo *rel, + List *clauses, + Bitmapset **paramids, + bool *contains_const, + bool *constfalse); extern RelOptInfo *standard_join_search(PlannerInfo *root, int levels_needed, List *initial_rels); diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out index ad29f0f..7b86e6e 100644 --- a/src/test/regress/expected/partition_prune.out +++ b/src/test/regress/expected/partition_prune.out @@ -1427,3 +1427,500 @@ 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 partitioning +-- +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) + +deallocate ab_q1; +-- join +drop table if exists tbl1; +NOTICE: table "tbl1" does not exist, skipping +create table tbl1(col1 int); +insert into tbl1 values (501), (505); +-- basic table +drop table if exists tprt; +NOTICE: table "tprt" does not exist, skipping +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); +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=17 loops=1) + -> Seq Scan on tbl1 (actual rows=4 loops=1) + -> Append (actual rows=4 loops=4) + -> Index Only Scan using tprt1_idx on tprt_1 (actual rows=2 loops=4) + Index Cond: (col1 < tbl1.col1) + Heap Fetches: 8 + -> Index Only Scan using tprt2_idx on tprt_2 (actual rows=3 loops=3) + Index Cond: (col1 < tbl1.col1) + Heap Fetches: 8 + -> Index Only Scan using tprt3_idx on tprt_3 (actual rows=1 loops=1) + 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) + +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=4 loops=1) + -> Append (actual rows=1 loops=4) + -> 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=1 loops=2) + 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 +(17 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) + +-- parallel append +prepare ab_q1 (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_q1 (1, 8); + avg +----- + +(1 row) + +execute ab_q1 (1, 8); + avg +----- + +(1 row) + +execute ab_q1 (1, 8); + avg +----- + +(1 row) + +execute ab_q1 (1, 8); + avg +----- + +(1 row) + +execute ab_q1 (1, 8); + avg +----- + +(1 row) + +explain (analyze, costs off, summary off, timing off) execute ab_q1 (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) + +deallocate ab_q1; +drop table ab, tbl1, tprt; diff --git a/src/test/regress/sql/partition_prune.sql b/src/test/regress/sql/partition_prune.sql index 6921e39..d7b359d 100644 --- a/src/test/regress/sql/partition_prune.sql +++ b/src/test/regress/sql/partition_prune.sql @@ -225,3 +225,154 @@ 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 partitioning +-- +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); + +deallocate ab_q1; + +-- join +drop table if exists tbl1; +create table tbl1(col1 int); +insert into tbl1 values (501), (505); + +-- basic table +drop table if exists tprt; +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); +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; + +-- parallel append +prepare ab_q1 (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_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); + +deallocate ab_q1; + +drop table ab, tbl1, tprt;