From 184a5457666ba5636cc5f41da7c56537ee97b453 Mon Sep 17 00:00:00 2001 From: amit Date: Tue, 22 Aug 2017 13:48:13 +0900 Subject: [PATCH v21 2/5] Introduce a get_partitions_from_clauses() Whereas get_partition_for_tuple() takes a tuple and returns index of the partition of the table that should contain that tuple, get_partitions_from_clauses() will take a list of query clauses and return a set of indexes of the partitions that satisfy all of those clauses. It is meant as a faster alternative to the planner's current method of selecting a table's partitions by running contraint exclusion algorithm against the partition constraint of each of the partitions. Callers must have checked that each of the clauses matches one of the partition keys. Authors: Amit Langote, David Rowley (david.rowley@2ndquadrant.com) --- src/backend/catalog/partition.c | 2077 +++++++++++++++++++++++++++++++++- src/backend/optimizer/util/clauses.c | 4 +- src/include/catalog/partition.h | 3 + src/include/catalog/pg_opfamily.h | 3 + src/include/optimizer/clauses.h | 2 + 5 files changed, 2085 insertions(+), 4 deletions(-) diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c index 1edbf66eae..b35e35cfb6 100644 --- a/src/backend/catalog/partition.c +++ b/src/backend/catalog/partition.c @@ -28,6 +28,8 @@ #include "catalog/pg_inherits.h" #include "catalog/pg_inherits_fn.h" #include "catalog/pg_opclass.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_opfamily.h" #include "catalog/pg_partitioned_table.h" #include "catalog/pg_type.h" #include "commands/tablecmds.h" @@ -38,6 +40,8 @@ #include "nodes/parsenodes.h" #include "optimizer/clauses.h" #include "optimizer/planmain.h" +#include "optimizer/planner.h" +#include "optimizer/predtest.h" #include "optimizer/prep.h" #include "optimizer/var.h" #include "parser/parse_coerce.h" @@ -151,7 +155,6 @@ typedef struct PartitionRangeBound */ typedef struct PartitionBoundCmpArg { - bool is_bound; union { PartitionListValue *lbound; @@ -161,8 +164,101 @@ typedef struct PartitionBoundCmpArg Datum *datums; int ndatums; + bool is_bound; } PartitionBoundCmpArg; +/* + * Information about a clause matched with a partition key column kept to + * avoid recomputing it in remove_redundant_clauses(). + */ +typedef struct PartClause +{ + OpExpr *op; + Expr *constarg; + + /* cached info. */ + bool valid_cache; /* Are the following fields populated? */ + int op_strategy; + Oid op_subtype; + FmgrInfo op_func; +} PartClause; + +/* + * Strategy of a partition clause operator per the partitioning operator class + * definition. + */ +typedef enum PartOpStrategy +{ + PART_OP_EQUAL, + PART_OP_LESS, + PART_OP_GREATER +} PartOpStrategy; + +/* + * Clauses matched to partition keys + */ +typedef struct PartScanClauseInfo +{ + /* Lists of clauses indexed by partition key */ + List *clauses[PARTITION_MAX_KEYS]; + + List *or_clauses; /* List of clauses found in an OR branch */ + List *ne_clauses; /* Clauses in the form partkey <> Expr */ + + Bitmapset *keyisnull; + Bitmapset *keyisnotnull; + + /* Stored data is known to contain impossible contradictions */ + bool constfalse; +} PartScanClauseInfo; + +/* + * PartScanKeyInfo + * Information about partition look up keys to be passed to + * get_partitions_for_keys() + * + * Stores Datums and nullness properties found in clauses which match the + * partition key. Properties found are cached and are indexed by the + * partition key index. + */ +typedef struct PartScanKeyInfo +{ + /* + * Equality look up key. Used to store known Datums values from clauses + * compared by an equality operation to the partition key. + */ + Datum eqkeys[PARTITION_MAX_KEYS]; + + /* + * Lower and upper bounds on a sequence of selected partitions. These may + * contain values for only a prefix of the partition keys. + */ + Datum minkeys[PARTITION_MAX_KEYS]; + Datum maxkeys[PARTITION_MAX_KEYS]; + + /* + * Number of values stored in the corresponding array above + */ + int n_eqkeys; + int n_minkeys; + int n_maxkeys; + + /* + * Properties to mark if the clauses found for the corresponding partition + * are inclusive of the stored value or not. + */ + bool min_incl; + bool max_incl; + + /* + * Information about nullness of the partition keys, either specified + * explicitly in the query (in the form of a IS [NOT] NULL clause) or + * implied from strict clauses matching the partition key. + */ + Bitmapset *keyisnull; + Bitmapset *keyisnotnull; +} PartScanKeyInfo; + static int32 qsort_partition_hbound_cmp(const void *a, const void *b); static int32 qsort_partition_list_value_cmp(const void *a, const void *b, void *arg); @@ -211,6 +307,35 @@ static uint64 compute_hash_value(PartitionKey key, Datum *values, bool *isnull); /* SQL-callable function for use in hash partition CHECK constraints */ PG_FUNCTION_INFO_V1(satisfies_hash_partition); +static Bitmapset *get_partitions_from_clauses_recurse(Relation relation, + int rt_index, List *clauses); +static Bitmapset *get_partitions_excluded_by_ne_clauses(Relation relation, + List *ne_clauses); +static Bitmapset *get_partitions_from_or_clause_args(Relation relation, + int rt_index, List *or_clause_args); +static bool extract_partition_key_clauses(PartitionKey partkey, List *clauses, + int rt_index, PartScanClauseInfo *partclauses); +static bool extract_bounding_datums(PartitionKey partkey, + PartScanClauseInfo *partclauses, + PartScanKeyInfo *keys); +static bool partition_cmp_args(PartitionKey key, int partkeyidx, + PartClause *op, PartClause *leftarg, PartClause *rightarg, + bool *result); +static PartOpStrategy partition_op_strategy(PartitionKey key, PartClause *op, + bool *incl); +static bool partkey_datum_from_expr(PartitionKey key, int partkeyidx, + Expr *expr, Datum *value); +static void remove_redundant_clauses(PartitionKey partkey, + PartScanClauseInfo *partclauses); +static Bitmapset *get_partitions_for_keys(Relation rel, + PartScanKeyInfo *keys); +static Bitmapset *get_partitions_for_keys_hash(Relation rel, + PartScanKeyInfo *keys); +static Bitmapset *get_partitions_for_keys_list(Relation rel, + PartScanKeyInfo *keys); +static Bitmapset *get_partitions_for_keys_range(Relation rel, + PartScanKeyInfo *keys); + /* * RelationBuildPartitionDesc * Form rel's partition descriptor @@ -1581,9 +1706,1959 @@ get_partition_qual_relid(Oid relid) return result; } +/* + * get_partitions_from_clauses + * Determine all partitions of 'relation' that could possibly contain a + * record that matches 'partclauses' + * + * Returns a Bitmapset of the matching partition indexes, or NULL if none can + * match. + */ +Bitmapset * +get_partitions_from_clauses(Relation relation, int rt_index, + List *partclauses) +{ + PartitionDesc partdesc = RelationGetPartitionDesc(relation); + PartitionBoundInfo boundinfo; + List *clauses; + + /* All partitions match if there are no clauses */ + if (!partclauses) + return bms_add_range(NULL, 0, partdesc->nparts - 1); + + /* Some functions called below modify this list */ + clauses = list_copy(partclauses); + boundinfo = partdesc->boundinfo; + + /* + * If relation is a sub-partitioned table, add its partition constraint + * clauses to the list of clauses to use for partition pruning. This + * is done to facilitate correct decision regarding the default + * partition. Adding the partition constraint clauses to the list helps + * restrict the possible key space to only that allowed by the partition + * and thus avoids the default partition being inadvertently added to the + * set of selected partitions for a query whose clauses select a key space + * bigger than the partition's. + */ + if (partition_bound_has_default(boundinfo)) + { + List *partqual = RelationGetPartitionQual(relation); + + partqual = (List *) expression_planner((Expr *) partqual); + + /* Fix Vars to have the desired varno */ + if (rt_index != 1) + ChangeVarNodes((Node *) partqual, 1, rt_index, 0); + + clauses = list_concat(clauses, partqual); + } + + return get_partitions_from_clauses_recurse(relation, rt_index, clauses); +} + /* Module-local functions */ /* + * get_partitions_from_clauses_recurse + * Determine relation's partitions that satisfy *all* of the clauses + * in the list + * + * Return value is a Bitmapset containing the indexes of selected partitions. + */ +static Bitmapset * +get_partitions_from_clauses_recurse(Relation relation, int rt_index, + List *clauses) +{ + PartitionDesc partdesc = RelationGetPartitionDesc(relation); + PartitionKey partkey = RelationGetPartitionKey(relation); + PartScanClauseInfo partclauses; + Bitmapset *result; + PartScanKeyInfo keys; + ListCell *lc; + + /* Populate partclauses from the clause list */ + if (extract_partition_key_clauses(partkey, clauses, rt_index, &partclauses)) + { + /* + * No partitions to scan if extract_partition_key_clauses found some + * clause contradiction. + */ + if (partclauses.constfalse) + return NULL; + + /* collapse clauses down to the most restrictive set */ + remove_redundant_clauses(partkey, &partclauses); + + /* Did remove_redundant_clauses find any contradicting clauses? */ + if (partclauses.constfalse) + return NULL; + + if (extract_bounding_datums(partkey, &partclauses, &keys)) + { + result = get_partitions_for_keys(relation, &keys); + + /* + * No point in trying to look at other conjunctive clauses, if we got + * an empty set in the first place. + */ + if (bms_is_empty(result)) + return NULL; + } + else + { + /* + * We found nothing useful to indicate which partitions might need to + * be scanned. Perhaps we'll find something below that indicates + * which ones won't need to be scanned. + */ + result = bms_add_range(NULL, 0, partdesc->nparts - 1); + } + } + else + { + /* + * no useful key clauses found, but we might still be able to + * eliminate some partitions with ne_clauses or or_clauses. + */ + result = bms_add_range(NULL, 0, partdesc->nparts - 1); + } + + /* Select partitions by applying the clauses containing <> operators. */ + if (partclauses.ne_clauses) + { + Bitmapset *ne_clause_parts; + + ne_clause_parts = get_partitions_excluded_by_ne_clauses(relation, + partclauses.ne_clauses); + + /* Remove any partitions we found to not be needed */ + result = bms_del_members(result, ne_clause_parts); + bms_free(ne_clause_parts); + } + + /* Select partitions by applying OR clauses. */ + foreach(lc, partclauses.or_clauses) + { + BoolExpr *or = (BoolExpr *) lfirst(lc); + Bitmapset *or_parts; + + or_parts = get_partitions_from_or_clause_args(relation, rt_index, + or->args); + /* + * Clauses in or_clauses are mutually conjunctive and also in + * in conjunction with the rest of the clauses above, so combine the + * partitions thus selected with those in result using set + * intersection. + */ + result = bms_int_members(result, or_parts); + bms_free(or_parts); + } + + return result; +} + +/* + * get_partitions_excluded_by_ne_clauses + * + * Returns a Bitmapset of partition indexes of any partition that can safely + * be removed due to 'ne_clauses' containing not-equal clauses for all + * possible values that the partition can contain. + */ +static Bitmapset * +get_partitions_excluded_by_ne_clauses(Relation relation, List *ne_clauses) +{ + ListCell *lc; + Bitmapset *excluded_parts = NULL; + Bitmapset *foundoffsets = NULL; + PartitionKey partkey = RelationGetPartitionKey(relation); + PartitionDesc partdesc = RelationGetPartitionDesc(relation); + PartitionBoundInfo boundinfo = partdesc->boundinfo; + PartitionBoundCmpArg arg; + int *datums_in_part; + int *datums_found; + int i; + + Assert(partkey->strategy == PARTITION_STRATEGY_LIST); + Assert(partkey->partnatts == 1); + + memset(&arg, 0, sizeof(arg)); + + /* + * Build a Bitmapset to record the indexes of all datums of the + * query that are found in boundinfo. + */ + foreach(lc, ne_clauses) + { + PartClause *pc = (PartClause *) lfirst(lc); + Datum datum; + + if (partkey_datum_from_expr(partkey, 0, pc->constarg, &datum)) + { + int offset; + bool is_equal; + + arg.datums = &datum; + arg.ndatums = 1; + offset = partition_bound_bsearch(partkey, boundinfo, &arg, + &is_equal); + + if (offset >= 0 && is_equal) + { + Assert(boundinfo->indexes[offset] >= 0); + foundoffsets = bms_add_member(foundoffsets, offset); + } + } + } + + /* No partitions can be excluded if none of the datums were found. */ + if (bms_is_empty(foundoffsets)) + return NULL; + + /* + * Since each list partition can permit multiple values, we must ensure + * that we got clauses for all those values before we can eliminate the + * the entire partition. + * + * We'll need two arrays for this, one to count the number of unique + * datums we found in the query, and another to record the number of + * datums permitted in each partition. Once we've counted all this, we + * can eliminate any partition where the number of datums found matches + * the number of datums allowed in the partition. + */ + datums_in_part = (int *) palloc0(sizeof(int) * partdesc->nparts); + datums_found = (int *) palloc0(sizeof(int) * partdesc->nparts); + + i = -1; + while ((i = bms_next_member(foundoffsets, i)) >= 0) + datums_found[boundinfo->indexes[i]]++; + + /* + * Now, in a single pass over all the datums, count the number of datums + * permitted in each partition. + */ + for (i = 0; i < boundinfo->ndatums; i++) + datums_in_part[boundinfo->indexes[i]]++; + + /* + * Now compare the counts and eliminate any partition for which we found + * clauses for all its permitted values. We must be careful here not to + * eliminate the default partition. We can recognize that by it having a + * zero value in both arrays. + */ + for (i = 0; i < partdesc->nparts; i++) + { + if (datums_found[i] >= datums_in_part[i] && datums_found[i] > 0) + excluded_parts = bms_add_member(excluded_parts, i); + } + + /* + * Because the above clauses are strict, we can also exclude the NULL + * partition, provided it does not also allow non-NULL values. + */ + if (partition_bound_accepts_nulls(boundinfo) && + datums_in_part[boundinfo->null_index] == 0) + excluded_parts = bms_add_member(excluded_parts, + boundinfo->null_index); + + pfree(datums_in_part); + pfree(datums_found); + + return excluded_parts; +} + +/* + * get_partitions_from_or_clause_args + * + * Returns the set of partitions of relation, each of which satisfies some + * clause in or_clause_args. + */ +static Bitmapset * +get_partitions_from_or_clause_args(Relation relation, int rt_index, + List *or_clause_args) +{ + ListCell *lc; + Bitmapset *result = NULL; + + foreach(lc, or_clause_args) + { + List *arg_clauses = list_make1(lfirst(lc)); + List *partconstr = RelationGetPartitionQual(relation); + Bitmapset *arg_partset; + + /* + * It's possible that this clause is never true for this relation due + * to it contradicting the partition's constraint. In this case we + * must not include any partitions for this OR clause. However, this + * OR clause may not contain any quals matching this partition table's + * partition key, it may contain some belonging to a parent partition + * though, so we may not have all the quals here required to make use + * of get_partitions_from_clauses_recurse to determine the correct set + * of partitions, so we'll just make use of predicate_refuted_by + * instead. + */ + if (partconstr) + { + partconstr = (List *) expression_planner((Expr *) partconstr); + if (rt_index != 1) + ChangeVarNodes((Node *) partconstr, 1, rt_index, 0); + if (predicate_refuted_by(partconstr, arg_clauses, false)) + continue; + } + + arg_partset = get_partitions_from_clauses_recurse(relation, rt_index, + arg_clauses); + result = bms_add_members(result, arg_partset); + bms_free(arg_partset); + } + + return result; +} + +/* Match partition key (partattno/partexpr) to an expression (expr). */ +#define EXPR_MATCHES_PARTKEY(expr, partattno, partexpr) \ + ((partattno) != 0 ? \ + (IsA((expr), Var) && \ + ((Var *) (expr))->varattno == (partattno)) : \ + equal((expr), (partexpr))) + +#define COLLATION_MATCH(partcoll, exprcoll) \ + (!OidIsValid(partcoll) || (partcoll) == (exprcoll)) + +/* + * extract_partition_key_clauses + * Process 'clauses' to extract clause matching the partition key. + * This populates 'partclauses' with the set of clauses matching each + * key also also collects other useful clauses to assist in partition + * elimination, such as or clauses and not equal clauses. We also record + * which partitions keys we can prove are NULL or NOT NULL. + * + * We may also discover some contradition in the clauses which means that no + * partition can possibly match. In this case the function sets partclauses's + * 'constfalse' to true and returns true. In this case the caller should not + * assume the clauses have been fully processed as we abort as soon as we find + * a contradicting condition. + * + * The function returns false if no useful key clauses were found. + */ +static bool +extract_partition_key_clauses(PartitionKey partkey, List *clauses, + int rt_index, + PartScanClauseInfo *partclauses) +{ + int i; + ListCell *lc; + bool got_useful_keys = false; + + memset(partclauses, 0, sizeof(PartScanClauseInfo)); + + foreach(lc, clauses) + { + Expr *clause = (Expr *) lfirst(lc); + ListCell *partexprs_item; + + if (IsA(clause, RestrictInfo)) + { + RestrictInfo *rinfo = (RestrictInfo *) clause; + + clause = rinfo->clause; + if (rinfo->pseudoconstant && + !DatumGetBool(((Const *) clause)->constvalue)) + { + partclauses->constfalse = true; + return true; + } + } + + /* Get the BoolExpr's out of the way.*/ + if (IsA(clause, BoolExpr)) + { + if (or_clause((Node *) clause)) + { + partclauses->or_clauses = lappend(partclauses->or_clauses, clause); + continue; + } + else if (and_clause((Node *) clause)) + { + clauses = list_concat(clauses, + list_copy(((BoolExpr *) clause)->args)); + continue; + } + /* Fall-through for a NOT clause, which is handled below. */ + } + + partexprs_item = list_head(partkey->partexprs); + for (i = 0; i < partkey->partnatts; i++) + { + Oid partopfamily = partkey->partopfamily[i]; + AttrNumber partattno = partkey->partattrs[i]; + Oid partcoll = partkey->partcollation[i]; + Expr *partexpr = NULL; + PartClause *pc; + Oid commutator = InvalidOid; + + /* + * A zero attno means the partition key is an expression, so grab + * the next expression from the list. + */ + if (partattno == 0) + { + if (partexprs_item == NULL) + elog(ERROR, "wrong number of partition key expressions"); + + partexpr = (Expr *) lfirst(partexprs_item); + + /* + * Expressions stored for the PartitionKey in the relcache are + * all stored with the dummy varno of 1. Change that to what + * we need. + */ + if (rt_index != 1) + { + /* make a copy so as not to overwrite the relcache */ + partexpr = (Expr *) copyObject(partexpr); + ChangeVarNodes((Node *) partexpr, 1, rt_index, 0); + } + + partexprs_item = lnext(partexprs_item); + } + + if (IsA(clause, OpExpr)) + { + OpExpr *opclause = (OpExpr *) clause; + Expr *leftop, + *rightop, + *constexpr; + bool is_ne_listp = false; + + leftop = (Expr *) get_leftop(clause); + if (IsA(leftop, RelabelType)) + leftop = ((RelabelType *) leftop)->arg; + rightop = (Expr *) get_rightop(clause); + if (IsA(rightop, RelabelType)) + rightop = ((RelabelType *) rightop)->arg; + + /* check if the clause matches the partition key */ + if (EXPR_MATCHES_PARTKEY(leftop, partattno, partexpr)) + constexpr = rightop; + else if (EXPR_MATCHES_PARTKEY(rightop, partattno, partexpr)) + { + constexpr = leftop; + + commutator = get_commutator(opclause->opno); + + /* nothing we can do unless we can swap the operands */ + if (!OidIsValid(commutator)) + continue; + } + else + /* Clause does not match this partition key. */ + continue; + + /* + * Also, useless, if the clause's collation is different from + * the partitioning collation. + */ + if (!COLLATION_MATCH(partcoll, opclause->inputcollid)) + continue; + + /* + * Only allow strict operators. This will guarantee nulls are + * filtered. + */ + if (!op_strict(opclause->opno)) + continue; + + /* Useless if the "constant" can change its value. */ + if (contain_volatile_functions((Node *) constexpr)) + continue; + + /* + * Handle cases where the clause's operator does not belong to + * the partitioning operator family. We currently handle two + * such cases: 1. Operators named '<>' are not listed in any + * operator family whatsoever, 2. Ordering opertors like '<' + * are not listed in the hash operator families. For 1, check + * if list partitioning is in use and if so, proceed to pass + * the clause to the caller without doing any more processing + * ourselves. 2 cannot be handled at all, so the clause is + * simply skipped. + */ + if (!op_in_opfamily(opclause->opno, partopfamily)) + { + int strategy; + Oid negator, + lefttype, + righttype; + + /* + * To confirm if the operator is really '<>', check if its + * negator is a equality operator. If it's a btree + * equality operator *and* this is a list partitioned + * table, we can use it prune partitions. + */ + negator = get_negator(opclause->opno); + if (OidIsValid(negator) && + op_in_opfamily(negator, partopfamily)) + { + get_op_opfamily_properties(negator, partopfamily, + false, + &strategy, + &lefttype, &righttype); + if (strategy == BTEqualStrategyNumber && + partkey->strategy == PARTITION_STRATEGY_LIST) + is_ne_listp = true; + } + + /* Cannot handle this clause. */ + if (!is_ne_listp) + continue; + } + + pc = (PartClause *) palloc0(sizeof(PartClause)); + pc->constarg = constexpr; + + /* + * If commutator is set to a valid Oid then we'll need to swap + * the left and right operands. Later code requires that the + * partkey is on the left side. + */ + if (!OidIsValid(commutator)) + pc->op = opclause; + else + { + OpExpr *commuted; + + commuted = (OpExpr *) copyObject(opclause); + commuted->opno = commutator; + commuted->opfuncid = get_opcode(commutator); + commuted->args = list_make2(rightop, leftop); + pc->op = commuted; + } + + /* + * We don't turn a <> operator clause into a key right away. + * Instead, the caller will hand over such clauses to + * get_partitions_excluded_by_ne_clauses(). + */ + if (is_ne_listp) + partclauses->ne_clauses = lappend(partclauses->ne_clauses, + pc); + else + { + partclauses->clauses[i] = lappend(partclauses->clauses[i], pc); + got_useful_keys = true; + + /* + * Since we only allow strict operators, require keys to + * be not null. + */ + if (bms_is_member(i, partclauses->keyisnull)) + { + partclauses->constfalse = true; + return true; + } + partclauses->keyisnotnull = + bms_add_member(partclauses->keyisnotnull, i); + } + } + else if (IsA(clause, ScalarArrayOpExpr)) + { + ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause; + Oid saop_op = saop->opno; + Oid saop_opfuncid = saop->opfuncid; + Oid saop_coll = saop->inputcollid; + Expr *leftop = (Expr *) linitial(saop->args), + *rightop = (Expr *) lsecond(saop->args); + List *elem_exprs, + *elem_clauses; + ListCell *lc1; + bool negated = false; + + if (IsA(leftop, RelabelType)) + leftop = ((RelabelType *) leftop)->arg; + + /* Check it matches this partition key */ + if (!EXPR_MATCHES_PARTKEY(leftop, partattno, partexpr)) + continue; + + /* + * Also, useless, if the clause's collation is different from + * the partitioning collation. + */ + if (!COLLATION_MATCH(partcoll, saop->inputcollid)) + continue; + + /* + * Only allow strict operators. This will guarantee null are + * filtered. + */ + if (!op_strict(saop->opno)) + continue; + + /* Useless if the array has any volatile functions. */ + if (contain_volatile_functions((Node *) rightop)) + continue; + + /* + * In case of NOT IN (..), we get a '<>', which while not + * listed as part of any operator family, we are able to + * handle it if its negator is indeed a part of the + * partitioning operator family. + */ + if (!op_in_opfamily(saop_op, partopfamily)) + { + Oid negator = get_negator(saop_op); + int strategy; + Oid lefttype, + righttype; + + if (!OidIsValid(negator)) + continue; + get_op_opfamily_properties(negator, partopfamily, false, + &strategy, + &lefttype, &righttype); + if (strategy == BTEqualStrategyNumber) + negated = true; + } + + /* + * First generate a list of Const nodes, one for each array + * element. + */ + elem_exprs = NIL; + if (IsA(rightop, Const)) + { + Const *arr = (Const *) lsecond(saop->args); + ArrayType *arrval = DatumGetArrayTypeP(arr->constvalue); + int16 elemlen; + bool elembyval; + char elemalign; + Datum *elem_values; + bool *elem_nulls; + int num_elems; + + get_typlenbyvalalign(ARR_ELEMTYPE(arrval), + &elemlen, &elembyval, &elemalign); + deconstruct_array(arrval, + ARR_ELEMTYPE(arrval), + elemlen, elembyval, elemalign, + &elem_values, &elem_nulls, + &num_elems); + for (i = 0; i < num_elems; i++) + { + if (!elem_nulls[i]) + elem_exprs = lappend(elem_exprs, + makeConst(ARR_ELEMTYPE(arrval), + -1, arr->constcollid, + elemlen, elem_values[i], + false, elembyval)); + else + elem_exprs = lappend(elem_exprs, + makeNullConst(ARR_ELEMTYPE(arrval), + -1, + arr->constcollid)); + } + } + else + { + ArrayExpr *arrexpr = castNode(ArrayExpr, rightop); + + /* + * For a nested ArrayExpr, we don't know how to get the + * actual scalar values out into a flat list, so we give + * up doing anything with this ScalarArrayOpExpr. + */ + if (arrexpr->multidims) + continue; + + elem_exprs = arrexpr->elements; + } + + /* + * Now generate a list of clauses, one for each array element, + * of the form: saop_leftop saop_op elem_expr + */ + elem_clauses = NIL; + foreach(lc1, elem_exprs) + { + Expr *rightop = (Expr *) lfirst(lc1); + Expr *elem_clause; + + if (IsA(rightop, Const) && ((Const *) rightop)->constisnull) + { + NullTest *nulltest = makeNode(NullTest); + + nulltest->arg = (Expr *) leftop; + nulltest->nulltesttype = !negated ? IS_NULL + : IS_NOT_NULL; + nulltest->argisrow = false; + nulltest->location = -1; + elem_clause = (Expr *) nulltest; + } + else + { + OpExpr *opexpr = makeNode(OpExpr); + + opexpr->opno = saop_op; + opexpr->opfuncid = saop_opfuncid; + opexpr->opresulttype = BOOLOID; + opexpr->opretset = false; + opexpr->opcollid = InvalidOid; + opexpr->inputcollid = saop_coll; + opexpr->args = list_make2(leftop, rightop); + opexpr->location = -1; + elem_clause = (Expr *) opexpr; + } + + elem_clauses = lappend(elem_clauses, elem_clause); + } + + /* + * Build the OR clause if needed or add the clauses to the end + * of the list that's being processed currently. + */ + if (saop->useOr) + partclauses->or_clauses = lappend(partclauses->or_clauses, + makeBoolExpr(OR_EXPR, elem_clauses, + -1)); + else + clauses = list_concat(clauses, elem_clauses); + } + else if (IsA(clause, NullTest)) + { + NullTest *nulltest = (NullTest *) clause; + Expr *arg = nulltest->arg; + + if (IsA(arg, RelabelType)) + arg = ((RelabelType *) arg)->arg; + + /* Does leftop match with this partition key column? */ + if (EXPR_MATCHES_PARTKEY(arg, partattno, partexpr)) + { + if (nulltest->nulltesttype == IS_NULL) + { + /* check for conflicting IS NOT NULLs */ + if (bms_is_member(i, partclauses->keyisnotnull)) + { + partclauses->constfalse = true; + return true; + } + partclauses->keyisnull = + bms_add_member(partclauses->keyisnull, i); + } + else + { + /* check for conflicting IS NULLs */ + if (bms_is_member(i, partclauses->keyisnull)) + { + partclauses->constfalse = true; + return true; + } + + partclauses->keyisnotnull = + bms_add_member(partclauses->keyisnotnull, i); + } + got_useful_keys = true; + } + } + /* + * Boolean conditions have a special shape, which would've been + * accepted if the partitioning opfamily accepts Boolean + * conditions. + */ + else if (IsBooleanOpfamily(partopfamily) && + (IsA(clause, BooleanTest) || + IsA(clause, Var) || + not_clause((Node *) clause))) + { + Expr *leftop, + *rightop; + + pc = (PartClause *) palloc0(sizeof(PartClause)); + + if (IsA(clause, BooleanTest)) + { + BooleanTest *btest = (BooleanTest *) clause; + + /* Only IS [NOT] TRUE/FALSE are any good to us */ + if (btest->booltesttype == IS_UNKNOWN || + btest->booltesttype == IS_NOT_UNKNOWN) + continue; + + leftop = btest->arg; + if (IsA(leftop, RelabelType)) + leftop = ((RelabelType *) leftop)->arg; + + /* Clause does not match this partition key. */ + if (!EXPR_MATCHES_PARTKEY(leftop, partattno, partexpr)) + continue; + + rightop = (btest->booltesttype == IS_TRUE || + btest->booltesttype == IS_NOT_FALSE) + ? (Expr *) makeBoolConst(true, false) + : (Expr *) makeBoolConst(false, false); + } + else + { + leftop = IsA(clause, Var) + ? (Expr *) clause + : (Expr *) get_notclausearg((Expr *) clause); + if (IsA(leftop, RelabelType)) + leftop = ((RelabelType *) leftop)->arg; + + /* Clause does not match this partition key. */ + if (!EXPR_MATCHES_PARTKEY(leftop, partattno, partexpr)) + continue; + + rightop = IsA(clause, Var) + ? (Expr *) makeBoolConst(true, false) + : (Expr *) makeBoolConst(false, false); + } + pc->op = (OpExpr *) make_opclause(BooleanEqualOperator, + BOOLOID, false, + leftop, rightop, + InvalidOid, InvalidOid); + pc->constarg = rightop; + partclauses->clauses[i] = lappend(partclauses->clauses[i], + pc); + got_useful_keys = true; + } + } + } + + return got_useful_keys; +} + +/* + * extract_bounding_datums + * Process 'partclauses' and populate 'keys' with all min/max/equal values + * that we're able to determine. + * + * For RANGE partitioning we do not need to match all partition keys. We may + * be able to eliminate some partitions with just a prefix of the partition + * keys. HASH partitioning does require all keys are matched to with at least + * some combinations of equality clauses and IS NULL clauses. LIST partitions + * don't support multiple partition keys. + * + * Returns true if any keys were found during partition pruning. + */ +static bool +extract_bounding_datums(PartitionKey partkey, PartScanClauseInfo *partclauses, + PartScanKeyInfo *keys) +{ + bool need_next_eq, + need_next_min, + need_next_max; + int i; + ListCell *lc; + + /* + * Based on the strategies of the clause operators (=, />=), try to + * construct tuples of those datums that serve as the exact look up tuple + * or tuples that serve as minimum and maximum bound. If we find datums + * for all partition key columns that appear in = operator clauses, then + * we have the exact match look up tuple, which will be used to match just + * one partition. If the last datum in a tuple comes from a clause + * containing />= operator, then that constitutes the minimum + * or maximum bound tuple, respectively. There is one exception -- if + * we have a tuple containing values for only a prefix of partition key + * columns, where none of its values come from a />= operator + * clause, we still consider such tuple as both the minimum and maximum + * bound tuple. + */ + need_next_eq = true; + need_next_min = true; + need_next_max = true; + memset(keys, 0, sizeof(PartScanKeyInfo)); + for (i = 0; i < partkey->partnatts; i++) + { + List *clauselist = partclauses->clauses[i]; + + /* + * Min and max keys must constitute a prefix of the partition key and + * must appear in the same order as partition keys. Equal keys have + * to satisfy that requirement only for non-hash partitioning. + */ + if (i > keys->n_eqkeys && + partkey->strategy != PARTITION_STRATEGY_HASH) + need_next_eq = false; + + if (i > keys->n_minkeys) + need_next_min = false; + + if (i > keys->n_maxkeys) + need_next_max = false; + + foreach(lc, clauselist) + { + PartClause *clause = (PartClause *) lfirst(lc); + Expr *constarg = clause->constarg; + bool incl; + PartOpStrategy op_strategy; + + op_strategy = partition_op_strategy(partkey, clause, &incl); + switch (op_strategy) + { + case PART_OP_EQUAL: + Assert(incl); + if (need_next_eq && + partkey_datum_from_expr(partkey, i, constarg, + &keys->eqkeys[i])) + keys->n_eqkeys++; + + if (need_next_max && + partkey_datum_from_expr(partkey, i, constarg, + &keys->maxkeys[i])) + { + keys->n_maxkeys++; + keys->max_incl = true; + } + + if (need_next_min && + partkey_datum_from_expr(partkey, i, constarg, + &keys->minkeys[i])) + { + keys->n_minkeys++; + keys->min_incl = true; + } + break; + + case PART_OP_LESS: + if (need_next_max && + partkey_datum_from_expr(partkey, i, constarg, + &keys->maxkeys[i])) + { + keys->n_maxkeys++; + keys->max_incl = incl; + if (!incl) + need_next_eq = need_next_max = false; + } + break; + + case PART_OP_GREATER: + if (need_next_min && + partkey_datum_from_expr(partkey, i, constarg, + &keys->minkeys[i])) + { + keys->n_minkeys++; + keys->min_incl = incl; + if (!incl) + need_next_eq = need_next_min = false; + } + break; + + default: + Assert(false); + } + } + } + + /* + * To set eqkeys, we must have found matching clauses containing equality + * operators for all partition key columns and if present we don't need + * the values in minkeys and maxkeys anymore. In the case hash + * partitioning, we don't require all of eqkeys to be operator clausses. + * In that case, any IS NULL clauses involving partition key columns are + * also considered as equality keys by the code for hash partition pruning, + * which checks that all partition columns are covered before actually + * performing the pruning. + */ + if (keys->n_eqkeys == partkey->partnatts || + partkey->strategy == PARTITION_STRATEGY_HASH) + keys->n_minkeys = keys->n_maxkeys = 0; + else + keys->n_eqkeys = 0; + + /* Finally, also set the keyisnull and keyisnotnull values. */ + keys->keyisnull = partclauses->keyisnull; + keys->keyisnotnull = partclauses->keyisnotnull; + + return (keys->n_eqkeys > 0 || keys->n_minkeys > 0 || + keys->n_maxkeys > 0 || !bms_is_empty(keys->keyisnull) || + !bms_is_empty(keys->keyisnotnull)); +} + +/* + * partition_op_strategy + * Returns whether the clause in 'op' contains an =, />= + * operator and set *incl to true if the operator's strategy is + * inclusive. + */ +static PartOpStrategy +partition_op_strategy(PartitionKey key, PartClause *op, bool *incl) +{ + PartOpStrategy result; + + *incl = false; /* overwritten as appropriate below */ + switch (key->strategy) + { + /* Hash partitioning allows only hash equality. */ + case PARTITION_STRATEGY_HASH: + if (op->op_strategy == HTEqualStrategyNumber) + { + *incl = true; + result = PART_OP_EQUAL; + } + break; + + /* List and range partitioning support all btree operators. */ + case PARTITION_STRATEGY_LIST: + case PARTITION_STRATEGY_RANGE: + switch (op->op_strategy) + { + case BTLessEqualStrategyNumber: + *incl = true; + /* fall through */ + case BTLessStrategyNumber: + result = PART_OP_LESS; + break; + case BTEqualStrategyNumber: + *incl = true; + result = PART_OP_EQUAL; + break; + case BTGreaterEqualStrategyNumber: + *incl = true; + /* fall through */ + case BTGreaterStrategyNumber: + result = PART_OP_GREATER; + break; + } + break; + + default: + elog(ERROR, "unexpected partition strategy: %d", + (int) key->strategy); + } + + return result; +} + +/* + * partkey_datum_from_expr + * Set *value to the constant value obtained by evaluating 'expr' + * + * Note that we may not be able to evaluate the input expression, in which + * case, the function returns false to indicate that *value has not been + * set. True is returned otherwise. + */ +static bool +partkey_datum_from_expr(PartitionKey key, int partkeyidx, + Expr *expr, Datum *value) +{ + Oid exprtype = exprType((Node *) expr); + + if (exprtype != key->parttypid[partkeyidx]) + { + ParseState *pstate = make_parsestate(NULL); + + expr = (Expr *) coerce_to_target_type(pstate, (Node *) expr, + exprtype, + key->parttypid[partkeyidx], -1, + COERCION_EXPLICIT, + COERCE_IMPLICIT_CAST, -1); + free_parsestate(pstate); + + /* + * If we couldn't coerce to the partition key's type, that is, the + * type of the datums stored in PartitionBoundInfo for this partition + * key, there's no hope of using this expression for anything + * partitioning-related. + */ + if (expr == NULL) + return false; + + /* + * Transform into a form that the following code can do something + * useful with. + */ + expr = evaluate_expr(expr, + exprType((Node *) expr), + exprTypmod((Node *) expr), + exprCollation((Node *) expr)); + } + + /* + * Add more expression types here as needed to support higher-level + * code. + */ + if (IsA(expr, Const)) + { + *value = ((Const *) expr)->constvalue; + return true; + } + + return false; +} + +/* + * remove_redundant_clauses + * Collapse the 'partclauses' clauses lists to remove clause which are + * superseeded by a clause which is more restrictive. + * + * Here we perform further processing on 'partclauses' to remove any redundant + * clauses. We also look for clauses which contradict one another in a way + * that proves the 'partclauses' cannot possibly match any partition. + * Impossible clauses include things like: x = 1 AND x = 2, x > 0 and x < 10 + * + * We also transform 'partclauses' into the minimum set of clauses by removing + * any clauses which are made redundant by a more restrictive clause. For + * example, x > 1 AND x > 2 and x >= 5, the latter is the most restrictive so + * 5 is the best minimum bound. The operator here also happens to be + * inclusive. + * + * If we find that two clauses contradict each other then 'partclauses' + * constfalse is set to true to alert the caller that nothing can match. + */ +static void +remove_redundant_clauses(PartitionKey partkey, + PartScanClauseInfo *partclauses) +{ + PartClause *hash_clause, + *btree_clauses[BTMaxStrategyNumber]; + ListCell *lc; + int s; + int i; + bool test_result; + List *newlist; + + for (i = 0; i < partkey->partnatts; i++) + { + List *all_clauses = partclauses->clauses[i]; + + hash_clause = NULL; + newlist = NIL; + + memset(btree_clauses, 0, sizeof(btree_clauses)); + + foreach(lc, all_clauses) + { + PartClause *cur = (PartClause *) lfirst(lc); + + if (!cur->valid_cache) + { + Oid lefttype; + + get_op_opfamily_properties(cur->op->opno, + partkey->partopfamily[i], + false, + &cur->op_strategy, + &lefttype, + &cur->op_subtype); + fmgr_info(get_opcode(cur->op->opno), &cur->op_func); + cur->valid_cache = true; + } + + /* + * Hash-partitioning knows only about equality. So, if we've + * matched a clause and found another clause whose constant + * operand doesn't match the constant operand of the former, then + * we have found mutually contradictory clauses. + */ + if (partkey->strategy == PARTITION_STRATEGY_HASH) + { + if (hash_clause == NULL) + hash_clause = cur; + /* check if another clause would contradict the one we have */ + else if (partition_cmp_args(partkey, i, + cur, cur, hash_clause, + &test_result)) + { + if (!test_result) + { + partclauses->constfalse = true; + return; + } + } + /* + * Couldn't compare; keep hash_clause set to the previous value, + * and add this one directly to the result. Caller would + * arbitrarily choose one of the many and perform + * partition-pruning with it. + */ + else + newlist = lappend(newlist, cur); + + /* + * The code below handles btree operators, so not relevant for + * hash partitioning. + */ + continue; + } + + /* + * The code that follows closely mimics similar processing done by + * nbtutils.c: _bt_preprocess_keys(). + * + * btree_clauses[s] points currently best clause containing the + * operator strategy type s+1; it is NULL if we haven't yet found + * such a clause. + */ + s = cur->op_strategy - 1; + if (btree_clauses[s] == NULL) + { + btree_clauses[s] = cur; + } + else + { + /* + * Is this one more restrictive than what we already have? + * + * Consider some examples: 1. If btree_clauses[BTLT] now contains + * a < 5, and cur is a < 3, then because 3 < 5 is true, a < 5 + * currently at btree_clauses[BTLT] will be replaced by a < 3. + * + * 2. If btree_clauses[BTEQ] now contains a = 5 and cur is a = 7, + * then because 5 = 7 is false, we found a mutual contradiction, + * so we set *constfalse to true and return. + * + * 3. If btree_clauses[BTLT] now contains a < 5 and cur is a < 7, + * 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(partkey, i, + cur, cur, btree_clauses[s], + &test_result)) + { + /* cur is more restrictive, so replace the existing. */ + if (test_result) + btree_clauses[s] = cur; + else if (s == BTEqualStrategyNumber - 1) + { + partclauses->constfalse = true; + return; + } + + /* Old one is more restrictive, so keep around. */ + } + else + { + /* + * We couldn't determine which one is more restrictive. Keep + * the previous one in btree_clauses[s] and push this one directly + * to the output list. + */ + newlist = lappend(newlist, cur); + } + } + } + + if (partkey->strategy == PARTITION_STRATEGY_HASH) + { + /* Note we didn't add this one to the result yet. */ + if (hash_clause) + newlist = lappend(newlist, hash_clause); + list_free(partclauses->clauses[i]); + partclauses->clauses[i] = newlist; + continue; + } + + /* Compare btree operator clauses across strategies. */ + + /* Compare the equality clause with clauses of other strategies. */ + if (btree_clauses[BTEqualStrategyNumber - 1]) + { + PartClause *eq = btree_clauses[BTEqualStrategyNumber - 1]; + + for (s = 0; s < BTMaxStrategyNumber; s++) + { + PartClause *chk = btree_clauses[s]; + + if (!chk || s == (BTEqualStrategyNumber - 1)) + continue; + + /* + * Suppose btree_clauses[BTLT] contained a < 5 and the eq clause + * is a = 5, then because 5 < 5 is false, we found contradiction. + * That is, a < 5 and a = 5 are mutually contradictory. OTOH, if + * 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(partkey, i, + chk, eq, chk, + &test_result)) + { + if (!test_result) + { + partclauses->constfalse = true; + return; + } + /* Discard the no longer needed clause. */ + btree_clauses[s] = NULL; + } + } + } + + /* + * Try to keep only one of <, <=. + * + * Suppose btree_clauses[BTLT] contains a < 3 and btree_clauses[BTLE] + * contains a <= 3 (or a <= 4), then because 3 <= 3 (or 3 <= 4) is true, + * we discard the a <= 3 (or a <= 4) as redundant. If the latter contains + * contains a <= 2, then because 3 <= 2 is false, we dicard a < 3 as + * redundant. + */ + if (btree_clauses[BTLessStrategyNumber - 1] && + btree_clauses[BTLessEqualStrategyNumber - 1]) + { + PartClause *lt = btree_clauses[BTLessStrategyNumber - 1], + *le = btree_clauses[BTLessEqualStrategyNumber - 1]; + + if (partition_cmp_args(partkey, i, + le, lt, le, + &test_result)) + { + if (test_result) + btree_clauses[BTLessEqualStrategyNumber - 1] = NULL; + else + btree_clauses[BTLessStrategyNumber - 1] = NULL; + } + } + + /* Try to keep only one of >, >=. See the example above. */ + if (btree_clauses[BTGreaterStrategyNumber - 1] && + btree_clauses[BTGreaterEqualStrategyNumber - 1]) + { + PartClause *gt = btree_clauses[BTGreaterStrategyNumber - 1], + *ge = btree_clauses[BTGreaterEqualStrategyNumber - 1]; + + if (partition_cmp_args(partkey, i, + ge, gt, ge, + &test_result)) + { + if (test_result) + btree_clauses[BTGreaterEqualStrategyNumber - 1] = NULL; + else + btree_clauses[BTGreaterStrategyNumber - 1] = NULL; + } + } + + /* + * btree_clauses now contains the "best" clause or NULL for each btree + * strategy number. Add to the newlist. + */ + for (s = 0; s < BTMaxStrategyNumber; s++) + { + if (btree_clauses[s]) + newlist = lappend(newlist, btree_clauses[s]); + } + + /* + * Replace the old List with the new one with the redundant clauses + * removed. + */ + list_free(partclauses->clauses[i]); + partclauses->clauses[i] = newlist; + } +} + +/* + * partition_cmp_args + * Try to compare the constant arguments of 'leftarg' and 'rightarg', in + * that order, using the operator of 'op' and set *result to the result + * of this comparison. + * + * Returns true if we could actually perform the comparison; otherwise false. + * We may not be able to perform the comparison if operand values are + * unavailable and/or types of operands are incompatible with the operator. + */ +static bool +partition_cmp_args(PartitionKey key, int partkeyidx, + PartClause *op, PartClause *leftarg, PartClause *rightarg, + bool *result) +{ + Oid partopfamily = key->partopfamily[partkeyidx]; + Datum leftarg_const, + rightarg_const; + + Assert(op->valid_cache && leftarg->valid_cache && rightarg->valid_cache); + /* Get the constant values from the operands */ + if (!partkey_datum_from_expr(key, partkeyidx, + leftarg->constarg, &leftarg_const)) + return false; + if (!partkey_datum_from_expr(key, partkeyidx, + rightarg->constarg, &rightarg_const)) + return false; + + /* + * We can compare leftarg_const and rightarg_const using op's operator + * only if both are of the type expected by it. + */ + if (leftarg->op_subtype == op->op_subtype && + rightarg->op_subtype == op->op_subtype) + { + *result = DatumGetBool(FunctionCall2Coll(&op->op_func, + op->op->inputcollid, + leftarg_const, + rightarg_const)); + return true; + } + else + { + /* Otherwise, look one up in the partitioning operator family. */ + Oid cmp_op = get_opfamily_member(partopfamily, + leftarg->op_subtype, + rightarg->op_subtype, + op->op_strategy); + if (OidIsValid(cmp_op)) + { + *result = DatumGetBool(OidFunctionCall2Coll(get_opcode(cmp_op), + op->op->inputcollid, + leftarg_const, + rightarg_const)); + return true; + } + } + + /* Couldn't do the comparison. */ + *result = false; + return false; +} + +/* + * get_partitions_for_keys + * Returns the partitions of 'rel' that will need to be scanned for the + * given look up keys + * + * Input: + * See the comments above the definition of PartScanKeyInfo to see what + * kind of information is contained in 'keys'. + * + * Outputs: + * Bitmapset containing indexes of the selceted partitions + */ +static Bitmapset * +get_partitions_for_keys(Relation rel, PartScanKeyInfo *keys) +{ + /* Return an empty set if no partitions to see. */ + if (RelationGetPartitionDesc(rel)->nparts == 0) + return NULL; + + switch (RelationGetPartitionKey(rel)->strategy) + { + case PARTITION_STRATEGY_HASH: + return get_partitions_for_keys_hash(rel, keys); + + case PARTITION_STRATEGY_LIST: + return get_partitions_for_keys_list(rel, keys); + + case PARTITION_STRATEGY_RANGE: + return get_partitions_for_keys_range(rel, keys); + + default: + elog(ERROR, "unexpected partition strategy: %d", + RelationGetPartitionKey(rel)->strategy); + } + + return NULL; /* keep compiler quiet */ +} + +/* + * get_partitions_for_keys_hash + * Return partitions of a hash partitioned table for requested + * keys + * + * This interprets the keys and looks up partitions in the partition bound + * descriptor using the hash partitioning semantics. + */ +static Bitmapset * +get_partitions_for_keys_hash(Relation rel, PartScanKeyInfo *keys) +{ + PartitionKey partkey = RelationGetPartitionKey(rel); + PartitionDesc partdesc = RelationGetPartitionDesc(rel); + PartitionBoundInfo boundinfo = partdesc->boundinfo; + bool keyisnull[PARTITION_MAX_KEYS]; + int i; + + Assert(partdesc->nparts > 0); + + /* + * Since tuples with NULL values in the partition key columns are stored + * in regular partitions, we'll treat any IS NULL clauses here as regular + * equality clauses. + */ + memset(keyisnull, false, sizeof(keyisnull)); + for (i = 0; i < partkey->partnatts; i++) + { + if (bms_is_member(i, keys->keyisnull)) + { + keys->n_eqkeys++; + keyisnull[i] = true; + } + } + + /* + * Can only do pruning if we know all the keys and they're all equality + * keys including the nulls that we just counted above. + */ + if (keys->n_eqkeys == partkey->partnatts) + { + uint64 rowHash; + int greatest_modulus = get_greatest_modulus(boundinfo), + result_index; + + rowHash = compute_hash_value(partkey, keys->eqkeys, keyisnull); + result_index = boundinfo->indexes[rowHash % greatest_modulus]; + if (result_index >= 0) + return bms_make_singleton(result_index); + } + else + /* Can't do pruning otherwise, so return all partitions. */ + return bms_add_range(NULL, 0, partdesc->nparts - 1); + + return NULL; +} + +/* + * get_partitions_for_keys_list + * Return partitions of a list partitioned table for requested keys + * + * This interprets the keys and looks up partitions in the partition bound + * descriptor using the list partitioning semantics. + */ +static Bitmapset * +get_partitions_for_keys_list(Relation rel, PartScanKeyInfo *keys) +{ + Bitmapset *result = NULL; + PartitionKey partkey = RelationGetPartitionKey(rel); + PartitionBoundInfo boundinfo = RelationGetPartitionDesc(rel)->boundinfo; + PartitionBoundCmpArg arg; + int i, + eqoff, + minoff, + maxoff; + bool is_equal; + + Assert(RelationGetPartitionDesc(rel)->nparts > 0); + Assert(partkey->partnatts == 1); + + /* + * If the query is looking for null keys, there can only be one such + * partition. Return the same if one exists. + */ + if (!bms_is_empty(keys->keyisnull)) + { + /* + * NULLs may only exist in the NULL partition, or in the + * default, if there's no NULL partition. + */ + if (partition_bound_accepts_nulls(boundinfo)) + return bms_make_singleton(boundinfo->null_index); + else if (partition_bound_has_default(boundinfo)) + return bms_make_singleton(boundinfo->default_index); + else + return NULL; + } + + /* + * If there are no datums to compare keys with, but there are partitions, + * just return the default partition if one exists. + */ + if (boundinfo->ndatums == 0) + { + if (partition_bound_has_default(boundinfo)) + return bms_make_singleton(boundinfo->default_index); + else + return NULL; /* shouldn't happen */ + } + + /* Equality key. */ + if (keys->n_eqkeys > 0) + { + memset(&arg, 0, sizeof(PartitionBoundCmpArg)); + arg.datums = keys->eqkeys; + arg.ndatums = keys->n_eqkeys; + eqoff = partition_bound_bsearch(partkey, boundinfo, &arg, &is_equal); + + if (eqoff >= 0 && is_equal) + { + /* Exactly matching datum exists. */ + Assert(boundinfo->indexes[eqoff] >= 0); + return bms_make_singleton(boundinfo->indexes[eqoff]); + } + else if (partition_bound_has_default(boundinfo)) + return bms_make_singleton(boundinfo->default_index); + else + return NULL; + } + + /* + * Find the left-most bound that satisfies the query, i.e., the one that + * satisfies minkeys. + */ + minoff = 0; + if (keys->n_minkeys > 0) + { + memset(&arg, 0, sizeof(PartitionBoundCmpArg)); + arg.datums = keys->minkeys; + arg.ndatums = keys->n_minkeys; + minoff = partition_bound_bsearch(partkey, boundinfo, &arg, &is_equal); + + if (minoff >= 0) + { + /* + * The bound at minoff is <= minkeys, given the way + * partition_bound_bsearch() works. If it's not equal (<), then + * increment minoff to make it point to the datum on the right + * that necessarily satisfies minkeys. Also do the same if it is + * equal but minkeys is exclusive. + */ + if (!is_equal || !keys->min_incl) + minoff++; + } + else + { + /* + * minoff set to -1 means all datums are greater than minkeys, + * which means all partitions satisfy minkeys. In that case, set + * minoff to the index of the leftmost datum, viz. 0. + */ + minoff = 0; + } + + /* + * minkeys is greater than the datums of all non-default partitions, + * meaning there isn't one to return. Return the default partition if + * one exists. + */ + if (minoff > boundinfo->ndatums - 1) + return partition_bound_has_default(boundinfo) + ? bms_make_singleton(boundinfo->default_index) + : NULL; + } + + /* + * Find the right-most bound that satisfies the query, i.e., the one that + * satisfies maxkeys. + */ + maxoff = boundinfo->ndatums - 1; + if (keys->n_maxkeys > 0) + { + memset(&arg, 0, sizeof(PartitionBoundCmpArg)); + arg.datums = keys->maxkeys; + arg.ndatums = keys->n_maxkeys; + maxoff = partition_bound_bsearch(partkey, boundinfo, &arg, &is_equal); + + if (maxoff >= 0) + { + /* + * The bound at maxoff is <= maxkeys, given the way + * partition_bound_bsearch works. If the bound at maxoff exactly + * matches maxkey (is_equal), but the maxkey is exclusive, then + * decrement maxoff to point to the bound on the left. + */ + if (is_equal && !keys->max_incl) + maxoff--; + } + + /* + * maxkeys is smaller than the datums of all non-default partitions, + * meaning there isn't one to return. Return the default partition if + * one exists. + */ + if (maxoff < 0) + return partition_bound_has_default(boundinfo) + ? bms_make_singleton(boundinfo->default_index) + : NULL; + } + + Assert (minoff >= 0 && maxoff >= 0); + + /* + * All datums between those at minoff and maxoff satisfy query's keys, so + * add the corresponding partitions to the result set. + */ + for (i = minoff; i <= maxoff; i++) + result = bms_add_member(result, boundinfo->indexes[i]); + + /* + * For range queries, always include the default list partition, + * because list partitions divide the key space in a discontinuous + * manner, not all values in the given range will have a partition + * assigned. + */ + if (partition_bound_has_default(boundinfo)) + return bms_add_member(result, boundinfo->default_index); + + return result; +} + +/* + * get_partitions_for_keys_range + * Return partitions of a range partitioned table for requested keys + * + * This interprets the keys and looks up partitions in the partition bound + * descriptor using the range partitioning semantics. + */ +static Bitmapset * +get_partitions_for_keys_range(Relation rel, PartScanKeyInfo *keys) +{ + Bitmapset *result = NULL; + PartitionKey partkey = RelationGetPartitionKey(rel); + PartitionBoundInfo boundinfo = RelationGetPartitionDesc(rel)->boundinfo; + PartitionBoundCmpArg arg; + int i, + eqoff, + minoff, + maxoff; + bool is_equal, + include_def = false; + + Assert(RelationGetPartitionDesc(rel)->nparts > 0); + + /* + * We might be able to get the answer sooner based on the nullness of + * keys, so get that out of the way. + */ + for (i = 0; i < partkey->partnatts; i++) + { + if (bms_is_member(i, keys->keyisnull)) + { + /* Only the default partition accepts nulls. */ + if (partition_bound_has_default(boundinfo)) + return bms_make_singleton(boundinfo->default_index); + else + return NULL; + } + } + + /* + * If there are no datums to compare keys with, but there are partitions, + * just return the default partition, if one exists. + */ + if (boundinfo->ndatums == 0) + { + if (partition_bound_has_default(boundinfo)) + return bms_make_singleton(boundinfo->default_index); + else + return NULL; + } + + /* Equality keys. */ + if (keys->n_eqkeys > 0) + { + /* Valid iff there are as many as partition key columns. */ + Assert(keys->n_eqkeys == partkey->partnatts); + memset(&arg, 0, sizeof(PartitionBoundCmpArg)); + arg.datums = keys->eqkeys; + arg.ndatums = keys->n_eqkeys; + eqoff = partition_bound_bsearch(partkey, boundinfo, &arg, &is_equal); + + /* + * The bound at eqoff is known to be <= eqkeys, given the way + * partition_bound_bsearch works. Considering it as the lower bound + * of the partition that eqkeys falls into, the bound at eqoff + 1 + * would be its upper bound, so use eqoff + 1 to get the desired + * partition's index. + */ + if (eqoff >= 0 && boundinfo->indexes[eqoff + 1] >= 0) + return bms_make_singleton(boundinfo->indexes[eqoff+1]); + /* + * eqkeys falls into a range of values for which no non-default + * partition exists. + */ + else if (partition_bound_has_default(boundinfo)) + return bms_make_singleton(boundinfo->default_index); + else + return NULL; + } + + /* + * Find the leftmost bound that satisfies the query, that is, make minoff + * point to the datum corresponding to the upper bound of the left-most + * partition to be selected. + */ + minoff = 0; + if (keys->n_minkeys > 0) + { + memset(&arg, 0, sizeof(PartitionBoundCmpArg)); + arg.datums = keys->minkeys; + arg.ndatums = keys->n_minkeys; + minoff = partition_bound_bsearch(partkey, boundinfo, &arg, &is_equal); + + /* + * If minkeys does not contain values for all partition key columns, + * that is, only a prefix is specified, then there may be multiple + * bounds in boundinfo that share the same prefix. But + * partition_bound_bsearch would've returned the offset of just one of + * those. If minkey is inclusive, we must decrement minoff until it + * reaches the leftmost of those bound values, so that partitions + * corresponding to all those bound values are selected. If minkeys + * is exclusive, we must increment minoff until it reaches the first + * bound greater than this prefix, so that none of the partitions + * corresponding to those bound values are selected. + */ + if (is_equal && arg.ndatums < partkey->partnatts) + { + while (minoff >= 1 && minoff < boundinfo->ndatums - 1) + { + int32 cmpval; + + cmpval = partition_bound_cmp(partkey, boundinfo, + keys->min_incl + ? minoff - 1 : minoff + 1, + &arg); + if (cmpval != 0) + { + /* Move to the non-equal bound only in this case. */ + if (!keys->min_incl) + minoff += 1; + break; + } + + if (keys->min_incl) + minoff -= 1; + else + minoff += 1; + } + } + /* + * Assuming minoff currently points to the lower bound of the left- + * most selected partition, increment it so that it points to the + * upper bound. + */ + else + minoff += 1; + } + + /* + * Find the rightmost bound that satisfies the query, that is, make maxoff + * maxoff point to the datum corresponding to the upper bound of the + * right-most partition to be selected. + */ + maxoff = boundinfo->ndatums; + if (keys->n_maxkeys > 0) + { + memset(&arg, 0, sizeof(PartitionBoundCmpArg)); + arg.datums = keys->maxkeys; + arg.ndatums = keys->n_maxkeys; + maxoff = partition_bound_bsearch(partkey, boundinfo, &arg, &is_equal); + + /* See the comment written above for minkeys. */ + if (is_equal && arg.ndatums < partkey->partnatts) + { + while (maxoff >= 1 && maxoff < boundinfo->ndatums - 1) + { + int32 cmpval; + + cmpval = partition_bound_cmp(partkey, boundinfo, + keys->max_incl + ? maxoff + 1 : maxoff - 1, + &arg); + if (cmpval != 0) + { + /* Move to the non-equal bound only in this case. */ + if (!keys->max_incl) + maxoff -= 1; + break; + } + + if (keys->max_incl) + maxoff += 1; + else + maxoff -= 1; + } + + /* + * Assuming maxoff currently points to the lower bound of the + * right-most partition, increment it so that it points to the + * upper bound. + */ + maxoff += 1; + } + /* + * Assuming maxoff currently points to the lower bound of the right- + * most selected partition, increment it so that it points to the + * upper bound. We do not need to include that partition though if + * maxkeys exactly matched the bound in question and it is exclusive. + * Not incrementing simply means we treat the matched bound itself + * the upper bound of the right-most selected partition. + */ + else if (!is_equal || keys->max_incl) + maxoff += 1; + } + + Assert (minoff >= 0 && maxoff >= 0); + + /* + * At this point, we believe that minoff/maxoff point to the upper bound + * of some partition, but it may not be the case. It might actually be + * the upper bound of an unassigned range of values, which if so, move + * minoff/maxoff to the adjacent bound which must be the upper bound of + * a valid partition. + * + * By doing that, we skip over a portion of values that do indeed satisfy + * the query, but don't have a valid partition assigned. The default + * partition will have to be included to cover those values. Although, if + * the original bound in question contains an infinite value, there would + * not be any unassigned range to speak of, because the range us unbounded + * in that direction by definition, so no need to include the default. + */ + if (boundinfo->indexes[minoff] < 0) + { + int lastkey = partkey->partnatts - 1; + + if (keys->n_minkeys > 0) + lastkey = keys->n_minkeys - 1; + if (minoff >=0 && minoff < boundinfo->ndatums && + boundinfo->kind[minoff][lastkey] == PARTITION_RANGE_DATUM_VALUE) + include_def = true; + minoff += 1; + } + + if (maxoff >= 1 && boundinfo->indexes[maxoff] < 0) + { + int lastkey = partkey->partnatts - 1; + + if (keys->n_maxkeys > 0) + lastkey = keys->n_maxkeys - 1; + if (maxoff >=0 && maxoff <= boundinfo->ndatums && + boundinfo->kind[maxoff - 1][lastkey] == PARTITION_RANGE_DATUM_VALUE) + include_def = true; + maxoff -= 1; + } + + if (minoff <= maxoff) + result = bms_add_range(result, + boundinfo->indexes[minoff], + boundinfo->indexes[maxoff]); + /* + * There may exist a range of values unassigned to any non-default + * partition between the datums at minoff and maxoff. + */ + for (i = minoff; i <= maxoff; i++) + { + if (boundinfo->indexes[i] < 0) + { + include_def = true; + break; + } + } + + /* + * Since partition keys with nulls are mapped to the default range + * partition, we must include the default partition if some keys + * could be null. + */ + if (keys->n_minkeys < partkey->partnatts || + keys->n_maxkeys < partkey->partnatts) + { + for (i = 0; i < partkey->partnatts; i++) + { + if (!bms_is_member(i, keys->keyisnotnull)) + { + include_def = true; + break; + } + } + } + + if (include_def && partition_bound_has_default(boundinfo)) + result = bms_add_member(result, boundinfo->default_index); + + return result; +} + +/* * get_partition_operator * * Return oid of the operator of given strategy for a given partition key diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 89f27ce0eb..0c1f23951a 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -152,8 +152,6 @@ static Node *substitute_actual_parameters(Node *expr, int nargs, List *args, static Node *substitute_actual_parameters_mutator(Node *node, substitute_actual_parameters_context *context); static void sql_inline_error_callback(void *arg); -static Expr *evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod, - Oid result_collation); static Query *substitute_actual_srf_parameters(Query *expr, int nargs, List *args); static Node *substitute_actual_srf_parameters_mutator(Node *node, @@ -4833,7 +4831,7 @@ sql_inline_error_callback(void *arg) * We use the executor's routine ExecEvalExpr() to avoid duplication of * code and ensure we get the same result as the executor would get. */ -static Expr * +Expr * evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod, Oid result_collation) { diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h index 2faf0ca26e..8423c6e886 100644 --- a/src/include/catalog/partition.h +++ b/src/include/catalog/partition.h @@ -73,4 +73,7 @@ extern List *get_proposed_default_constraint(List *new_part_constaints); extern int get_partition_for_tuple(Relation relation, Datum *values, bool *isnull); +/* For partition-pruning */ +extern Bitmapset *get_partitions_from_clauses(Relation relation, int rt_index, + List *partclauses); #endif /* PARTITION_H */ diff --git a/src/include/catalog/pg_opfamily.h b/src/include/catalog/pg_opfamily.h index b544474254..0847df97ff 100644 --- a/src/include/catalog/pg_opfamily.h +++ b/src/include/catalog/pg_opfamily.h @@ -188,4 +188,7 @@ DATA(insert OID = 4104 ( 3580 box_inclusion_ops PGNSP PGUID )); DATA(insert OID = 5000 ( 4000 box_ops PGNSP PGUID )); DATA(insert OID = 5008 ( 4000 poly_ops PGNSP PGUID )); +#define IsBooleanOpfamily(opfamily) \ + ((opfamily) == BOOL_BTREE_FAM_OID || (opfamily) == BOOL_HASH_FAM_OID) + #endif /* PG_OPFAMILY_H */ diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h index ba4fa4b68b..3c2f54964b 100644 --- a/src/include/optimizer/clauses.h +++ b/src/include/optimizer/clauses.h @@ -84,5 +84,7 @@ extern Node *estimate_expression_value(PlannerInfo *root, Node *node); extern Query *inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte); +extern Expr *evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod, + Oid result_collation); #endif /* CLAUSES_H */ -- 2.11.0