default_range_partition_WIP.patch
application/octet-stream
Filename: default_range_partition_WIP.patch
Type: application/octet-stream
Part: 0
Message:
Default Partition for Range
Patch
Format: unified
| File | + | − |
|---|---|---|
| src/backend/catalog/partition.c | 247 | 107 |
| src/backend/parser/gram.y | 3 | 0 |
| src/backend/parser/parse_utilcmd.c | 20 | 5 |
| src/backend/utils/adt/ruleutils.c | 11 | 0 |
| src/include/nodes/parsenodes.h | 2 | 0 |
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 86b512a..ddae8f7 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -72,7 +72,8 @@ typedef enum RangeDatumContent
{
RANGE_DATUM_FINITE = 0, /* actual datum stored elsewhere */
RANGE_DATUM_NEG_INF, /* negative infinity */
- RANGE_DATUM_POS_INF /* positive infinity */
+ RANGE_DATUM_POS_INF, /* positive infinity */
+ RANGE_DATUM_DEFAULT /* Default Datum */
} RangeDatumContent;
typedef struct PartitionBoundInfoData
@@ -89,10 +90,8 @@ typedef struct PartitionBoundInfoData
* partitioned table) */
int null_index; /* Index of the null-accepting partition; -1
* if there isn't one */
- bool has_default; /* Is there a default partition? Currently false
- * for a range partitioned table */
- int default_index; /* Index of the default list partition. -1 for
- * range partitioned tables */
+ bool has_default; /* Is there a default partition? */
+ int default_index; /* Index of the default list partition. */
} PartitionBoundInfoData;
#define partition_bound_accepts_nulls(bi) ((bi)->null_index != -1)
@@ -135,7 +134,7 @@ static void get_range_key_properties(PartitionKey key, int keynum,
Const **lower_val, Const **upper_val);
static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec,
bool is_def, List *boundspecs);
-static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec, bool is_def);
static List *generate_partition_qual(Relation rel);
static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index,
@@ -146,6 +145,7 @@ static int32 partition_rbound_cmp(PartitionKey key,
static int32 partition_rbound_datum_cmp(PartitionKey key,
Datum *rb_datums, RangeDatumContent *rb_content,
Datum *tuple_datums);
+static List *get_qual_for_range_default(Relation parent, Oid *defid);
static int32 partition_bound_cmp(PartitionKey key,
PartitionBoundInfo boundinfo,
@@ -176,11 +176,11 @@ RelationBuildPartitionDesc(Relation rel)
MemoryContext oldcxt;
int ndatums = 0;
+ bool found_def = false;
+ int def_index = -1;
/* List partitioning specific */
PartitionListValue **all_values = NULL;
- bool found_def = false;
- int def_index = -1;
int null_index = -1;
/* Range partitioning specific */
@@ -345,10 +345,21 @@ RelationBuildPartitionDesc(Relation rel)
PartitionBoundSpec *spec = lfirst(cell);
PartitionRangeBound *lower,
*upper;
+ ListCell *lc;
if (spec->strategy != PARTITION_STRATEGY_RANGE)
elog(ERROR, "invalid strategy in partition bound spec");
+ foreach(lc, spec->lowerdatums)
+ {
+ if (isDefaultPartitionBound((Node *) lfirst(lc)))
+ {
+ found_def = true;
+ def_index = i;
+ break;
+ }
+ }
+
lower = make_one_range_bound(key, i, spec->lowerdatums,
true);
upper = make_one_range_bound(key, i, spec->upperdatums,
@@ -559,6 +570,9 @@ RelationBuildPartitionDesc(Relation rel)
boundinfo->content[i][j] = rbounds[i]->content[j];
}
+ if (found_def && mapping[def_index] == -1)
+ mapping[def_index] = next_index++;
+
/*
* There is no mapping for invalid indexes.
*
@@ -581,6 +595,11 @@ RelationBuildPartitionDesc(Relation rel)
boundinfo->indexes[i] = mapping[orig_index];
}
}
+
+ if (found_def)
+ boundinfo->default_index = mapping[def_index];
+ else
+ boundinfo->default_index = -1;
boundinfo->indexes[i] = -1;
break;
}
@@ -750,7 +769,6 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
}
}
}
-
break;
}
@@ -763,95 +781,113 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
lower = make_one_range_bound(key, -1, spec->lowerdatums, true);
upper = make_one_range_bound(key, -1, spec->upperdatums, false);
+
/*
- * First check if the resulting range would be empty with
- * specified lower and upper bounds
+ * If the new partition is default check if the parent table
+ * already has a default partition
*/
- if (partition_rbound_cmp(key, lower->datums, lower->content, true,
- upper) >= 0)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
- errmsg("cannot create range partition with empty range"),
- parser_errposition(pstate, spec->location)));
- if (partdesc->nparts > 0)
+ if ((lower->content[0] == RANGE_DATUM_DEFAULT))
+ {
+ if (partdesc->nparts > 0 && partdesc->boundinfo->has_default)
+ {
+ overlap = true;
+ with = partdesc->boundinfo->default_index;
+ break;
+ }
+ }
+ else
{
- PartitionBoundInfo boundinfo = partdesc->boundinfo;
- int off1,
- off2;
- bool equal = false;
-
- Assert(boundinfo && boundinfo->ndatums > 0 &&
- boundinfo->strategy == PARTITION_STRATEGY_RANGE);
-
- /*
- * Firstly, find the greatest range bound that is less
- * than or equal to the new lower bound.
- */
- off1 = partition_bound_bsearch(key, boundinfo, lower, true,
- &equal);
-
/*
- * off1 == -1 means that all existing bounds are greater
- * than the new lower bound. In that case and the case
- * where no partition is defined between the bounds at
- * off1 and off1 + 1, we have a "gap" in the range that
- * could be occupied by the new partition. We confirm if
- * so by checking whether the new upper bound is confined
- * within the gap.
+ * First check if the resulting range would be empty with
+ * specified lower and upper bounds
*/
- if (!equal && boundinfo->indexes[off1 + 1] < 0)
+ if (partition_rbound_cmp(key, lower->datums, lower->content, true,
+ upper) >= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot create range partition with empty range"),
+ parser_errposition(pstate, spec->location)));
+
+ if (partdesc->nparts > 0)
{
- off2 = partition_bound_bsearch(key, boundinfo, upper,
- true, &equal);
+ PartitionBoundInfo boundinfo = partdesc->boundinfo;
+ int off1,
+ off2;
+ bool equal = false;
+
+ Assert(boundinfo && boundinfo->ndatums > 0 &&
+ boundinfo->strategy == PARTITION_STRATEGY_RANGE);
/*
- * If the new upper bound is returned to be equal to
- * the bound at off2, the latter must be the upper
- * bound of some partition with which the new
- * partition clearly overlaps.
- *
- * Also, if bound at off2 is not same as the one
- * returned for the new lower bound (IOW, off1 !=
- * off2), then the new partition overlaps at least one
- * partition.
+ * Firstly, find the greatest range bound that is less
+ * than or equal to the new lower bound.
*/
- if (equal || off1 != off2)
+ off1 = partition_bound_bsearch(key, boundinfo, lower, true,
+ &equal);
+
+ /*
+ * off1 == -1 means that all existing bounds are
+ * greater than the new lower bound. In that case and
+ * the case where no partition is defined between the
+ * bounds at off1 and off1 + 1, we have a "gap" in the
+ * range that could be occupied by the new partition.
+ * We confirm if so by checking whether the new upper
+ * bound is confined within the gap.
+ */
+ if (!equal && boundinfo->indexes[off1 + 1] < 0)
{
- overlap = true;
+ off2 = partition_bound_bsearch(key, boundinfo, upper,
+ true, &equal);
/*
- * The bound at off2 could be the lower bound of
- * the partition with which the new partition
- * overlaps. In that case, use the upper bound
- * (that is, the bound at off2 + 1) to get the
- * index of that partition.
+ * If the new upper bound is returned to be equal
+ * to the bound at off2, the latter must be the
+ * upper bound of some partition with which the
+ * new partition clearly overlaps.
+ *
+ * Also, if bound at off2 is not same as the one
+ * returned for the new lower bound (IOW, off1 !=
+ * off2), then the new partition overlaps at least
+ * one partition.
*/
- if (boundinfo->indexes[off2] < 0)
- with = boundinfo->indexes[off2 + 1];
- else
- with = boundinfo->indexes[off2];
+ if (equal || off1 != off2)
+ {
+ overlap = true;
+
+ /*
+ * The bound at off2 could be the lower bound
+ * of the partition with which the new
+ * partition overlaps. In that case, use the
+ * upper bound (that is, the bound at off2 +
+ * 1) to get the index of that partition.
+ */
+ if (boundinfo->indexes[off2] < 0)
+ with = boundinfo->indexes[off2 + 1];
+ else
+ with = boundinfo->indexes[off2];
+ }
+ }
+ else
+ {
+ /*
+ * Equal has been set to true and there is no
+ * "gap" between the bound at off1 and that at
+ * off1 + 1, so the new partition will overlap
+ * some partition. In the former case, the new
+ * lower bound is found to be equal to the bound
+ * at off1, which could only ever be true if the
+ * latter is the lower bound of some partition.
+ * It's clear in such a case that the new
+ * partition overlaps that partition, whose index
+ * we get using its upper bound (that is, using
+ * the bound at off1 + 1).
+ */
+ overlap = true;
+ with = boundinfo->indexes[off1 + 1];
}
- }
- else
- {
- /*
- * Equal has been set to true and there is no "gap"
- * between the bound at off1 and that at off1 + 1, so
- * the new partition will overlap some partition. In
- * the former case, the new lower bound is found to be
- * equal to the bound at off1, which could only ever
- * be true if the latter is the lower bound of some
- * partition. It's clear in such a case that the new
- * partition overlaps that partition, whose index we
- * get using its upper bound (that is, using the bound
- * at off1 + 1).
- */
- overlap = true;
- with = boundinfo->indexes[off1 + 1];
}
}
-
break;
}
@@ -871,13 +907,12 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
}
/*
- * When adding a list partition after default partition, scan the
- * default partition for rows satisfying the new partition
- * constraint. If found don't allow addition of a new partition.
- * Otherwise continue with the creation of new partition.
+ * When adding a partition after default partition, scan the default
+ * partition for rows satisfying the new partition constraint. If found,
+ * don't allow addition of a new partition. Otherwise continue with the
+ * creation of new partition.
*/
- if (spec->strategy == PARTITION_STRATEGY_LIST && partdesc->nparts > 0
- && boundinfo->has_default)
+ if (partdesc->nparts > 0 && boundinfo->has_default)
{
List *partConstraint = NIL;
ExprContext *econtext;
@@ -1104,6 +1139,77 @@ get_qual_for_default(Relation parent, Oid *defid)
return boundspecs;
}
+
+static List *
+get_qual_for_range_default(Relation parent, Oid *defid)
+{
+ PartitionKey key = RelationGetPartitionKey(parent);
+ List *inhoids;
+ ListCell *cell;
+ List *result = NIL;
+
+ inhoids = find_inheritance_children(RelationGetRelid(parent), AccessExclusiveLock);
+
+ foreach(cell, inhoids)
+ {
+ Oid inhrelid = lfirst_oid(cell);
+ HeapTuple tuple;
+ Datum datum;
+ bool isnull;
+ bool def_elem = false;
+ PartitionBoundSpec *bspec;
+ ListCell *cell_u,
+ *cell_l;
+
+ tuple = SearchSysCache1(RELOID, inhrelid);
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", inhrelid);
+
+ /*
+ * It is possible that the pg_class tuple of a partition has not been
+ * updated yet to set its relpartbound field. The only case where
+ * this happens is when we open the parent relation to check using its
+ * partition descriptor that a new partition's bound does not overlap
+ * some existing partition.
+ */
+ if (!((Form_pg_class) GETSTRUCT(tuple))->relispartition)
+ {
+ ReleaseSysCache(tuple);
+ continue;
+ }
+
+ datum = SysCacheGetAttr(RELOID, tuple,
+ Anum_pg_class_relpartbound,
+ &isnull);
+
+ Assert(!isnull);
+ bspec = (PartitionBoundSpec *) stringToNode(TextDatumGetCString(datum));
+
+ forboth(cell_l, bspec->lowerdatums, cell_u, bspec->upperdatums)
+ {
+ Node *value = lfirst(cell_l);
+
+ if (isDefaultPartitionBound(value))
+ {
+ def_elem = true;
+ *defid = inhrelid;
+ break;
+ }
+ }
+ if (!def_elem)
+ {
+ List *boundspecs = get_qual_for_range(key, bspec, true);
+
+ result = lappend(result,
+ list_length(boundspecs) > 1
+ ? makeBoolExpr(OR_EXPR, boundspecs, -1)
+ : linitial(boundspecs));
+ }
+ ReleaseSysCache(tuple);
+ }
+ return result;
+}
+
/*
* Return a list of executable expressions as new partition constraint
* for default partition while adding a new partition after default
@@ -1119,16 +1225,22 @@ generate_qual_for_defaultpart(Relation parent, Node *bound, Oid * defid)
List *bound_datums;
spec = (PartitionBoundSpec *) bound;
- bound_datums = list_copy(spec->listdatums);
+ if (spec->strategy == PARTITION_STRATEGY_LIST)
+ {
+ bound_datums = list_copy(spec->listdatums);
+ boundspecs = get_qual_for_default(parent, defid);
- boundspecs = get_qual_for_default(parent, defid);
+ foreach(cell, bound_datums)
+ {
+ Node *value = lfirst(cell);
- foreach(cell, bound_datums)
- {
- Node *value = lfirst(cell);
- boundspecs = lappend(boundspecs, value);
+ boundspecs = lappend(boundspecs, value);
+ }
+ partConstraint = get_qual_for_list(key, spec, true, boundspecs);
}
- partConstraint = get_qual_for_list(key, spec, true, boundspecs);
+ else if (spec->strategy == PARTITION_STRATEGY_RANGE)
+ partConstraint = get_qual_for_range_default(parent, defid);
+
return partConstraint;
}
@@ -1166,7 +1278,17 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
break;
case PARTITION_STRATEGY_RANGE:
Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
- my_qual = get_qual_for_range(key, spec);
+ foreach(cell, spec->lowerdatums)
+ {
+ Node *value = lfirst(cell);
+
+ if (isDefaultPartitionBound(value))
+ is_def = true;
+ }
+ if (is_def)
+ my_qual = get_qual_for_range_default(parent, &defid);
+ else
+ my_qual = get_qual_for_range(key, spec, is_def);
break;
default:
@@ -1542,6 +1664,12 @@ make_partition_op_expr(PartitionKey key, int keynum,
}
case PARTITION_STRATEGY_RANGE:
+ if (is_def && ((operoid = get_negator(operoid)) == InvalidOid))
+ ereport(ERROR, (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("DEFAULT partition cannot be used"
+ " without negator of operator %s",
+ get_opname(operoid))));
+
result = make_opclause(operoid,
BOOLOID,
false,
@@ -1754,7 +1882,7 @@ get_range_key_properties(PartitionKey key, int keynum,
* expect a non-empty list.
*/
static List *
-get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
+get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec, bool is_def)
{
List *result = NIL;
ListCell *cell1,
@@ -1889,7 +2017,7 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
result = lappend(result,
make_partition_op_expr(key, i, BTEqualStrategyNumber,
keyCol, (Expr *) lower_val,
- false));
+ is_def));
i++;
}
@@ -1948,7 +2076,7 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
strategy,
keyCol,
(Expr *) lower_val,
- false));
+ is_def));
}
if (need_next_upper_arm && upper_val)
@@ -1971,7 +2099,7 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
strategy,
keyCol,
(Expr *) upper_val,
- false));
+ is_def));
}
@@ -2385,10 +2513,12 @@ make_one_range_bound(PartitionKey key, int index, List *datums, bool lower)
PartitionRangeDatum *datum = lfirst(cell);
/* What's contained in this range datum? */
- bound->content[i] = !datum->infinite
- ? RANGE_DATUM_FINITE
- : (lower ? RANGE_DATUM_NEG_INF
- : RANGE_DATUM_POS_INF);
+ bound->content[i] = (datum->type == T_DefElem)
+ ? RANGE_DATUM_DEFAULT
+ : (!datum->infinite
+ ? RANGE_DATUM_FINITE
+ : (lower ? RANGE_DATUM_NEG_INF
+ : RANGE_DATUM_POS_INF));
if (bound->content[i] == RANGE_DATUM_FINITE)
{
@@ -2436,15 +2566,25 @@ partition_rbound_cmp(PartitionKey key,
for (i = 0; i < key->partnatts; i++)
{
/*
- * First, handle cases involving infinity, which don't require
- * invoking the comparison proc.
+ * First, handle cases involving infinity and default, which don't
+ * require invoking the comparison proc.
*/
+ if (content1[i] == RANGE_DATUM_DEFAULT ||
+ content2[i] == RANGE_DATUM_DEFAULT)
+ {
+ if (content1[i] == content2[i])
+ return 0;
+ else if (content1[i] == RANGE_DATUM_DEFAULT)
+ return lower1 ? 1 : -1;
+ else
+ return lower1 ? -1 : 1;
+ }
if (content1[i] != RANGE_DATUM_FINITE &&
content2[i] != RANGE_DATUM_FINITE)
/*
- * Both are infinity, so they are equal unless one is negative
- * infinity and other positive (or vice versa)
+ * Both are infinity, so they are equal unless one is
+ * negative infinity and other positive (or vice versa)
*/
return content1[i] == content2[i] ? 0
: (content1[i] < content2[i] ? -1 : 1);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3af286f..7ba6425 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2682,7 +2682,10 @@ ForValues:
PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
n->strategy = PARTITION_DEFAULT;
+ n->isdefault = true;
n->listdatums = $1;
+ n->lowerdatums = $1;
+ n->upperdatums = $1;
n->location = @1;
$$ = (Node *) n;
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 306a5d1..b6495f9 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3384,15 +3384,29 @@ transformPartitionBound(ParseState *pstate, Relation parent, Node *bound)
j;
char *colname;
bool seen_unbounded;
+ bool is_def;
if (spec->strategy != PARTITION_STRATEGY_RANGE)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("invalid bound specification for a range partition"),
- parser_errposition(pstate, exprLocation(bound))));
-
+ {
+ /*
+ * If the partition is the default partition switch back to
+ * PARTITION_STRATEGY_RANGE
+ */
+ if (spec->strategy == PARTITION_DEFAULT)
+ {
+ is_def = true;
+ result_spec->strategy = PARTITION_STRATEGY_RANGE;
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a range partition"),
+ parser_errposition(pstate, exprLocation(bound))));
+ }
Assert(spec->lowerdatums != NIL && spec->upperdatums != NIL);
+ if(!is_def)
+ {
if (list_length(spec->lowerdatums) != partnatts)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
@@ -3401,6 +3415,7 @@ transformPartitionBound(ParseState *pstate, Relation parent, Node *bound)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("TO must specify exactly one value per partitioning column")));
+ }
/*
* Check that no finite value follows a UNBOUNDED literal in either of
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 5bff62a..038c937 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8689,6 +8689,17 @@ get_rule_expr(Node *node, deparse_context *context,
list_length(spec->lowerdatums) ==
list_length(spec->upperdatums));
+ foreach(cell, spec->lowerdatums)
+ {
+ if (isDefaultPartitionBound((Node *) lfirst(cell)))
+ {
+ appendStringInfoString(buf, "DEFAULT");
+ is_def = true;
+ break;
+ }
+ }
+ if (is_def)
+ break;
appendStringInfoString(buf, "FOR VALUES");
appendStringInfoString(buf, " FROM");
appendStringInfoString(buf, " (");
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index e632a42..8fe1aa5 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -791,6 +791,8 @@ typedef struct PartitionBoundSpec
char strategy;
+ bool isdefault;
+
/* List partition values */
List *listdatums;