|
|
|
@ -118,10 +118,18 @@ static int32 qsort_partition_list_value_cmp(const void *a, const void *b, |
|
|
|
|
static int32 qsort_partition_rbound_cmp(const void *a, const void *b, |
|
|
|
|
void *arg); |
|
|
|
|
|
|
|
|
|
static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec); |
|
|
|
|
static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec); |
|
|
|
|
static Oid get_partition_operator(PartitionKey key, int col, |
|
|
|
|
StrategyNumber strategy, bool *need_relabel); |
|
|
|
|
static Expr *make_partition_op_expr(PartitionKey key, int keynum, |
|
|
|
|
uint16 strategy, Expr *arg1, Expr *arg2); |
|
|
|
|
static void get_range_key_properties(PartitionKey key, int keynum, |
|
|
|
|
PartitionRangeDatum *ldatum, |
|
|
|
|
PartitionRangeDatum *udatum, |
|
|
|
|
ListCell **partexprs_item, |
|
|
|
|
Expr **keyCol, |
|
|
|
|
Const **lower_val, Const **upper_val); |
|
|
|
|
static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec); |
|
|
|
|
static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec); |
|
|
|
|
static List *generate_partition_qual(Relation rel); |
|
|
|
|
|
|
|
|
|
static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index, |
|
|
|
@ -1145,6 +1153,123 @@ RelationGetPartitionDispatchInfo(Relation rel, int lockmode, |
|
|
|
|
|
|
|
|
|
/* Module-local functions */ |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* get_partition_operator |
|
|
|
|
* |
|
|
|
|
* Return oid of the operator of given strategy for a given partition key |
|
|
|
|
* column. |
|
|
|
|
*/ |
|
|
|
|
static Oid |
|
|
|
|
get_partition_operator(PartitionKey key, int col, StrategyNumber strategy, |
|
|
|
|
bool *need_relabel) |
|
|
|
|
{ |
|
|
|
|
Oid operoid; |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* First check if there exists an operator of the given strategy, with |
|
|
|
|
* this column's type as both its lefttype and righttype, in the |
|
|
|
|
* partitioning operator family specified for the column. |
|
|
|
|
*/ |
|
|
|
|
operoid = get_opfamily_member(key->partopfamily[col], |
|
|
|
|
key->parttypid[col], |
|
|
|
|
key->parttypid[col], |
|
|
|
|
strategy); |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* If one doesn't exist, we must resort to using an operator in the same |
|
|
|
|
* opreator family but with the operator class declared input type. It is |
|
|
|
|
* OK to do so, because the column's type is known to be binary-coercible |
|
|
|
|
* with the operator class input type (otherwise, the operator class in |
|
|
|
|
* question would not have been accepted as the partitioning operator |
|
|
|
|
* class). We must however inform the caller to wrap the non-Const |
|
|
|
|
* expression with a RelabelType node to denote the implicit coercion. It |
|
|
|
|
* ensures that the resulting expression structurally matches similarly |
|
|
|
|
* processed expressions within the optimizer. |
|
|
|
|
*/ |
|
|
|
|
if (!OidIsValid(operoid)) |
|
|
|
|
{ |
|
|
|
|
operoid = get_opfamily_member(key->partopfamily[col], |
|
|
|
|
key->partopcintype[col], |
|
|
|
|
key->partopcintype[col], |
|
|
|
|
strategy); |
|
|
|
|
*need_relabel = true; |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
*need_relabel = false; |
|
|
|
|
|
|
|
|
|
if (!OidIsValid(operoid)) |
|
|
|
|
elog(ERROR, "could not find operator for partitioning"); |
|
|
|
|
|
|
|
|
|
return operoid; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* make_partition_op_expr |
|
|
|
|
* Returns an Expr for the given partition key column with arg1 and |
|
|
|
|
* arg2 as its leftop and rightop, respectively |
|
|
|
|
*/ |
|
|
|
|
static Expr * |
|
|
|
|
make_partition_op_expr(PartitionKey key, int keynum, |
|
|
|
|
uint16 strategy, Expr *arg1, Expr *arg2) |
|
|
|
|
{ |
|
|
|
|
Oid operoid; |
|
|
|
|
bool need_relabel = false; |
|
|
|
|
Expr *result = NULL; |
|
|
|
|
|
|
|
|
|
/* Get the correct btree operator for this partitioning column */ |
|
|
|
|
operoid = get_partition_operator(key, keynum, strategy, &need_relabel); |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Chosen operator may be such that the non-Const operand needs to be |
|
|
|
|
* coerced, so apply the same; see the comment in |
|
|
|
|
* get_partition_operator(). |
|
|
|
|
*/ |
|
|
|
|
if (!IsA(arg1, Const) && |
|
|
|
|
(need_relabel || |
|
|
|
|
key->partcollation[keynum] != key->parttypcoll[keynum])) |
|
|
|
|
arg1 = (Expr *) makeRelabelType(arg1, |
|
|
|
|
key->partopcintype[keynum], |
|
|
|
|
-1, |
|
|
|
|
key->partcollation[keynum], |
|
|
|
|
COERCE_EXPLICIT_CAST); |
|
|
|
|
|
|
|
|
|
/* Generate the actual expression */ |
|
|
|
|
switch (key->strategy) |
|
|
|
|
{ |
|
|
|
|
case PARTITION_STRATEGY_LIST: |
|
|
|
|
{ |
|
|
|
|
ScalarArrayOpExpr *saopexpr; |
|
|
|
|
|
|
|
|
|
/* Build leftop = ANY (rightop) */ |
|
|
|
|
saopexpr = makeNode(ScalarArrayOpExpr); |
|
|
|
|
saopexpr->opno = operoid; |
|
|
|
|
saopexpr->opfuncid = get_opcode(operoid); |
|
|
|
|
saopexpr->useOr = true; |
|
|
|
|
saopexpr->inputcollid = key->partcollation[0]; |
|
|
|
|
saopexpr->args = list_make2(arg1, arg2); |
|
|
|
|
saopexpr->location = -1; |
|
|
|
|
|
|
|
|
|
result = (Expr *) saopexpr; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
case PARTITION_STRATEGY_RANGE: |
|
|
|
|
result = make_opclause(operoid, |
|
|
|
|
BOOLOID, |
|
|
|
|
false, |
|
|
|
|
arg1, arg2, |
|
|
|
|
InvalidOid, |
|
|
|
|
key->partcollation[keynum]); |
|
|
|
|
break; |
|
|
|
|
|
|
|
|
|
default: |
|
|
|
|
elog(ERROR, "invalid partitioning strategy"); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return result; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* get_qual_for_list |
|
|
|
|
* |
|
|
|
@ -1155,14 +1280,12 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec) |
|
|
|
|
{ |
|
|
|
|
List *result; |
|
|
|
|
ArrayExpr *arr; |
|
|
|
|
ScalarArrayOpExpr *opexpr; |
|
|
|
|
Expr *opexpr; |
|
|
|
|
ListCell *cell, |
|
|
|
|
*prev, |
|
|
|
|
*next; |
|
|
|
|
Expr *keyCol; |
|
|
|
|
Oid operoid; |
|
|
|
|
bool need_relabel, |
|
|
|
|
list_has_null = false; |
|
|
|
|
bool list_has_null = false; |
|
|
|
|
NullTest *nulltest1 = NULL, |
|
|
|
|
*nulltest2 = NULL; |
|
|
|
|
|
|
|
|
@ -1233,24 +1356,9 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec) |
|
|
|
|
arr->multidims = false; |
|
|
|
|
arr->location = -1; |
|
|
|
|
|
|
|
|
|
/* Get the correct btree equality operator */ |
|
|
|
|
operoid = get_partition_operator(key, 0, BTEqualStrategyNumber, |
|
|
|
|
&need_relabel); |
|
|
|
|
if (need_relabel || key->partcollation[0] != key->parttypcoll[0]) |
|
|
|
|
keyCol = (Expr *) makeRelabelType(keyCol, |
|
|
|
|
key->partopcintype[0], |
|
|
|
|
-1, |
|
|
|
|
key->partcollation[0], |
|
|
|
|
COERCE_EXPLICIT_CAST); |
|
|
|
|
|
|
|
|
|
/* Build leftop = ANY (rightop) */ |
|
|
|
|
opexpr = makeNode(ScalarArrayOpExpr); |
|
|
|
|
opexpr->opno = operoid; |
|
|
|
|
opexpr->opfuncid = get_opcode(operoid); |
|
|
|
|
opexpr->useOr = true; |
|
|
|
|
opexpr->inputcollid = key->partcollation[0]; |
|
|
|
|
opexpr->args = list_make2(keyCol, arr); |
|
|
|
|
opexpr->location = -1; |
|
|
|
|
/* Generate the main expression, i.e., keyCol = ANY (arr) */ |
|
|
|
|
opexpr = make_partition_op_expr(key, 0, BTEqualStrategyNumber, |
|
|
|
|
keyCol, (Expr *) arr); |
|
|
|
|
|
|
|
|
|
if (nulltest1) |
|
|
|
|
result = list_make2(nulltest1, opexpr); |
|
|
|
@ -1267,10 +1375,91 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec) |
|
|
|
|
return result; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* get_range_key_properties |
|
|
|
|
* Returns range partition key information for a given column |
|
|
|
|
* |
|
|
|
|
* On return, *partexprs_item points to the cell containing the next |
|
|
|
|
* expression in the key->partexprs list, or NULL. |
|
|
|
|
*/ |
|
|
|
|
static void |
|
|
|
|
get_range_key_properties(PartitionKey key, int keynum, |
|
|
|
|
PartitionRangeDatum *ldatum, |
|
|
|
|
PartitionRangeDatum *udatum, |
|
|
|
|
ListCell **partexprs_item, |
|
|
|
|
Expr **keyCol, |
|
|
|
|
Const **lower_val, Const **upper_val) |
|
|
|
|
{ |
|
|
|
|
/* Partition key expression for this column */ |
|
|
|
|
if (key->partattrs[keynum] != 0) |
|
|
|
|
{ |
|
|
|
|
*keyCol = (Expr *) makeVar(1, |
|
|
|
|
key->partattrs[keynum], |
|
|
|
|
key->parttypid[keynum], |
|
|
|
|
key->parttypmod[keynum], |
|
|
|
|
key->parttypcoll[keynum], |
|
|
|
|
0); |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
{ |
|
|
|
|
*keyCol = copyObject(lfirst(*partexprs_item)); |
|
|
|
|
*partexprs_item = lnext(*partexprs_item); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (!ldatum->infinite) |
|
|
|
|
*lower_val = (Const *) ldatum->value; |
|
|
|
|
else |
|
|
|
|
*lower_val = NULL; |
|
|
|
|
|
|
|
|
|
if (!udatum->infinite) |
|
|
|
|
*upper_val = (Const *) udatum->value; |
|
|
|
|
else |
|
|
|
|
*upper_val = NULL; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* get_qual_for_range |
|
|
|
|
* |
|
|
|
|
* Get a list of OpExpr's to use as a range partition's constraint. |
|
|
|
|
* Get a list of expressions to use as a range partition's constraint. |
|
|
|
|
* If there are multiple expressions, they are to be considered implicitly |
|
|
|
|
* ANDed. |
|
|
|
|
* |
|
|
|
|
* For a multi-column range partition key, say (a, b, c), with (al, bl, cl) |
|
|
|
|
* as the lower bound tuple and (au, bu, cu) as the upper bound tuple, we |
|
|
|
|
* generate an expression tree of the following form: |
|
|
|
|
* |
|
|
|
|
* (a > al OR (a = al AND b > bl) OR (a = al AND b = bl AND c >= cl)) |
|
|
|
|
* AND |
|
|
|
|
* (a < au OR (a = au AND b < bu) OR (a = au AND b = bu AND c < cu)) |
|
|
|
|
* |
|
|
|
|
* If, say, b were an expression key instead of a simple column, we also |
|
|
|
|
* append (b IS NOT NULL) to the AND's argument list. |
|
|
|
|
* |
|
|
|
|
* It is often the case that a prefix of lower and upper bound tuples contains |
|
|
|
|
* the same values, for example, (al = au), in which case, we will emit an |
|
|
|
|
* expression tree of the following form: |
|
|
|
|
* |
|
|
|
|
* (a = al) |
|
|
|
|
* AND |
|
|
|
|
* (b > bl OR (b = bl AND c >= cl)) |
|
|
|
|
* AND |
|
|
|
|
* (b < bu) OR (b = bu AND c < cu)) |
|
|
|
|
* |
|
|
|
|
* If cu happens to be UNBOUNDED, we need not emit any expression for it, so |
|
|
|
|
* the last line would be: |
|
|
|
|
* |
|
|
|
|
* (b < bu) OR (b = bu), which is simplified to (b <= bu) |
|
|
|
|
* |
|
|
|
|
* In most common cases with only one partition column, say a, the following |
|
|
|
|
* expression tree will be generated: a >= al AND a < au |
|
|
|
|
* |
|
|
|
|
* If all values of both lower and upper bounds are UNBOUNDED, the partition |
|
|
|
|
* does not really have a constraint, except the IS NOT NULL constraint for |
|
|
|
|
* any expression keys. |
|
|
|
|
* |
|
|
|
|
* If we end up with an empty result list, we append return a single-member |
|
|
|
|
* list containing a constant-true expression in that case, because callers |
|
|
|
|
* expect a non-empty list. |
|
|
|
|
*/ |
|
|
|
|
static List * |
|
|
|
|
get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec) |
|
|
|
@ -1278,56 +1467,49 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec) |
|
|
|
|
List *result = NIL; |
|
|
|
|
ListCell *cell1, |
|
|
|
|
*cell2, |
|
|
|
|
*partexprs_item; |
|
|
|
|
int i; |
|
|
|
|
*partexprs_item, |
|
|
|
|
*partexprs_item_saved; |
|
|
|
|
int i, |
|
|
|
|
j; |
|
|
|
|
PartitionRangeDatum *ldatum, |
|
|
|
|
*udatum; |
|
|
|
|
Expr *keyCol; |
|
|
|
|
Const *lower_val, |
|
|
|
|
*upper_val; |
|
|
|
|
NullTest *nulltest; |
|
|
|
|
List *lower_or_arms, |
|
|
|
|
*upper_or_arms; |
|
|
|
|
int num_or_arms, |
|
|
|
|
current_or_arm; |
|
|
|
|
ListCell *lower_or_start_datum, |
|
|
|
|
*upper_or_start_datum; |
|
|
|
|
bool need_next_lower_arm, |
|
|
|
|
need_next_upper_arm; |
|
|
|
|
|
|
|
|
|
lower_or_start_datum = list_head(spec->lowerdatums); |
|
|
|
|
upper_or_start_datum = list_head(spec->upperdatums); |
|
|
|
|
num_or_arms = key->partnatts; |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Iterate over columns of the key, emitting an OpExpr for each using the |
|
|
|
|
* corresponding lower and upper datums as constant operands. |
|
|
|
|
* A range-partitioned table does not allow partition keys to be null. For |
|
|
|
|
* simple columns, their NOT NULL constraint suffices for the enforcement |
|
|
|
|
* of non-nullability. But for the expression keys, which are still |
|
|
|
|
* nullable, we must emit a IS NOT NULL expression. Collect them in |
|
|
|
|
* result first. |
|
|
|
|
*/ |
|
|
|
|
i = 0; |
|
|
|
|
partexprs_item = list_head(key->partexprs); |
|
|
|
|
forboth(cell1, spec->lowerdatums, cell2, spec->upperdatums) |
|
|
|
|
for (i = 0; i < key->partnatts; i++) |
|
|
|
|
{ |
|
|
|
|
PartitionRangeDatum *ldatum = lfirst(cell1), |
|
|
|
|
*udatum = lfirst(cell2); |
|
|
|
|
Expr *keyCol; |
|
|
|
|
Const *lower_val = NULL, |
|
|
|
|
*upper_val = NULL; |
|
|
|
|
EState *estate; |
|
|
|
|
MemoryContext oldcxt; |
|
|
|
|
Expr *test_expr; |
|
|
|
|
ExprState *test_exprstate; |
|
|
|
|
Datum test_result; |
|
|
|
|
bool isNull; |
|
|
|
|
bool need_relabel = false; |
|
|
|
|
Oid operoid; |
|
|
|
|
NullTest *nulltest; |
|
|
|
|
|
|
|
|
|
/* Left operand */ |
|
|
|
|
if (key->partattrs[i] != 0) |
|
|
|
|
{ |
|
|
|
|
keyCol = (Expr *) makeVar(1, |
|
|
|
|
key->partattrs[i], |
|
|
|
|
key->parttypid[i], |
|
|
|
|
key->parttypmod[i], |
|
|
|
|
key->parttypcoll[i], |
|
|
|
|
0); |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
if (key->partattrs[i] == 0) |
|
|
|
|
{ |
|
|
|
|
keyCol = copyObject(lfirst(partexprs_item)); |
|
|
|
|
Expr *keyCol; |
|
|
|
|
|
|
|
|
|
if (partexprs_item == NULL) |
|
|
|
|
elog(ERROR, "wrong number of partition key expressions"); |
|
|
|
|
keyCol = lfirst(partexprs_item); |
|
|
|
|
partexprs_item = lnext(partexprs_item); |
|
|
|
|
} |
|
|
|
|
Assert(!IsA(keyCol, Var)); |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Emit a IS NOT NULL expression for non-Var keys, because whereas |
|
|
|
|
* simple attributes are covered by NOT NULL constraints, expression |
|
|
|
|
* keys are still nullable which is not acceptable in case of range |
|
|
|
|
* partitioning. |
|
|
|
|
*/ |
|
|
|
|
if (!IsA(keyCol, Var)) |
|
|
|
|
{ |
|
|
|
|
nulltest = makeNode(NullTest); |
|
|
|
|
nulltest->arg = keyCol; |
|
|
|
|
nulltest->nulltesttype = IS_NOT_NULL; |
|
|
|
@ -1335,182 +1517,221 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec) |
|
|
|
|
nulltest->location = -1; |
|
|
|
|
result = lappend(result, nulltest); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Iterate over the key columns and check if the corresponding lower and |
|
|
|
|
* upper datums are equal using the btree equality operator for the |
|
|
|
|
* column's type. If equal, we emit single keyCol = common_value |
|
|
|
|
* expression. Starting from the first column for which the corresponding |
|
|
|
|
* lower and upper bound datums are not equal, we generate OR expressions |
|
|
|
|
* as shown in the function's header comment. |
|
|
|
|
*/ |
|
|
|
|
i = 0; |
|
|
|
|
partexprs_item = list_head(key->partexprs); |
|
|
|
|
forboth(cell1, spec->lowerdatums, cell2, spec->upperdatums) |
|
|
|
|
{ |
|
|
|
|
EState *estate; |
|
|
|
|
MemoryContext oldcxt; |
|
|
|
|
Expr *test_expr; |
|
|
|
|
ExprState *test_exprstate; |
|
|
|
|
Datum test_result; |
|
|
|
|
bool isNull; |
|
|
|
|
|
|
|
|
|
ldatum = lfirst(cell1); |
|
|
|
|
udatum = lfirst(cell2); |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Stop at this column if either of lower or upper datum is infinite, |
|
|
|
|
* but do emit an OpExpr for the non-infinite datum. |
|
|
|
|
* Since get_range_key_properties() modifies partexprs_item, and we |
|
|
|
|
* might need to start over from the previous expression in the later |
|
|
|
|
* part of this functiom, save away the current value. |
|
|
|
|
*/ |
|
|
|
|
if (!ldatum->infinite) |
|
|
|
|
lower_val = (Const *) ldatum->value; |
|
|
|
|
if (!udatum->infinite) |
|
|
|
|
upper_val = (Const *) udatum->value; |
|
|
|
|
partexprs_item_saved = partexprs_item; |
|
|
|
|
|
|
|
|
|
get_range_key_properties(key, i, ldatum, udatum, |
|
|
|
|
&partexprs_item, |
|
|
|
|
&keyCol, |
|
|
|
|
&lower_val, &upper_val); |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* If lower_val and upper_val are both finite and happen to be equal, |
|
|
|
|
* emit only (keyCol = lower_val) for this column, because all rows in |
|
|
|
|
* this partition could only ever contain this value (ie, lower_val) |
|
|
|
|
* in the current partitioning column. We must consider further |
|
|
|
|
* columns because the above condition does not fully constrain the |
|
|
|
|
* rows of this partition. |
|
|
|
|
* If either or both of lower_val and upper_val is NULL, they are |
|
|
|
|
* unequal, because being NULL means the column is unbounded in the |
|
|
|
|
* respective direction. |
|
|
|
|
*/ |
|
|
|
|
if (lower_val && upper_val) |
|
|
|
|
{ |
|
|
|
|
/* Get the correct btree equality operator for the test */ |
|
|
|
|
operoid = get_partition_operator(key, i, BTEqualStrategyNumber, |
|
|
|
|
&need_relabel); |
|
|
|
|
|
|
|
|
|
/* Create the test expression */ |
|
|
|
|
estate = CreateExecutorState(); |
|
|
|
|
oldcxt = MemoryContextSwitchTo(estate->es_query_cxt); |
|
|
|
|
test_expr = make_opclause(operoid, |
|
|
|
|
BOOLOID, |
|
|
|
|
false, |
|
|
|
|
(Expr *) lower_val, |
|
|
|
|
(Expr *) upper_val, |
|
|
|
|
InvalidOid, |
|
|
|
|
key->partcollation[i]); |
|
|
|
|
fix_opfuncids((Node *) test_expr); |
|
|
|
|
test_exprstate = ExecInitExpr(test_expr, NULL); |
|
|
|
|
test_result = ExecEvalExprSwitchContext(test_exprstate, |
|
|
|
|
if (!lower_val || !upper_val) |
|
|
|
|
break; |
|
|
|
|
|
|
|
|
|
/* Create the test expression */ |
|
|
|
|
estate = CreateExecutorState(); |
|
|
|
|
oldcxt = MemoryContextSwitchTo(estate->es_query_cxt); |
|
|
|
|
test_expr = make_partition_op_expr(key, i, BTEqualStrategyNumber, |
|
|
|
|
(Expr *) lower_val, |
|
|
|
|
(Expr *) upper_val); |
|
|
|
|
fix_opfuncids((Node *) test_expr); |
|
|
|
|
test_exprstate = ExecInitExpr(test_expr, NULL); |
|
|
|
|
test_result = ExecEvalExprSwitchContext(test_exprstate, |
|
|
|
|
GetPerTupleExprContext(estate), |
|
|
|
|
&isNull); |
|
|
|
|
MemoryContextSwitchTo(oldcxt); |
|
|
|
|
FreeExecutorState(estate); |
|
|
|
|
&isNull); |
|
|
|
|
MemoryContextSwitchTo(oldcxt); |
|
|
|
|
FreeExecutorState(estate); |
|
|
|
|
|
|
|
|
|
if (DatumGetBool(test_result)) |
|
|
|
|
{ |
|
|
|
|
/* This can never be, but it's better to make sure */ |
|
|
|
|
if (i == key->partnatts - 1) |
|
|
|
|
elog(ERROR, "invalid range bound specification"); |
|
|
|
|
|
|
|
|
|
if (need_relabel || key->partcollation[i] != key->parttypcoll[i]) |
|
|
|
|
keyCol = (Expr *) makeRelabelType(keyCol, |
|
|
|
|
key->partopcintype[i], |
|
|
|
|
-1, |
|
|
|
|
key->partcollation[i], |
|
|
|
|
COERCE_EXPLICIT_CAST); |
|
|
|
|
result = lappend(result, |
|
|
|
|
make_opclause(operoid, |
|
|
|
|
BOOLOID, |
|
|
|
|
false, |
|
|
|
|
keyCol, |
|
|
|
|
(Expr *) lower_val, |
|
|
|
|
InvalidOid, |
|
|
|
|
key->partcollation[i])); |
|
|
|
|
|
|
|
|
|
/* Go over to consider the next column. */ |
|
|
|
|
i++; |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
/* If not equal, go generate the OR expressions */ |
|
|
|
|
if (!DatumGetBool(test_result)) |
|
|
|
|
break; |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* We can say here that lower_val != upper_val. Emit expressions |
|
|
|
|
* (keyCol >= lower_val) and (keyCol < upper_val), then stop. |
|
|
|
|
* The bounds for the last key column can't be equal, because such a |
|
|
|
|
* range partition would never be allowed to be defined (it would have |
|
|
|
|
* an empty range otherwise). |
|
|
|
|
*/ |
|
|
|
|
if (lower_val) |
|
|
|
|
{ |
|
|
|
|
operoid = get_partition_operator(key, i, |
|
|
|
|
BTGreaterEqualStrategyNumber, |
|
|
|
|
&need_relabel); |
|
|
|
|
|
|
|
|
|
if (need_relabel || key->partcollation[i] != key->parttypcoll[i]) |
|
|
|
|
keyCol = (Expr *) makeRelabelType(keyCol, |
|
|
|
|
key->partopcintype[i], |
|
|
|
|
-1, |
|
|
|
|
key->partcollation[i], |
|
|
|
|
COERCE_EXPLICIT_CAST); |
|
|
|
|
result = lappend(result, |
|
|
|
|
make_opclause(operoid, |
|
|
|
|
BOOLOID, |
|
|
|
|
false, |
|
|
|
|
keyCol, |
|
|
|
|
(Expr *) lower_val, |
|
|
|
|
InvalidOid, |
|
|
|
|
key->partcollation[i])); |
|
|
|
|
} |
|
|
|
|
if (i == key->partnatts - 1) |
|
|
|
|
elog(ERROR, "invalid range bound specification"); |
|
|
|
|
|
|
|
|
|
if (upper_val) |
|
|
|
|
/* Equal, so generate keyCol = lower_val expression */ |
|
|
|
|
result = lappend(result, |
|
|
|
|
make_partition_op_expr(key, i, BTEqualStrategyNumber, |
|
|
|
|
keyCol, (Expr *) lower_val)); |
|
|
|
|
|
|
|
|
|
i++; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* First pair of lower_val and upper_val that are not equal. */ |
|
|
|
|
lower_or_start_datum = cell1; |
|
|
|
|
upper_or_start_datum = cell2; |
|
|
|
|
|
|
|
|
|
/* OR will have as many arms as there are key columns left. */ |
|
|
|
|
num_or_arms = key->partnatts - i; |
|
|
|
|
current_or_arm = 0; |
|
|
|
|
lower_or_arms = upper_or_arms = NIL; |
|
|
|
|
need_next_lower_arm = need_next_upper_arm = true; |
|
|
|
|
while (current_or_arm < num_or_arms) |
|
|
|
|
{ |
|
|
|
|
List *lower_or_arm_args = NIL, |
|
|
|
|
*upper_or_arm_args = NIL; |
|
|
|
|
|
|
|
|
|
j = i; |
|
|
|
|
partexprs_item = partexprs_item_saved; |
|
|
|
|
|
|
|
|
|
for_both_cell(cell1, lower_or_start_datum, cell2, upper_or_start_datum) |
|
|
|
|
{ |
|
|
|
|
operoid = get_partition_operator(key, i, |
|
|
|
|
BTLessStrategyNumber, |
|
|
|
|
&need_relabel); |
|
|
|
|
|
|
|
|
|
if (need_relabel || key->partcollation[i] != key->parttypcoll[i]) |
|
|
|
|
keyCol = (Expr *) makeRelabelType(keyCol, |
|
|
|
|
key->partopcintype[i], |
|
|
|
|
-1, |
|
|
|
|
key->partcollation[i], |
|
|
|
|
COERCE_EXPLICIT_CAST); |
|
|
|
|
|
|
|
|
|
result = lappend(result, |
|
|
|
|
make_opclause(operoid, |
|
|
|
|
BOOLOID, |
|
|
|
|
false, |
|
|
|
|
keyCol, |
|
|
|
|
(Expr *) upper_val, |
|
|
|
|
InvalidOid, |
|
|
|
|
key->partcollation[i])); |
|
|
|
|
PartitionRangeDatum *ldatum_next = NULL, |
|
|
|
|
*udatum_next = NULL; |
|
|
|
|
|
|
|
|
|
ldatum = lfirst(cell1); |
|
|
|
|
if (lnext(cell1)) |
|
|
|
|
ldatum_next = lfirst(lnext(cell1)); |
|
|
|
|
udatum = lfirst(cell2); |
|
|
|
|
if (lnext(cell2)) |
|
|
|
|
udatum_next = lfirst(lnext(cell2)); |
|
|
|
|
get_range_key_properties(key, j, ldatum, udatum, |
|
|
|
|
&partexprs_item, |
|
|
|
|
&keyCol, |
|
|
|
|
&lower_val, &upper_val); |
|
|
|
|
|
|
|
|
|
if (need_next_lower_arm && lower_val) |
|
|
|
|
{ |
|
|
|
|
uint16 strategy; |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* For the non-last columns of this arm, use the EQ operator. |
|
|
|
|
* For the last or the last finite-valued column, use GE. |
|
|
|
|
*/ |
|
|
|
|
if (j - i < current_or_arm) |
|
|
|
|
strategy = BTEqualStrategyNumber; |
|
|
|
|
else if ((ldatum_next && ldatum_next->infinite) || |
|
|
|
|
j == key->partnatts - 1) |
|
|
|
|
strategy = BTGreaterEqualStrategyNumber; |
|
|
|
|
else |
|
|
|
|
strategy = BTGreaterStrategyNumber; |
|
|
|
|
|
|
|
|
|
lower_or_arm_args = lappend(lower_or_arm_args, |
|
|
|
|
make_partition_op_expr(key, j, |
|
|
|
|
strategy, |
|
|
|
|
keyCol, |
|
|
|
|
(Expr *) lower_val)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (need_next_upper_arm && upper_val) |
|
|
|
|
{ |
|
|
|
|
uint16 strategy; |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* For the non-last columns of this arm, use the EQ operator. |
|
|
|
|
* For the last finite-valued column, use LE. |
|
|
|
|
*/ |
|
|
|
|
if (j - i < current_or_arm) |
|
|
|
|
strategy = BTEqualStrategyNumber; |
|
|
|
|
else if (udatum_next && udatum_next->infinite) |
|
|
|
|
strategy = BTLessEqualStrategyNumber; |
|
|
|
|
else |
|
|
|
|
strategy = BTLessStrategyNumber; |
|
|
|
|
|
|
|
|
|
upper_or_arm_args = lappend(upper_or_arm_args, |
|
|
|
|
make_partition_op_expr(key, j, |
|
|
|
|
strategy, |
|
|
|
|
keyCol, |
|
|
|
|
(Expr *) upper_val)); |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Did we generate enough of OR's arguments? First arm considers |
|
|
|
|
* the first of the remaining columns, second arm considers first |
|
|
|
|
* two of the remaining columns, and so on. |
|
|
|
|
*/ |
|
|
|
|
++j; |
|
|
|
|
if (j - i > current_or_arm) |
|
|
|
|
{ |
|
|
|
|
/*
|
|
|
|
|
* We need not emit the next arm if the new column that will |
|
|
|
|
* be considered is unbounded. |
|
|
|
|
*/ |
|
|
|
|
need_next_lower_arm = ldatum_next && !ldatum_next->infinite; |
|
|
|
|
need_next_upper_arm = udatum_next && !udatum_next->infinite; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* We can stop at this column, because we would not have checked the |
|
|
|
|
* next column when routing a given row into this partition. |
|
|
|
|
*/ |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
if (lower_or_arm_args != NIL) |
|
|
|
|
lower_or_arms = lappend(lower_or_arms, |
|
|
|
|
list_length(lower_or_arm_args) > 1 |
|
|
|
|
? makeBoolExpr(AND_EXPR, lower_or_arm_args, -1) |
|
|
|
|
: linitial(lower_or_arm_args)); |
|
|
|
|
|
|
|
|
|
return result; |
|
|
|
|
} |
|
|
|
|
if (upper_or_arm_args != NIL) |
|
|
|
|
upper_or_arms = lappend(upper_or_arms, |
|
|
|
|
list_length(upper_or_arm_args) > 1 |
|
|
|
|
? makeBoolExpr(AND_EXPR, upper_or_arm_args, -1) |
|
|
|
|
: linitial(upper_or_arm_args)); |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* get_partition_operator |
|
|
|
|
* |
|
|
|
|
* Return oid of the operator of given strategy for a given partition key |
|
|
|
|
* column. |
|
|
|
|
*/ |
|
|
|
|
static Oid |
|
|
|
|
get_partition_operator(PartitionKey key, int col, StrategyNumber strategy, |
|
|
|
|
bool *need_relabel) |
|
|
|
|
{ |
|
|
|
|
Oid operoid; |
|
|
|
|
/* If no work to do in the next iteration, break away. */ |
|
|
|
|
if (!need_next_lower_arm && !need_next_upper_arm) |
|
|
|
|
break; |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* First check if there exists an operator of the given strategy, with |
|
|
|
|
* this column's type as both its lefttype and righttype, in the |
|
|
|
|
* partitioning operator family specified for the column. |
|
|
|
|
*/ |
|
|
|
|
operoid = get_opfamily_member(key->partopfamily[col], |
|
|
|
|
key->parttypid[col], |
|
|
|
|
key->parttypid[col], |
|
|
|
|
strategy); |
|
|
|
|
++current_or_arm; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* If one doesn't exist, we must resort to using an operator in the same |
|
|
|
|
* opreator family but with the operator class declared input type. It is |
|
|
|
|
* OK to do so, because the column's type is known to be binary-coercible |
|
|
|
|
* with the operator class input type (otherwise, the operator class in |
|
|
|
|
* question would not have been accepted as the partitioning operator |
|
|
|
|
* class). We must however inform the caller to wrap the non-Const |
|
|
|
|
* expression with a RelabelType node to denote the implicit coercion. It |
|
|
|
|
* ensures that the resulting expression structurally matches similarly |
|
|
|
|
* processed expressions within the optimizer. |
|
|
|
|
* Generate the OR expressions for each of lower and upper bounds (if |
|
|
|
|
* required), and append to the list of implicitly ANDed list of |
|
|
|
|
* expressions. |
|
|
|
|
*/ |
|
|
|
|
if (!OidIsValid(operoid)) |
|
|
|
|
{ |
|
|
|
|
operoid = get_opfamily_member(key->partopfamily[col], |
|
|
|
|
key->partopcintype[col], |
|
|
|
|
key->partopcintype[col], |
|
|
|
|
strategy); |
|
|
|
|
*need_relabel = true; |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
*need_relabel = false; |
|
|
|
|
if (lower_or_arms != NIL) |
|
|
|
|
result = lappend(result, |
|
|
|
|
list_length(lower_or_arms) > 1 |
|
|
|
|
? makeBoolExpr(OR_EXPR, lower_or_arms, -1) |
|
|
|
|
: linitial(lower_or_arms)); |
|
|
|
|
if (upper_or_arms != NIL) |
|
|
|
|
result = lappend(result, |
|
|
|
|
list_length(upper_or_arms) > 1 |
|
|
|
|
? makeBoolExpr(OR_EXPR, upper_or_arms, -1) |
|
|
|
|
: linitial(upper_or_arms)); |
|
|
|
|
|
|
|
|
|
/* As noted above, caller expects the list to be non-empty. */ |
|
|
|
|
if (result == NULL) |
|
|
|
|
result = list_make1(makeBoolConst(true, false)); |
|
|
|
|
|
|
|
|
|
if (!OidIsValid(operoid)) |
|
|
|
|
elog(ERROR, "could not find operator for partitioning"); |
|
|
|
|
|
|
|
|
|
return operoid; |
|
|
|
|
return result; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|