Default Partition for Range
Hello,
Many were in favour of the default partition for tables partitioned by
range [1]Discussion on default partition: */messages/by-id/CAOgcT0PLPge=5U6=GU5SnC3_8yutCbWWOiUva3Cw94M9zpbvgQ@mail.gmail.com </messages/by-id/CAOgcT0PLPge=5U6=GU5SnC3_8yutCbWWOiUva3Cw94M9zpbvgQ@mail.gmail.com>*.
Please find attached the WIP patch for the same which can be applied over
the default_partition_v12.patch.
Syntax: Same as agreed for list:
CREATE PARTITION <part_name> PARTITION OF <parent_tbl> DEFAULT;
Default Constraint:
Negation constraints of all existing partitions.
One case:
CREATE TABLE range1 (a int, b int) PARTITION by range (a);
CREATE TABLE range1_1 PARTITION OF range1 FOR VALUES FROM (1) TO (5);
CREATE TABLE range1_2 PARTITION OF range1 FOR VALUES FROM (7) TO (10);
CREATE TABLE range1_def PARTITION OF range1 DEFAULT;
\d+ range1_def
Table "public.range1_def"
Column | Type | Collation | Nullable | Default | Storage | Stats target
| Description
--------+---------+-----------+----------+---------+--------
-+--------------+-------------
a | integer | | not null | | plain |
|
b | integer | | | | plain |
|
Partition of: range1 DEFAULT
Partition constraint: (((a < 1) OR (a >= 5)) AND ((a < 7) OR (a >= 10)))
It still needs more work:
1. Handling addition of new partition after default, insertion of data, few
more bugs
2. Documentation
3. Regression tests
[1]: Discussion on default partition: */messages/by-id/CAOgcT0PLPge=5U6=GU5SnC3_8yutCbWWOiUva3Cw94M9zpbvgQ@mail.gmail.com </messages/by-id/CAOgcT0PLPge=5U6=GU5SnC3_8yutCbWWOiUva3Cw94M9zpbvgQ@mail.gmail.com>*
*/messages/by-id/CAOgcT0PLPge=5U6=GU5SnC3_8yutCbWWOiUva3Cw94M9zpbvgQ@mail.gmail.com
</messages/by-id/CAOgcT0PLPge=5U6=GU5SnC3_8yutCbWWOiUva3Cw94M9zpbvgQ@mail.gmail.com>*
--
Beena Emerson
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Attachments:
default_range_partition_WIP.patchapplication/octet-stream; name=default_range_partition_WIP.patchDownload
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;
On Mon, May 22, 2017 at 7:27 AM, Beena Emerson <memissemerson@gmail.com> wrote:
Hello,
Many were in favour of the default partition for tables partitioned by range
[1].
Please find attached the WIP patch for the same which can be applied over
the default_partition_v12.patch.Syntax: Same as agreed for list:
CREATE PARTITION <part_name> PARTITION OF <parent_tbl> DEFAULT;Default Constraint:
Negation constraints of all existing partitions.One case:
CREATE TABLE range1 (a int, b int) PARTITION by range (a);
CREATE TABLE range1_1 PARTITION OF range1 FOR VALUES FROM (1) TO (5);
CREATE TABLE range1_2 PARTITION OF range1 FOR VALUES FROM (7) TO (10);
CREATE TABLE range1_def PARTITION OF range1 DEFAULT;
\d+ range1_def
Table "public.range1_def"
Column | Type | Collation | Nullable | Default | Storage | Stats target
| Description
--------+---------+-----------+----------+---------+---------+--------------+-------------
a | integer | | not null | | plain |
|
b | integer | | | | plain |
|
Partition of: range1 DEFAULT
Partition constraint: (((a < 1) OR (a >= 5)) AND ((a < 7) OR (a >= 10)))
Would it be more readable if this reads as NOT
(constraint_for_partition_1 || constraint_for_partition_2 ||
constraint_for_partition_3)? That way, the code to create
constraint_for_partition_n can be reused and there's high chance that
we will end up with consistent constraints?
It still needs more work:
1. Handling addition of new partition after default, insertion of data, few
more bugs
2. Documentation
3. Regression tests
I think, the default partition support for all the strategies
supporting it should be a single patch since most of the code will be
shared?
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hello,
On Mon, May 22, 2017 at 11:29 AM, Ashutosh Bapat <
ashutosh.bapat@enterprisedb.com> wrote:
On Mon, May 22, 2017 at 7:27 AM, Beena Emerson <memissemerson@gmail.com>
wrote:
Would it be more readable if this reads as NOT
(constraint_for_partition_1 || constraint_for_partition_2 ||
constraint_for_partition_3)? That way, the code to create
constraint_for_partition_n can be reused and there's high chance that
we will end up with consistent constraints?
PFA the patch which gives the default partition constraint as you have
suggested.
It still needs more work:
1. Handling addition of new partition after default, insertion of data,few
more bugs
2. Documentation
3. Regression testsI think, the default partition support for all the strategies
supporting it should be a single patch since most of the code will be
shared?
Dependency on list default patch:
gram.y : adding the syntax
partition.c:
- default_index member in PartitionBoundInfoData;
- check_new_partition_bound : the code for adding a partition after default
has been completely reused.
- isDefaultPartitionBound function is used.
The structures are same but the handling of list and range is very
different and the code mostly has the switch case construct to handle the
2 separately. I feel it could be taken separately.
As suggested in the default list thread, I have created
a partition_bound_has_default macro and avoided usage of has_default in
range code. This has to be used for list as well.
Another suggestion for list which has to be implemented in this patch in
removal of PARTITION_DEFAULT. Ii have not done this in this version.
--
Beena Emerson
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Attachments:
default_range_partition.patchapplication/octet-stream; name=default_range_partition.patchDownload
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 86b512a..df370a7 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,13 +90,12 @@ 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)
+#define partition_bound_has_default(bi) ((bi)->default_index != -1)
/*
* When qsort'ing partition bounds after reading from the catalog, each bound
@@ -135,7 +135,8 @@ 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 +147,9 @@ 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,
+ PartitionBoundSpec *new_spec, Oid *defid);
+static List *get_range_nulltest(PartitionKey key);
static int32 partition_bound_cmp(PartitionKey key,
PartitionBoundInfo boundinfo,
@@ -176,11 +180,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 */
@@ -349,6 +353,9 @@ RelationBuildPartitionDesc(Relation rel)
if (spec->strategy != PARTITION_STRATEGY_RANGE)
elog(ERROR, "invalid strategy in partition bound spec");
+ if (isDefaultPartitionBound((Node *) linitial(spec->lowerdatums)))
+ def_index = i;
+
lower = make_one_range_bound(key, i, spec->lowerdatums,
true);
upper = make_one_range_bound(key, i, spec->upperdatums,
@@ -537,7 +544,6 @@ RelationBuildPartitionDesc(Relation rel)
sizeof(RangeDatumContent *));
boundinfo->indexes = (int *) palloc((ndatums + 1) *
sizeof(int));
- boundinfo->has_default = found_def;
for (i = 0; i < ndatums; i++)
{
@@ -559,6 +565,9 @@ RelationBuildPartitionDesc(Relation rel)
boundinfo->content[i][j] = rbounds[i]->content[j];
}
+ if (def_index != -1 && mapping[def_index] == -1)
+ mapping[def_index] = next_index++;
+
/*
* There is no mapping for invalid indexes.
*
@@ -581,6 +590,10 @@ RelationBuildPartitionDesc(Relation rel)
boundinfo->indexes[i] = mapping[orig_index];
}
}
+
+ boundinfo->default_index = (def_index == -1) ?
+ -1 : mapping[def_index];
+
boundinfo->indexes[i] = -1;
break;
}
@@ -764,8 +777,24 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
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 ((lower->content[0] == RANGE_DATUM_DEFAULT))
+ {
+ if (partdesc->nparts > 0 &&
+ partition_bound_has_default(partdesc->boundinfo))
+ {
+ overlap = true;
+ with = partdesc->boundinfo->default_index;
+ }
+ break;
+ }
+
+ /*
+ * Check if the resulting range would be empty with specified
+ * lower and upper bounds
*/
if (partition_rbound_cmp(key, lower->datums, lower->content, true,
upper) >= 0)
@@ -871,13 +900,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 && partition_bound_has_default(boundinfo))
{
List *partConstraint = NIL;
ExprContext *econtext;
@@ -891,11 +919,13 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
MemoryContext oldCxt;
TupleTableSlot *tupslot;
TupleDesc tupdesc;
- List *all_parts;
+ List *all_parts;
ListCell *lc;
+ partConstraint = (key->strategy == PARTITION_STRATEGY_LIST)
+ ? generate_qual_for_defaultpart(parent, bound, &defid)
+ : get_qual_for_range_default(parent, spec, &defid);
- partConstraint = generate_qual_for_defaultpart(parent, bound, &defid);
partConstraint = (List *) eval_const_expressions(NULL,
(Node *) partConstraint);
partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
@@ -1104,6 +1134,148 @@ get_qual_for_default(Relation parent, Oid *defid)
return boundspecs;
}
+ /*
+ * A non-default range partiton table does not currently allow partition keys
+ * to be null, so emit an IS NOT NULL expression for each key column.
+ */
+static List *
+get_range_nulltest(PartitionKey key)
+{
+ List *result = NIL;
+ NullTest *nulltest;
+ ListCell *partexprs_item;
+ int i;
+
+ partexprs_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ Expr *keyCol;
+
+ if (key->partattrs[i] != 0)
+ {
+ keyCol = (Expr *) makeVar(1,
+ key->partattrs[i],
+ key->parttypid[i],
+ key->parttypmod[i],
+ key->parttypcoll[i],
+ 0);
+ }
+ else
+ {
+ if (partexprs_item == NULL)
+ elog(ERROR, "wrong number of partition key expressions");
+ keyCol = copyObject(lfirst(partexprs_item));
+ partexprs_item = lnext(partexprs_item);
+ }
+
+ nulltest = makeNode(NullTest);
+ nulltest->arg = keyCol;
+ nulltest->nulltesttype = IS_NOT_NULL;
+ nulltest->argisrow = false;
+ nulltest->location = -1;
+ result = lappend(result, nulltest);
+ }
+
+ return result;
+}
+
+/*
+ * Return a qual for the default partition of range.
+ *
+ * Generally it is negation of the constraints of all other partitions:
+ * NOT ( <partiton1 constraint> [ || <partiontion2 constraint>] [|| ...])
+ *
+ * When it is called for the insertion of a new partiton after default, the
+ * list will also have the constraints of new_spec.
+ *
+ * If DEFAULT is the only partiton for the table then this returns TRUE.
+ *
+ * If the only other partition is [UNBOUNDED, UNBOUNDED]; this returns
+ * NOT ( <keycol1> != NULL [ AND <keycol2> != NULL] [AND ...])
+ * In this case, default will only store tuples where keycol value is NULL.
+ */
+static List *
+get_qual_for_range_default(Relation parent, PartitionBoundSpec *new_spec, Oid *defid)
+{
+ PartitionKey key = RelationGetPartitionKey(parent);
+ List *inhoids,
+ *result = NIL,
+ *or_expr_args = NIL;
+ ListCell *cell;
+
+ inhoids = find_inheritance_children(RelationGetRelid(parent), AccessExclusiveLock);
+
+ foreach(cell, inhoids)
+ {
+ Oid inhrelid = lfirst_oid(cell);
+ HeapTuple tuple;
+ Datum datum;
+ bool isnull;
+ PartitionBoundSpec *bspec;
+
+ 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));
+
+ if (isDefaultPartitionBound((linitial(bspec->lowerdatums))))
+ *defid = inhrelid;
+ else
+ {
+ List *part_qual = get_qual_for_range(key, bspec, true);
+
+ /* AND the constraints of the partition and add to or_expr_args */
+ or_expr_args = lappend(or_expr_args, list_length(part_qual) > 1
+ ? makeBoolExpr(AND_EXPR, part_qual, -1)
+ : linitial(part_qual));
+
+ }
+ ReleaseSysCache(tuple);
+ }
+
+ if (new_spec != NULL)
+ {
+ List *part_qual = get_qual_for_range(key, new_spec, true);
+
+ or_expr_args = lappend(or_expr_args, list_length(part_qual) > 1
+ ? makeBoolExpr(AND_EXPR, part_qual, -1)
+ : linitial(part_qual));
+ }
+
+ if (or_expr_args != NIL)
+ {
+ /* OR all the non-default partition constraints; then negate it */
+ result = lappend(result,
+ list_length(or_expr_args) > 1
+ ? makeBoolExpr(OR_EXPR, or_expr_args, -1)
+ : linitial(or_expr_args));
+ result = list_make1(makeBoolExpr(NOT_EXPR, result, -1));
+ }
+ else /* When default is the only partition */
+ result = list_make1(makeBoolConst(true, false));
+
+ return result;
+}
+
/*
* Return a list of executable expressions as new partition constraint
* for default partition while adding a new partition after default
@@ -1144,7 +1316,7 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
PartitionKey key = RelationGetPartitionKey(parent);
List *my_qual = NIL;
bool is_def = false;
- Oid defid;
+ Oid defid;
ListCell *cell;
List *boundspecs = NIL;
@@ -1166,7 +1338,11 @@ 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);
+
+ if (isDefaultPartitionBound((Node *) linitial(spec->lowerdatums)))
+ my_qual = get_qual_for_range_default(parent, NULL, &defid);
+ else
+ my_qual = get_qual_for_range(key, spec, is_def);
break;
default:
@@ -1754,7 +1930,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,
@@ -1768,7 +1944,6 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
Expr *keyCol;
Const *lower_val,
*upper_val;
- NullTest *nulltest;
List *lower_or_arms,
*upper_or_arms;
int num_or_arms,
@@ -1782,39 +1957,8 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
upper_or_start_datum = list_head(spec->upperdatums);
num_or_arms = key->partnatts;
- /*
- * A range-partitioned table does not currently allow partition keys to
- * be null, so emit an IS NOT NULL expression for each key column.
- */
- partexprs_item = list_head(key->partexprs);
- for (i = 0; i < key->partnatts; i++)
- {
- Expr *keyCol;
-
- if (key->partattrs[i] != 0)
- {
- keyCol = (Expr *) makeVar(1,
- key->partattrs[i],
- key->parttypid[i],
- key->parttypmod[i],
- key->parttypcoll[i],
- 0);
- }
- else
- {
- if (partexprs_item == NULL)
- elog(ERROR, "wrong number of partition key expressions");
- keyCol = copyObject(lfirst(partexprs_item));
- partexprs_item = lnext(partexprs_item);
- }
-
- nulltest = makeNode(NullTest);
- nulltest->arg = keyCol;
- nulltest->nulltesttype = IS_NOT_NULL;
- nulltest->argisrow = false;
- nulltest->location = -1;
- result = lappend(result, nulltest);
- }
+ if (!is_def)
+ result = get_range_nulltest(key);
/*
* Iterate over the key columns and check if the corresponding lower and
@@ -2030,7 +2174,9 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
/* As noted above, caller expects the list to be non-empty. */
if (result == NULL)
- result = list_make1(makeBoolConst(true, false));
+ result = is_def
+ ? get_range_nulltest(key)
+ : list_make1(makeBoolConst(true, false));
return result;
}
@@ -2197,8 +2343,7 @@ get_partition_for_tuple(PartitionDispatch *pd,
Datum values[PARTITION_MAX_KEYS];
bool isnull[PARTITION_MAX_KEYS];
int cur_offset,
- cur_index;
- int i,
+ cur_index = -1,
result;
ExprContext *ecxt = GetPerTupleExprContext(estate);
TupleTableSlot *ecxt_scantuple_old = ecxt->ecxt_scantuple;
@@ -2242,33 +2387,16 @@ get_partition_for_tuple(PartitionDispatch *pd,
ecxt->ecxt_scantuple = slot;
FormPartitionKeyDatum(parent, slot, estate, values, isnull);
- if (key->strategy == PARTITION_STRATEGY_RANGE)
+ if (key->strategy == PARTITION_STRATEGY_LIST && isnull[0])
{
/*
- * Since we cannot route tuples with NULL partition keys through
- * a range-partitioned table, simply return that no partition
- * exists
+ * A null partition key is only acceptable if null-accepting list
+ * partition exists.
*/
- for (i = 0; i < key->partnatts; i++)
- {
- if (isnull[i])
- {
- *failed_at = parent;
- *failed_slot = slot;
- result = -1;
- goto error_exit;
- }
- }
+ if (partition_bound_accepts_nulls(partdesc->boundinfo))
+ cur_index = partdesc->boundinfo->null_index;
}
-
- /*
- * A null partition key is only acceptable if null-accepting list
- * partition exists.
- */
- cur_index = -1;
- if (isnull[0] && partition_bound_accepts_nulls(partdesc->boundinfo))
- cur_index = partdesc->boundinfo->null_index;
- else if (!isnull[0])
+ else
{
/* Else bsearch in partdesc->boundinfo */
bool equal = false;
@@ -2311,7 +2439,7 @@ get_partition_for_tuple(PartitionDispatch *pd,
* If partitioned table has a default partition, return
* its sequence number
*/
- if (partdesc->boundinfo->has_default)
+ if (partition_bound_has_default(partdesc->boundinfo))
{
result = parent->indexes[partdesc->boundinfo->default_index];
if (result >= 0)
@@ -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,9 +2566,19 @@ 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 -1;
+ else
+ return 1;
+ }
if (content1[i] != RANGE_DATUM_FINITE &&
content2[i] != RANGE_DATUM_FINITE)
@@ -2489,6 +2629,9 @@ partition_rbound_datum_cmp(PartitionKey key,
for (i = 0; i < key->partnatts; i++)
{
+ if (rb_content[i] == RANGE_DATUM_DEFAULT)
+ continue;
+
if (rb_content[i] != RANGE_DATUM_FINITE)
return rb_content[i] == RANGE_DATUM_NEG_INF ? -1 : 1;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3af286f..26c00ad 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2683,6 +2683,8 @@ ForValues:
n->strategy = PARTITION_DEFAULT;
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..5e13772 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3384,23 +3384,38 @@ 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),
+ {
+ /*
+ * 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))));
-
+ parser_errposition(pstate, exprLocation(bound))));
+ }
Assert(spec->lowerdatums != NIL && spec->upperdatums != NIL);
- if (list_length(spec->lowerdatums) != partnatts)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("FROM must specify exactly one value per partitioning column")));
- if (list_length(spec->upperdatums) != partnatts)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("TO must specify exactly one value per partitioning column")));
+ if (!is_def)
+ {
+ if (list_length(spec->lowerdatums) != partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("FROM must specify exactly one value per partitioning column")));
+ if (list_length(spec->upperdatums) != partnatts)
+ 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, " (");
Hi Beena,
I went through your patch, and here are some of my comments:
- For generating a qual for default range partition, instead of scanning
for all
the children and collecting all the boundspecs, how about creating and
negating
an expression from the lists of lowerdatums and upperdatums in boundinfo.
- Wrong comment:
+ int default_index; /* Index of the default list partition. */
- Suggested by Robert earlier on default list partitioning thread, instead
of
abbreviating is_def/found_def use is(found)_default etc.
- unrelated change:
- List *all_parts;
+ List *all_parts;
- typo: partiton should be partition, similar typo at other places too.
+ * A non-default range partiton table does not currently allow partition
keys
- Useless hunk for this patch:
- Oid defid;
+ Oid defid;
- better to use IsA here, instead of doing explicit check:
+ bound->content[i] = (datum->type == T_DefElem)
+ ? RANGE_DATUM_DEFAULT
- It is better to use head of either lowerdatums or upperdatums list to
verify
if its a default partition and get rid of the constant PARTITION_DEFAULT
altogether.
+ {
+ /*
+ * 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;
+ }
- I am sorry, but I could not understand following hunk. Does this change
really
belongs to this patch? If not, it will be better to handle it separately.
@@ -2242,33 +2387,16 @@ get_partition_for_tuple(PartitionDispatch *pd,
ecxt->ecxt_scantuple = slot;
FormPartitionKeyDatum(parent, slot, estate, values, isnull);
- if (key->strategy == PARTITION_STRATEGY_RANGE)
+ if (key->strategy == PARTITION_STRATEGY_LIST && isnull[0])
{
/*
- * Since we cannot route tuples with NULL partition keys through
- * a range-partitioned table, simply return that no partition
- * exists
+ * A null partition key is only acceptable if null-accepting list
+ * partition exists.
*/
- for (i = 0; i < key->partnatts; i++)
- {
- if (isnull[i])
- {
- *failed_at = parent;
- *failed_slot = slot;
- result = -1;
- goto error_exit;
- }
- }
+ if (partition_bound_accepts_nulls(partdesc->boundinfo))
+ cur_index = partdesc->boundinfo->null_index;
- Change not related to this patch:
- List *all_parts;
+ List *all_parts;
- In the function get_qual_from_partbound() is_def is always going to be
false
for range partition, the function get_qual_for_range can be directly passed
false instead.
- Following comment for function get_qual_for_range_default() implies that
this
function returns bool, but the actually it returns a List.
+ *
+ * If DEFAULT is the only partiton for the table then this returns TRUE.
+ *
Regards,
Jeevan Ladhe
On Wed, May 24, 2017 at 12:52 AM, Beena Emerson <memissemerson@gmail.com>
wrote:
Show quoted text
Hello,
On Mon, May 22, 2017 at 11:29 AM, Ashutosh Bapat <
ashutosh.bapat@enterprisedb.com> wrote:On Mon, May 22, 2017 at 7:27 AM, Beena Emerson <memissemerson@gmail.com>
wrote:
Would it be more readable if this reads as NOT
(constraint_for_partition_1 || constraint_for_partition_2 ||
constraint_for_partition_3)? That way, the code to create
constraint_for_partition_n can be reused and there's high chance that
we will end up with consistent constraints?PFA the patch which gives the default partition constraint as you have
suggested.It still needs more work:
1. Handling addition of new partition after default, insertion of data,few
more bugs
2. Documentation
3. Regression testsI think, the default partition support for all the strategies
supporting it should be a single patch since most of the code will be
shared?Dependency on list default patch:
gram.y : adding the syntax
partition.c:
- default_index member in PartitionBoundInfoData;
- check_new_partition_bound : the code for adding a partition after
default has been completely reused.
- isDefaultPartitionBound function is used.The structures are same but the handling of list and range is very
different and the code mostly has the switch case construct to handle the
2 separately. I feel it could be taken separately.As suggested in the default list thread, I have created
a partition_bound_has_default macro and avoided usage of has_default in
range code. This has to be used for list as well.
Another suggestion for list which has to be implemented in this patch in
removal of PARTITION_DEFAULT. Ii have not done this in this version.--
Beena Emerson
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, May 29, 2017 at 3:22 PM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:
Hi Beena,
I went through your patch, and here are some of my comments:
Thank you for your comments. I will take care of them in the next
version of patch.
- I am sorry, but I could not understand following hunk. Does this change
really
belongs to this patch? If not, it will be better to handle it separately.@@ -2242,33 +2387,16 @@ get_partition_for_tuple(PartitionDispatch *pd,
ecxt->ecxt_scantuple = slot;
FormPartitionKeyDatum(parent, slot, estate, values, isnull);- if (key->strategy == PARTITION_STRATEGY_RANGE) + if (key->strategy == PARTITION_STRATEGY_LIST && isnull[0]) { /* - * Since we cannot route tuples with NULL partition keys through - * a range-partitioned table, simply return that no partition - * exists + * A null partition key is only acceptable if null-accepting list + * partition exists. */
In RANGE, initially NULL was not allowed, now NULL is routed to
default. I have only removed the check for null in RANGE and kept the
check for null partition in case of list.
--
Beena Emerson
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hello,
PFA the updated patch.
Dependent patch default_partition_v17.patch [1]http://www.mail-archive.com/pgsql-hackers@postgresql.org/msg315573.html
This patch adds regression tests and removes the separate function to
get default partition qual.
On Mon, May 29, 2017 at 3:22 PM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:
Hi Beena,
I went through your patch, and here are some of my comments:
- For generating a qual for default range partition, instead of scanning for
all
the children and collecting all the boundspecs, how about creating and
negating
an expression from the lists of lowerdatums and upperdatums in boundinfo.
Unlike list, range partition can be for multiple columns and the
expressions get complicated. I have used the same logic of looping
through different partitions and negating the ORed expr. However, this
is now done under get_qual_for_range.
- Wrong comment: + int default_index; /* Index of the default list partition. */
Comment is made generic in the dependent patch.
- Suggested by Robert earlier on default list partitioning thread, instead
of
abbreviating is_def/found_def use is(found)_default etc.
corrected.
- unrelated change: - List *all_parts; + List *all_parts;
undone.
- typo: partiton should be partition, similar typo at other places too. + * A non-default range partiton table does not currently allow partition keys
rectified.
- Useless hunk for this patch: - Oid defid; + Oid defid;
undone.
- better to use IsA here, instead of doing explicit check: + bound->content[i] = (datum->type == T_DefElem) + ? RANGE_DATUM_DEFAULT
Modified
- It is better to use head of either lowerdatums or upperdatums list to
verify
if its a default partition and get rid of the constant PARTITION_DEFAULT
altogether.
modified this part as necessary.
- In the function get_qual_from_partbound() is_def is always going to be
false
for range partition, the function get_qual_for_range can be directly passed
false instead.- Following comment for function get_qual_for_range_default() implies that this function returns bool, but the actually it returns a List. + * + * If DEFAULT is the only partiton for the table then this returns TRUE. + *
Updated.
[1]: http://www.mail-archive.com/pgsql-hackers@postgresql.org/msg315573.html
--
Beena Emerson
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Attachments:
default_range_partition_v2.patchapplication/octet-stream; name=default_range_partition_v2.patchDownload
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index dc13833..de20b52 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 */
} RangeDatumContent;
typedef struct PartitionBoundInfoData
@@ -134,7 +135,9 @@ static void get_range_key_properties(PartitionKey key, int keynum,
Expr **keyCol,
Const **lower_val, Const **upper_val);
static List *get_qual_for_list(Relation parent, PartitionBoundSpec *spec);
-static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
+ bool for_default);
+static List *get_range_nulltest(PartitionKey key);
static List *generate_partition_qual(Relation rel);
static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index,
@@ -177,11 +180,11 @@ RelationBuildPartitionDesc(Relation rel)
MemoryContext oldcxt;
int ndatums = 0;
+ int default_index = -1;
/* List partitioning specific */
PartitionListValue **all_values = NULL;
int null_index = -1;
- int default_index = -1;
/* Range partitioning specific */
PartitionRangeBound **rbounds = NULL;
@@ -357,6 +360,9 @@ RelationBuildPartitionDesc(Relation rel)
if (spec->strategy != PARTITION_STRATEGY_RANGE)
elog(ERROR, "invalid strategy in partition bound spec");
+ if (isDefaultPartitionBound(linitial(spec->lowerdatums)))
+ default_index = i;
+
lower = make_one_range_bound(key, i, spec->lowerdatums,
true);
upper = make_one_range_bound(key, i, spec->upperdatums,
@@ -570,6 +576,15 @@ RelationBuildPartitionDesc(Relation rel)
boundinfo->content[i][j] = rbounds[i]->content[j];
}
+ /* Assign mapping index for default partition. */
+ if (default_index != -1 && mapping[default_index] == -1)
+ {
+ mapping[default_index] = next_index++;
+ boundinfo->default_index = mapping[default_index];
+ }
+ else
+ boundinfo->default_index = -1;
+
/*
* There is no mapping for invalid indexes.
*
@@ -592,13 +607,11 @@ RelationBuildPartitionDesc(Relation rel)
boundinfo->indexes[i] = mapping[orig_index];
}
}
- boundinfo->indexes[i] = -1;
- /*
- * Currently range partition do not have default partition
- * support.
- */
- boundinfo->default_index = -1;
+ boundinfo->default_index = (default_index == -1) ?
+ -1 : mapping[default_index];
+
+ boundinfo->indexes[i] = -1;
break;
}
@@ -794,8 +807,9 @@ check_new_partition_bound(char *relname, Relation parent,
* First check if the resulting range would be empty with
* specified lower and upper bounds
*/
- if (partition_rbound_cmp(key, lower->datums, lower->content, true,
- upper) >= 0)
+ if ((lower->content[0] != RANGE_DATUM_DEFAULT) &&
+ 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"),
@@ -812,8 +826,22 @@ check_new_partition_bound(char *relname, Relation parent,
boundinfo->strategy == PARTITION_STRATEGY_RANGE);
/*
- * Firstly, find the greatest range bound that is less
- * than or equal to the new lower bound.
+ * Default partition cannot be added if there already
+ * exists one.
+ */
+ if ((lower->content[0] == RANGE_DATUM_DEFAULT))
+ {
+ if (partition_bound_has_default(partdesc->boundinfo))
+ {
+ overlap = true;
+ with = partdesc->boundinfo->default_index;
+ }
+ break;
+ }
+
+ /*
+ * 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);
@@ -923,11 +951,9 @@ check_default_allows_bound(Relation parent, PartitionBoundSpec *new_spec)
List *all_parts;
ListCell *lc;
- /* Currently default partition is supported only for LIST partition. */
- if (new_spec->strategy != PARTITION_STRATEGY_LIST)
- return;
-
- new_part_constraints = get_qual_for_list(parent, new_spec);
+ new_part_constraints = (new_spec->strategy == PARTITION_STRATEGY_LIST)
+ ? get_qual_for_list(parent, new_spec)
+ : get_qual_for_range(parent, new_spec, false);
new_part_constraints = (List *) eval_const_expressions(NULL,
(Node *) new_part_constraints);
new_part_constraints =
@@ -1109,7 +1135,8 @@ get_qual_from_partbound(Relation rel, Relation parent,
case PARTITION_STRATEGY_RANGE:
Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
- my_qual = get_qual_for_range(key, spec);
+
+ my_qual = get_qual_for_range(parent, spec, false);
break;
default:
@@ -1692,6 +1719,53 @@ get_range_key_properties(PartitionKey key, int keynum,
*upper_val = NULL;
}
+ /*
+ * get_range_nulltest
+ *
+ * A non-default range partition table does not currently allow partition keys
+ * to be null, so emit an IS NOT NULL expression for each key column.
+ */
+static List *
+get_range_nulltest(PartitionKey key)
+{
+ List *result = NIL;
+ NullTest *nulltest;
+ ListCell *partexprs_item;
+ int i;
+
+ partexprs_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ Expr *keyCol;
+
+ if (key->partattrs[i] != 0)
+ {
+ keyCol = (Expr *) makeVar(1,
+ key->partattrs[i],
+ key->parttypid[i],
+ key->parttypmod[i],
+ key->parttypcoll[i],
+ 0);
+ }
+ else
+ {
+ if (partexprs_item == NULL)
+ elog(ERROR, "wrong number of partition key expressions");
+ keyCol = copyObject(lfirst(partexprs_item));
+ partexprs_item = lnext(partexprs_item);
+ }
+
+ nulltest = makeNode(NullTest);
+ nulltest->arg = keyCol;
+ nulltest->nulltesttype = IS_NOT_NULL;
+ nulltest->argisrow = false;
+ nulltest->location = -1;
+ result = lappend(result, nulltest);
+ }
+
+ return result;
+}
+
/*
* get_qual_for_range
*
@@ -1732,11 +1806,15 @@ get_range_key_properties(PartitionKey key, int keynum,
* does not really have a constraint, except the IS NOT NULL constraint for
* partition keys.
*
+ * For default partition, it returns the negation of the constraints of all
+ * other partitions.
+ *
* If we end up with an empty result list, we return a single-member list
* containing a constant TRUE, because callers expect a non-empty list.
*/
static List *
-get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
+get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
+ bool for_default)
{
List *result = NIL;
ListCell *cell1,
@@ -1747,10 +1825,10 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
j;
PartitionRangeDatum *ldatum,
*udatum;
+ PartitionKey key = RelationGetPartitionKey(parent);
Expr *keyCol;
Const *lower_val,
*upper_val;
- NullTest *nulltest;
List *lower_or_arms,
*upper_or_arms;
int num_or_arms,
@@ -1760,44 +1838,70 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
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;
-
- /*
- * A range-partitioned table does not currently allow partition keys to be
- * null, so emit an IS NOT NULL expression for each key column.
- */
- partexprs_item = list_head(key->partexprs);
- for (i = 0; i < key->partnatts; i++)
+ if (isDefaultPartitionBound((Node *) linitial(spec->lowerdatums)))
{
- Expr *keyCol;
+ List *or_expr_args = NIL;
+ PartitionDesc pdesc = RelationGetPartitionDesc(parent);
+ Oid *inhoids = pdesc->oids;
+ int nparts = pdesc->nparts,
+ i;
- if (key->partattrs[i] != 0)
+ for (i = 0; i < nparts; i++)
{
- keyCol = (Expr *) makeVar(1,
- key->partattrs[i],
- key->parttypid[i],
- key->parttypmod[i],
- key->parttypcoll[i],
- 0);
+ Oid inhrelid = inhoids[i];
+ HeapTuple tuple;
+ Datum datum;
+ bool isnull;
+ PartitionBoundSpec *bspec;
+
+ tuple = SearchSysCache1(RELOID, inhrelid);
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", inhrelid);
+
+ datum = SysCacheGetAttr(RELOID, tuple,
+ Anum_pg_class_relpartbound,
+ &isnull);
+
+ Assert(!isnull);
+ bspec = (PartitionBoundSpec *) stringToNode(TextDatumGetCString(datum));
+
+ if (!isDefaultPartitionBound((linitial(bspec->lowerdatums))))
+ {
+ List *part_qual = get_qual_for_range(parent, bspec, true);
+
+ /*
+ * AND the constraints of the partition and add to
+ * or_expr_args
+ */
+ or_expr_args = lappend(or_expr_args, list_length(part_qual) > 1
+ ? makeBoolExpr(AND_EXPR, part_qual, -1)
+ : linitial(part_qual));
+ }
+ ReleaseSysCache(tuple);
}
- else
+
+ if (or_expr_args != NIL)
{
- if (partexprs_item == NULL)
- elog(ERROR, "wrong number of partition key expressions");
- keyCol = copyObject(lfirst(partexprs_item));
- partexprs_item = lnext(partexprs_item);
+ /* OR all the non-default partition constraints; then negate it */
+ result = lappend(result,
+ list_length(or_expr_args) > 1
+ ? makeBoolExpr(OR_EXPR, or_expr_args, -1)
+ : linitial(or_expr_args));
+ result = list_make1(makeBoolExpr(NOT_EXPR, result, -1));
}
+ else /* default is the only partition */
+ result = list_make1(makeBoolConst(true, false));
- nulltest = makeNode(NullTest);
- nulltest->arg = keyCol;
- nulltest->nulltesttype = IS_NOT_NULL;
- nulltest->argisrow = false;
- nulltest->location = -1;
- result = lappend(result, nulltest);
+ return result;
}
+ lower_or_start_datum = list_head(spec->lowerdatums);
+ upper_or_start_datum = list_head(spec->upperdatums);
+ num_or_arms = key->partnatts;
+
+ if (!for_default)
+ result = get_range_nulltest(key);
+
/*
* Iterate over the key columns and check if the corresponding lower and
* upper datums are equal using the btree equality operator for the
@@ -2014,7 +2118,9 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
/* As noted above, caller expects the list to be non-empty. */
if (result == NIL)
- result = list_make1(makeBoolConst(true, false));
+ result = for_default
+ ? get_range_nulltest(key)
+ : list_make1(makeBoolConst(true, false));
return result;
}
@@ -2182,8 +2288,7 @@ get_partition_for_tuple(PartitionDispatch *pd,
Datum values[PARTITION_MAX_KEYS];
bool isnull[PARTITION_MAX_KEYS];
int cur_offset,
- cur_index;
- int i,
+ cur_index = -1,
result;
ExprContext *ecxt = GetPerTupleExprContext(estate);
TupleTableSlot *ecxt_scantuple_old = ecxt->ecxt_scantuple;
@@ -2227,25 +2332,6 @@ get_partition_for_tuple(PartitionDispatch *pd,
ecxt->ecxt_scantuple = slot;
FormPartitionKeyDatum(parent, slot, estate, values, isnull);
- if (key->strategy == PARTITION_STRATEGY_RANGE)
- {
- /*
- * Since we cannot route tuples with NULL partition keys through a
- * range-partitioned table, simply return that no partition exists
- */
- for (i = 0; i < key->partnatts; i++)
- {
- if (isnull[i])
- {
- *failed_at = parent;
- *failed_slot = slot;
- result = -1;
- goto error_exit;
- }
- }
- }
-
- cur_index = -1;
/*
* A null partition key is acceptable if null-accepting list partition
* or a default partition exists. Check if there exists a null
@@ -2365,7 +2451,17 @@ make_one_range_bound(PartitionKey key, int index, List *datums, bool lower)
i = 0;
foreach(lc, datums)
{
- PartitionRangeDatum *datum = castNode(PartitionRangeDatum, lfirst(lc));
+ Node *node = lfirst(lc);
+ PartitionRangeDatum *datum;
+
+ /* Is it default? */
+ if (IsA(node, DefElem))
+ {
+ bound->content[i++] = RANGE_DATUM_DEFAULT;
+ continue;
+ }
+
+ datum = castNode(PartitionRangeDatum, node);
/* What's contained in this range datum? */
bound->content[i] = !datum->infinite
@@ -2419,9 +2515,19 @@ 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 -1;
+ else
+ return 1;
+ }
if (content1[i] != RANGE_DATUM_FINITE &&
content2[i] != RANGE_DATUM_FINITE)
@@ -2472,6 +2578,9 @@ partition_rbound_datum_cmp(PartitionKey key,
for (i = 0; i < key->partnatts; i++)
{
+ if (rb_content[i] == RANGE_DATUM_DEFAULT)
+ continue;
+
if (rb_content[i] != RANGE_DATUM_FINITE)
return rb_content[i] == RANGE_DATUM_NEG_INF ? -1 : 1;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index d592651..b6ec9dc 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2679,7 +2679,6 @@ ForValues:
/*
* A default partition, that can be partition of either LIST or RANGE
* partitioned table.
- * Currently this is supported only for LIST partition.
*/
| DEFAULT
{
@@ -2687,6 +2686,10 @@ ForValues:
n->listdatums =
list_make1(makeDefElem("default_partition", NULL, @1));
+ n->lowerdatums =
+ list_make1(makeDefElem("default_partition", NULL, @1));
+ n->upperdatums =
+ list_make1(makeDefElem("default_partition", NULL, @1));
n->location = @1;
$$ = n;
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index bbc0dd6..0267491 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3368,10 +3368,29 @@ transformPartitionBound(ParseState *pstate, Relation parent,
bool seen_unbounded;
if (spec->strategy != PARTITION_STRATEGY_RANGE)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ {
+ /*
+ * In case of default partition, parser had no way to identify the
+ * partition strategy. Assign the parent strategy to default
+ * partition bound spec.
+ */
+ if ((list_length(spec->lowerdatums) == 1) &&
+ isDefaultPartitionBound((Node *) linitial(spec->lowerdatums)))
+ {
+ result_spec->strategy = PARTITION_STRATEGY_RANGE;
+
+ /*
+ * Default partition datums do not need any more
+ * transformations.
+ */
+ return result_spec;
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("invalid bound specification for a range partition"),
parser_errposition(pstate, exprLocation((Node *) spec))));
+ }
if (list_length(spec->lowerdatums) != partnatts)
ereport(ERROR,
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 47810ae..029d9c2 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8684,6 +8684,12 @@ get_rule_expr(Node *node, deparse_context *context,
list_length(spec->lowerdatums) ==
list_length(spec->upperdatums));
+ if (isDefaultPartitionBound(linitial(spec->lowerdatums)))
+ {
+ appendStringInfoString(buf, "DEFAULT");
+ break;
+ }
+
appendStringInfoString(buf, "FOR VALUES FROM (");
sep = "";
foreach(cell, spec->lowerdatums)
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index f60f459..16e7cf0 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3341,6 +3341,20 @@ CREATE TABLE part2 (
);
ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
INFO: partition constraint for table "part2" is implied by existing constraints
+-- default partition
+CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
+-- cannot attach 2nd default
+CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS);
+ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT;
+ERROR: partition "partr_def2" would overlap partition "partr_def1"
+-- cannot attach overlapping partition
+INSERT INTO partr_def1 VALUES (2, 10);
+CREATE TABLE part3 (LIKE range_parted);
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (2, 10) TO (2, 20);
+ERROR: new default partition constraint is violated by some row
+DETAIL: Violating row contains (2, 10).
+-- ok if no overlapping rows
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (3, 10) TO (3, 20);
-- check that leaf partitions are scanned when attaching a partitioned
-- table
CREATE TABLE part_5 (
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 92068ee..1471d28 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -517,11 +517,6 @@ ERROR: TO must specify exactly one value per partitioning column
-- cannot specify null values in range bounds
CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (unbounded);
ERROR: cannot specify NULL in range bound
--- range partition cannot have default partition
-CREATE TABLE fail_part PARTITION OF range_parted DEFAULT;
-ERROR: invalid bound specification for a range partition
-LINE 1: CREATE TABLE fail_part PARTITION OF range_parted DEFAULT;
- ^
-- cannot specify finite values after UNBOUNDED has been specified
CREATE TABLE range_parted_multicol (a int, b int, c int) PARTITION BY RANGE (a, b, c);
CREATE TABLE fail_part PARTITION OF range_parted_multicol FOR VALUES FROM (1, UNBOUNDED, 1) TO (UNBOUNDED, 1, 1);
@@ -604,6 +599,17 @@ CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30);
ERROR: partition "fail_part" would overlap partition "part2"
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50);
ERROR: partition "fail_part" would overlap partition "part3"
+-- range default partition
+CREATE TABLE range2_default PARTITION OF range_parted2 DEFAULT;
+-- cannot allow 2 default partition
+CREATE TABLE fail_default_part PARTITION OF range_parted2 DEFAULT;
+ERROR: partition "fail_default_part" would overlap partition "range2_default"
+-- check default overlap
+INSERT INTO range_parted2 VALUES (85);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90);
+ERROR: new default partition constraint is violated by some row
+DETAIL: Violating row contains (85).
+CREATE TABLE part4 PARTITION OF range_parted2 FOR VALUES FROM (90) TO (100);
-- now check for multi-column range partition key
CREATE TABLE range_parted3 (
a int,
@@ -617,6 +623,7 @@ CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10)
CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, unbounded);
CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
ERROR: partition "fail_part" would overlap partition "part12"
+CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
-- cannot create a partition that says column b is allowed to range
-- from -infinity to +infinity, while there exist partitions that have
-- more specific ranges
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 841f7c3..aab4762 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -272,6 +272,15 @@ insert into range_parted values ('b', 10);
insert into range_parted values ('a');
ERROR: no partition of relation "range_parted" found for row
DETAIL: Partition key of the failing row contains (a, (b + 0)) = (a, null).
+-- Check default partition
+create table part_def partition of range_parted default;
+-- fail
+insert into part_def values ('b', 10);
+ERROR: new row for relation "part_def" violates partition constraint
+DETAIL: Failing row contains (b, 10).
+-- ok
+insert into part_def values ('c', 10);
+insert into range_parted values (null, null);
select tableoid::regclass, * from range_parted;
tableoid | a | b
----------+---+----
@@ -281,7 +290,9 @@ select tableoid::regclass, * from range_parted;
part3 | b | 1
part4 | b | 10
part4 | b | 10
-(6 rows)
+ part_def | c | 10
+ part_def | |
+(8 rows)
-- ok
insert into list_parted values (null, 1);
@@ -433,6 +444,35 @@ with ins (a, b, c) as
mlparted4 | 1 | 30 | 39
(5 rows)
+-- check multi-level default partition
+create table mlparted_def partition of mlparted default partition by range(a);
+create table mlparted_def1 partition of mlparted_def for values from (40) to (50);
+create table mlparted_def2 partition of mlparted_def for values from (50) to (60);
+insert into mlparted values (40, 100);
+insert into mlparted_def1 values (42, 100);
+insert into mlparted_def2 values (54, 50);
+-- fail
+insert into mlparted values (70, 100);
+ERROR: no partition of relation "mlparted_def" found for row
+DETAIL: Partition key of the failing row contains (a) = (70).
+insert into mlparted_def1 values (52, 50);
+ERROR: new row for relation "mlparted_def1" violates partition constraint
+DETAIL: Failing row contains (52, 50).
+insert into mlparted_def2 values (34, 50);
+ERROR: new row for relation "mlparted_def2" violates partition constraint
+DETAIL: Failing row contains (34, 50).
+-- ok
+create table mlparted_defd partition of mlparted_def default;
+insert into mlparted values (70, 100);
+select tableoid::regclass, * from mlparted_def;
+ tableoid | a | b
+---------------+----+-----
+ mlparted_def1 | 40 | 100
+ mlparted_def1 | 42 | 100
+ mlparted_def2 | 54 | 50
+ mlparted_defd | 70 | 100
+(4 rows)
+
-- check that message shown after failure to find a partition shows the
-- appropriate key description (or none) in various situations
create table key_desc (a int, b int) partition by list ((a+0));
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 6750152..e996640 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -77,6 +77,10 @@ mlparted12|f
mlparted2|f
mlparted3|f
mlparted4|f
+mlparted_def|f
+mlparted_def1|f
+mlparted_def2|f
+mlparted_defd|f
money_data|f
num_data|f
num_exp_add|t
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index 0f41e7e..70f3748 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -218,6 +218,15 @@ ERROR: new row for relation "part_b_10_b_20" violates partition constraint
DETAIL: Failing row contains (b, 9).
-- ok
update range_parted set b = b + 1 where b = 10;
+-- default
+create table part_def partition of range_parted default;
+insert into range_parted values ('c', 9);
+-- ok
+update part_def set a = 'd' where a = 'c';
+-- fail
+update part_def set a = 'a' where a = 'd';
+ERROR: new row for relation "part_def" violates partition constraint
+DETAIL: Failing row contains (a, 9).
create table list_parted (
a text,
b int
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 8562c12..f8eb971 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2169,6 +2169,18 @@ CREATE TABLE part2 (
);
ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
+-- default partition
+CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
+-- cannot attach 2nd default
+CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS);
+ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT;
+-- cannot attach overlapping partition
+INSERT INTO partr_def1 VALUES (2, 10);
+CREATE TABLE part3 (LIKE range_parted);
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (2, 10) TO (2, 20);
+-- ok if no overlapping rows
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (3, 10) TO (3, 20);
+
-- check that leaf partitions are scanned when attaching a partitioned
-- table
CREATE TABLE part_5 (
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index f44c0e0..559596d 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -486,8 +486,6 @@ CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a') TO ('z',
-- cannot specify null values in range bounds
CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (unbounded);
--- range partition cannot have default partition
-CREATE TABLE fail_part PARTITION OF range_parted DEFAULT;
-- cannot specify finite values after UNBOUNDED has been specified
CREATE TABLE range_parted_multicol (a int, b int, c int) PARTITION BY RANGE (a, b, c);
@@ -559,6 +557,15 @@ CREATE TABLE part3 PARTITION OF range_parted2 FOR VALUES FROM (30) TO (40);
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30);
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50);
+-- range default partition
+CREATE TABLE range2_default PARTITION OF range_parted2 DEFAULT;
+-- cannot allow 2 default partition
+CREATE TABLE fail_default_part PARTITION OF range_parted2 DEFAULT;
+-- check default overlap
+INSERT INTO range_parted2 VALUES (85);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90);
+CREATE TABLE part4 PARTITION OF range_parted2 FOR VALUES FROM (90) TO (100);
+
-- now check for multi-column range partition key
CREATE TABLE range_parted3 (
a int,
@@ -572,6 +579,7 @@ CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO
CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10);
CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, unbounded);
CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
+CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
-- cannot create a partition that says column b is allowed to range
-- from -infinity to +infinity, while there exist partitions that have
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index a9f2289..39f17d5 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -169,8 +169,16 @@ insert into range_parted values ('b', 1);
insert into range_parted values ('b', 10);
-- fail (partition key (b+0) is null)
insert into range_parted values ('a');
-select tableoid::regclass, * from range_parted;
+-- Check default partition
+create table part_def partition of range_parted default;
+-- fail
+insert into part_def values ('b', 10);
+-- ok
+insert into part_def values ('c', 10);
+insert into range_parted values (null, null);
+
+select tableoid::regclass, * from range_parted;
-- ok
insert into list_parted values (null, 1);
insert into list_parted (a) values ('aA');
@@ -278,6 +286,23 @@ with ins (a, b, c) as
(insert into mlparted (b, a) select s.a, 1 from generate_series(2, 39) s(a) returning tableoid::regclass, *)
select a, b, min(c), max(c) from ins group by a, b order by 1;
+-- check multi-level default partition
+create table mlparted_def partition of mlparted default partition by range(a);
+create table mlparted_def1 partition of mlparted_def for values from (40) to (50);
+create table mlparted_def2 partition of mlparted_def for values from (50) to (60);
+insert into mlparted values (40, 100);
+insert into mlparted_def1 values (42, 100);
+insert into mlparted_def2 values (54, 50);
+-- fail
+insert into mlparted values (70, 100);
+insert into mlparted_def1 values (52, 50);
+insert into mlparted_def2 values (34, 50);
+-- ok
+create table mlparted_defd partition of mlparted_def default;
+insert into mlparted values (70, 100);
+
+select tableoid::regclass, * from mlparted_def;
+
-- check that message shown after failure to find a partition shows the
-- appropriate key description (or none) in various situations
create table key_desc (a int, b int) partition by list ((a+0));
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 02840a4..b2902ab 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -125,6 +125,14 @@ update range_parted set b = b - 1 where b = 10;
-- ok
update range_parted set b = b + 1 where b = 10;
+-- default
+create table part_def partition of range_parted default;
+insert into range_parted values ('c', 9);
+-- ok
+update part_def set a = 'd' where a = 'c';
+-- fail
+update part_def set a = 'a' where a = 'd';
+
create table list_parted (
a text,
b int
On Wed, May 31, 2017 at 5:53 PM, Beena Emerson <memissemerson@gmail.com> wrote:
Hello,
PFA the updated patch.
Dependent patch default_partition_v17.patch [1]
This patch adds regression tests and removes the separate function to
get default partition qual.On Mon, May 29, 2017 at 3:22 PM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:Hi Beena,
I went through your patch, and here are some of my comments:
- For generating a qual for default range partition, instead of scanning for
all
the children and collecting all the boundspecs, how about creating and
negating
an expression from the lists of lowerdatums and upperdatums in boundinfo.Unlike list, range partition can be for multiple columns and the
expressions get complicated. I have used the same logic of looping
through different partitions and negating the ORed expr. However, this
is now done under get_qual_for_range.- Wrong comment: + int default_index; /* Index of the default list partition. */Comment is made generic in the dependent patch.
- Suggested by Robert earlier on default list partitioning thread, instead
of
abbreviating is_def/found_def use is(found)_default etc.corrected.
- unrelated change: - List *all_parts; + List *all_parts;undone.
- typo: partiton should be partition, similar typo at other places too. + * A non-default range partiton table does not currently allow partition keysrectified.
- Useless hunk for this patch: - Oid defid; + Oid defid;undone.
- better to use IsA here, instead of doing explicit check: + bound->content[i] = (datum->type == T_DefElem) + ? RANGE_DATUM_DEFAULTModified
- It is better to use head of either lowerdatums or upperdatums list to
verify
if its a default partition and get rid of the constant PARTITION_DEFAULT
altogether.modified this part as necessary.
- In the function get_qual_from_partbound() is_def is always going to be
false
for range partition, the function get_qual_for_range can be directly passed
false instead.- Following comment for function get_qual_for_range_default() implies that this function returns bool, but the actually it returns a List. + * + * If DEFAULT is the only partiton for the table then this returns TRUE. + *Updated.
[1] http://www.mail-archive.com/pgsql-hackers@postgresql.org/msg315573.html
Hi Beena,
I had a look at the patch from the angle of aesthetics and there are a
few cosmetic changes I might suggest. Please have a look at the
attached patch and if you agree with those changes you may merge it on
your patch. The patch also fixes a couple of more spelling mistakes
unrelated to this patch.
--
Regards,
Rafia Sabih
EnterpriseDB: http://www.enterprisedb.com/
Attachments:
cosmetic_range_default_partition.patchapplication/octet-stream; name=cosmetic_range_default_partition.patchDownload
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index c81a329905..c6fffd1c89 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -628,7 +628,7 @@ RelationBuildPartitionDesc(Relation rel)
/*
* Now assign OIDs from the original array into mapped indexes of the
* result array. Order of OIDs in the former is defined by the
- * catalog scan that retrived them, whereas that in the latter is
+ * catalog scan that retrieved them, whereas that in the latter is
* defined by canonicalized representation of the list values or the
* range bounds.
*/
@@ -1431,7 +1431,7 @@ get_partition_operator(PartitionKey key, int col, StrategyNumber 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
+ * operator 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
@@ -1810,7 +1810,7 @@ get_range_nulltest(PartitionKey key)
* partition keys.
*
* For default partition, it returns the negation of the constraints of all
- * other partitions.
+ * the other partitions.
*
* If we end up with an empty result list, we return a single-member list
* containing a constant TRUE, because callers expect a non-empty list.
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 8e332ebf62..bc5f9c6d62 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3341,19 +3341,19 @@ CREATE TABLE part2 (
);
ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
INFO: partition constraint for table "part2" is implied by existing constraints
--- default partition
+-- Create default partition
CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
--- cannot attach 2nd default
+-- Only one default partition is allowed, hence, following should give error
CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS);
ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT;
ERROR: partition "partr_def2" would overlap partition "partr_def1"
--- cannot attach overlapping partition
+-- Overlapping partitions cannot be attached, hence, following should give error
INSERT INTO partr_def1 VALUES (2, 10);
CREATE TABLE part3 (LIKE range_parted);
ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (2, 10) TO (2, 20);
ERROR: new default partition constraint is violated by some row
DETAIL: Violating row contains (2, 10).
--- ok if no overlapping rows
+-- Attaching partitions should be successful when there are no overlapping rows
ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (3, 10) TO (3, 20);
-- check that leaf partitions are scanned when attaching a partitioned
-- table
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 57b56df46c..02a8e22832 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -599,12 +599,12 @@ CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30);
ERROR: partition "fail_part" would overlap partition "part2"
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50);
ERROR: partition "fail_part" would overlap partition "part3"
--- range default partition
+-- Create a default partition for range partitioned table
CREATE TABLE range2_default PARTITION OF range_parted2 DEFAULT;
--- cannot allow 2 default partition
+-- More than one default partition is not allowed, so this should give error
CREATE TABLE fail_default_part PARTITION OF range_parted2 DEFAULT;
ERROR: partition "fail_default_part" would overlap partition "range2_default"
--- check default overlap
+-- Check if the range for default partitions overlap
INSERT INTO range_parted2 VALUES (85);
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90);
ERROR: new default partition constraint is violated by some row
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 1885be70d6..61799ad7ec 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -274,11 +274,11 @@ ERROR: no partition of relation "range_parted" found for row
DETAIL: Partition key of the failing row contains (a, (b + 0)) = (a, null).
-- Check default partition
create table part_def partition of range_parted default;
--- fail
+-- Following statement should fail
insert into part_def values ('b', 10);
ERROR: new row for relation "part_def" violates partition constraint
DETAIL: Failing row contains (b, 10).
--- ok
+-- Following should complete successfully
insert into part_def values ('c', 10);
insert into range_parted values (null, null);
select tableoid::regclass, * from range_parted;
@@ -444,14 +444,14 @@ with ins (a, b, c) as
mlparted4 | 1 | 30 | 39
(5 rows)
--- check multi-level default partition
+-- Check multi-level default partition
create table mlparted_def partition of mlparted default partition by range(a);
create table mlparted_def1 partition of mlparted_def for values from (40) to (50);
create table mlparted_def2 partition of mlparted_def for values from (50) to (60);
insert into mlparted values (40, 100);
insert into mlparted_def1 values (42, 100);
insert into mlparted_def2 values (54, 50);
--- fail
+-- Following statements should fail
insert into mlparted values (70, 100);
ERROR: no partition of relation "mlparted_def" found for row
DETAIL: Partition key of the failing row contains (a) = (70).
@@ -461,7 +461,7 @@ DETAIL: Failing row contains (52, 50).
insert into mlparted_def2 values (34, 50);
ERROR: new row for relation "mlparted_def2" violates partition constraint
DETAIL: Failing row contains (34, 50).
--- ok
+-- Following statements should complete successfully
create table mlparted_defd partition of mlparted_def default;
insert into mlparted values (70, 100);
select tableoid::regclass, * from mlparted_def;
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index 70f37488dd..488b622a03 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -218,12 +218,12 @@ ERROR: new row for relation "part_b_10_b_20" violates partition constraint
DETAIL: Failing row contains (b, 9).
-- ok
update range_parted set b = b + 1 where b = 10;
--- default
+-- Creating default partition for range
create table part_def partition of range_parted default;
insert into range_parted values ('c', 9);
--- ok
+-- Following statements should complete successfully
update part_def set a = 'd' where a = 'c';
--- fail
+-- Following statements should fail
update part_def set a = 'a' where a = 'd';
ERROR: new row for relation "part_def" violates partition constraint
DETAIL: Failing row contains (a, 9).
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index db9cc0ad30..99f8155953 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2169,16 +2169,19 @@ CREATE TABLE part2 (
);
ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
--- default partition
+-- Create default partition
CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
--- cannot attach 2nd default
+
+-- Only one default partition is allowed, hence, following should give error
CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS);
ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT;
--- cannot attach overlapping partition
+
+-- Overlapping partitions cannot be attached, hence, following should give error
INSERT INTO partr_def1 VALUES (2, 10);
CREATE TABLE part3 (LIKE range_parted);
ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (2, 10) TO (2, 20);
--- ok if no overlapping rows
+
+-- Attaching partitions should be successful when there are no overlapping rows
ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (3, 10) TO (3, 20);
-- check that leaf partitions are scanned when attaching a partitioned
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 556e2b88ce..2011cbf2c0 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -557,11 +557,13 @@ CREATE TABLE part3 PARTITION OF range_parted2 FOR VALUES FROM (30) TO (40);
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30);
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50);
--- range default partition
+-- Create a default partition for range partitioned table
CREATE TABLE range2_default PARTITION OF range_parted2 DEFAULT;
--- cannot allow 2 default partition
+
+-- More than one default partition is not allowed, so this should give error
CREATE TABLE fail_default_part PARTITION OF range_parted2 DEFAULT;
--- check default overlap
+
+-- Check if the range for default partitions overlap
INSERT INTO range_parted2 VALUES (85);
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90);
CREATE TABLE part4 PARTITION OF range_parted2 FOR VALUES FROM (90) TO (100);
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 7f040f4cc5..41ff17651d 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -172,9 +172,11 @@ insert into range_parted values ('a');
-- Check default partition
create table part_def partition of range_parted default;
--- fail
+
+-- Following statement should fail
insert into part_def values ('b', 10);
--- ok
+
+-- Following should complete successfully
insert into part_def values ('c', 10);
insert into range_parted values (null, null);
@@ -286,18 +288,20 @@ with ins (a, b, c) as
(insert into mlparted (b, a) select s.a, 1 from generate_series(2, 39) s(a) returning tableoid::regclass, *)
select a, b, min(c), max(c) from ins group by a, b order by 1;
--- check multi-level default partition
+-- Check multi-level default partition
create table mlparted_def partition of mlparted default partition by range(a);
create table mlparted_def1 partition of mlparted_def for values from (40) to (50);
create table mlparted_def2 partition of mlparted_def for values from (50) to (60);
insert into mlparted values (40, 100);
insert into mlparted_def1 values (42, 100);
insert into mlparted_def2 values (54, 50);
--- fail
+
+-- Following statements should fail
insert into mlparted values (70, 100);
insert into mlparted_def1 values (52, 50);
insert into mlparted_def2 values (34, 50);
--- ok
+
+-- Following statements should complete successfully
create table mlparted_defd partition of mlparted_def default;
insert into mlparted values (70, 100);
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index b2902ab83d..3ff61fea9b 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -125,12 +125,14 @@ update range_parted set b = b - 1 where b = 10;
-- ok
update range_parted set b = b + 1 where b = 10;
--- default
+-- Creating default partition for range
create table part_def partition of range_parted default;
insert into range_parted values ('c', 9);
--- ok
+
+-- Following statements should complete successfully
update part_def set a = 'd' where a = 'c';
--- fail
+
+-- Following statements should fail
update part_def set a = 'a' where a = 'd';
create table list_parted (
On Fri, Jun 2, 2017 at 4:12 PM, Rafia Sabih
<rafia.sabih@enterprisedb.com> wrote:
On Wed, May 31, 2017 at 5:53 PM, Beena Emerson <memissemerson@gmail.com> wrote:
Hi Beena,
I had a look at the patch from the angle of aesthetics and there are a
few cosmetic changes I might suggest. Please have a look at the
attached patch and if you agree with those changes you may merge it on
your patch. The patch also fixes a couple of more spelling mistakes
unrelated to this patch.
I think if you have found spelling mistakes unrelated to this patch,
then it is better to submit those as a separate patch in a new thread.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Jun 2, 2017 at 8:09 AM, Amit Kapila <amit.kapila16@gmail.com> wrote:
I think if you have found spelling mistakes unrelated to this patch,
then it is better to submit those as a separate patch in a new thread.
+1.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Jun 2, 2017 at 5:48 PM, Robert Haas <robertmhaas@gmail.com> wrote:
On Fri, Jun 2, 2017 at 8:09 AM, Amit Kapila <amit.kapila16@gmail.com> wrote:
I think if you have found spelling mistakes unrelated to this patch,
then it is better to submit those as a separate patch in a new thread.+1.
Sure, attached is the version without those changes.
--
Regards,
Rafia Sabih
EnterpriseDB: http://www.enterprisedb.com/
Attachments:
cosmetic_range_default_partition_v2.patchapplication/octet-stream; name=cosmetic_range_default_partition_v2.patchDownload
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index c81a329905..8e01f3901d 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -1810,7 +1810,7 @@ get_range_nulltest(PartitionKey key)
* partition keys.
*
* For default partition, it returns the negation of the constraints of all
- * other partitions.
+ * the other partitions.
*
* If we end up with an empty result list, we return a single-member list
* containing a constant TRUE, because callers expect a non-empty list.
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 8e332ebf62..bc5f9c6d62 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3341,19 +3341,19 @@ CREATE TABLE part2 (
);
ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
INFO: partition constraint for table "part2" is implied by existing constraints
--- default partition
+-- Create default partition
CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
--- cannot attach 2nd default
+-- Only one default partition is allowed, hence, following should give error
CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS);
ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT;
ERROR: partition "partr_def2" would overlap partition "partr_def1"
--- cannot attach overlapping partition
+-- Overlapping partitions cannot be attached, hence, following should give error
INSERT INTO partr_def1 VALUES (2, 10);
CREATE TABLE part3 (LIKE range_parted);
ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (2, 10) TO (2, 20);
ERROR: new default partition constraint is violated by some row
DETAIL: Violating row contains (2, 10).
--- ok if no overlapping rows
+-- Attaching partitions should be successful when there are no overlapping rows
ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (3, 10) TO (3, 20);
-- check that leaf partitions are scanned when attaching a partitioned
-- table
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 57b56df46c..02a8e22832 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -599,12 +599,12 @@ CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30);
ERROR: partition "fail_part" would overlap partition "part2"
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50);
ERROR: partition "fail_part" would overlap partition "part3"
--- range default partition
+-- Create a default partition for range partitioned table
CREATE TABLE range2_default PARTITION OF range_parted2 DEFAULT;
--- cannot allow 2 default partition
+-- More than one default partition is not allowed, so this should give error
CREATE TABLE fail_default_part PARTITION OF range_parted2 DEFAULT;
ERROR: partition "fail_default_part" would overlap partition "range2_default"
--- check default overlap
+-- Check if the range for default partitions overlap
INSERT INTO range_parted2 VALUES (85);
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90);
ERROR: new default partition constraint is violated by some row
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 1885be70d6..61799ad7ec 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -274,11 +274,11 @@ ERROR: no partition of relation "range_parted" found for row
DETAIL: Partition key of the failing row contains (a, (b + 0)) = (a, null).
-- Check default partition
create table part_def partition of range_parted default;
--- fail
+-- Following statement should fail
insert into part_def values ('b', 10);
ERROR: new row for relation "part_def" violates partition constraint
DETAIL: Failing row contains (b, 10).
--- ok
+-- Following should complete successfully
insert into part_def values ('c', 10);
insert into range_parted values (null, null);
select tableoid::regclass, * from range_parted;
@@ -444,14 +444,14 @@ with ins (a, b, c) as
mlparted4 | 1 | 30 | 39
(5 rows)
--- check multi-level default partition
+-- Check multi-level default partition
create table mlparted_def partition of mlparted default partition by range(a);
create table mlparted_def1 partition of mlparted_def for values from (40) to (50);
create table mlparted_def2 partition of mlparted_def for values from (50) to (60);
insert into mlparted values (40, 100);
insert into mlparted_def1 values (42, 100);
insert into mlparted_def2 values (54, 50);
--- fail
+-- Following statements should fail
insert into mlparted values (70, 100);
ERROR: no partition of relation "mlparted_def" found for row
DETAIL: Partition key of the failing row contains (a) = (70).
@@ -461,7 +461,7 @@ DETAIL: Failing row contains (52, 50).
insert into mlparted_def2 values (34, 50);
ERROR: new row for relation "mlparted_def2" violates partition constraint
DETAIL: Failing row contains (34, 50).
--- ok
+-- Following statements should complete successfully
create table mlparted_defd partition of mlparted_def default;
insert into mlparted values (70, 100);
select tableoid::regclass, * from mlparted_def;
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index 70f37488dd..488b622a03 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -218,12 +218,12 @@ ERROR: new row for relation "part_b_10_b_20" violates partition constraint
DETAIL: Failing row contains (b, 9).
-- ok
update range_parted set b = b + 1 where b = 10;
--- default
+-- Creating default partition for range
create table part_def partition of range_parted default;
insert into range_parted values ('c', 9);
--- ok
+-- Following statements should complete successfully
update part_def set a = 'd' where a = 'c';
--- fail
+-- Following statements should fail
update part_def set a = 'a' where a = 'd';
ERROR: new row for relation "part_def" violates partition constraint
DETAIL: Failing row contains (a, 9).
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index db9cc0ad30..99f8155953 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2169,16 +2169,19 @@ CREATE TABLE part2 (
);
ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
--- default partition
+-- Create default partition
CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
--- cannot attach 2nd default
+
+-- Only one default partition is allowed, hence, following should give error
CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS);
ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT;
--- cannot attach overlapping partition
+
+-- Overlapping partitions cannot be attached, hence, following should give error
INSERT INTO partr_def1 VALUES (2, 10);
CREATE TABLE part3 (LIKE range_parted);
ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (2, 10) TO (2, 20);
--- ok if no overlapping rows
+
+-- Attaching partitions should be successful when there are no overlapping rows
ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (3, 10) TO (3, 20);
-- check that leaf partitions are scanned when attaching a partitioned
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 556e2b88ce..2011cbf2c0 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -557,11 +557,13 @@ CREATE TABLE part3 PARTITION OF range_parted2 FOR VALUES FROM (30) TO (40);
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30);
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50);
--- range default partition
+-- Create a default partition for range partitioned table
CREATE TABLE range2_default PARTITION OF range_parted2 DEFAULT;
--- cannot allow 2 default partition
+
+-- More than one default partition is not allowed, so this should give error
CREATE TABLE fail_default_part PARTITION OF range_parted2 DEFAULT;
--- check default overlap
+
+-- Check if the range for default partitions overlap
INSERT INTO range_parted2 VALUES (85);
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90);
CREATE TABLE part4 PARTITION OF range_parted2 FOR VALUES FROM (90) TO (100);
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 7f040f4cc5..41ff17651d 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -172,9 +172,11 @@ insert into range_parted values ('a');
-- Check default partition
create table part_def partition of range_parted default;
--- fail
+
+-- Following statement should fail
insert into part_def values ('b', 10);
--- ok
+
+-- Following should complete successfully
insert into part_def values ('c', 10);
insert into range_parted values (null, null);
@@ -286,18 +288,20 @@ with ins (a, b, c) as
(insert into mlparted (b, a) select s.a, 1 from generate_series(2, 39) s(a) returning tableoid::regclass, *)
select a, b, min(c), max(c) from ins group by a, b order by 1;
--- check multi-level default partition
+-- Check multi-level default partition
create table mlparted_def partition of mlparted default partition by range(a);
create table mlparted_def1 partition of mlparted_def for values from (40) to (50);
create table mlparted_def2 partition of mlparted_def for values from (50) to (60);
insert into mlparted values (40, 100);
insert into mlparted_def1 values (42, 100);
insert into mlparted_def2 values (54, 50);
--- fail
+
+-- Following statements should fail
insert into mlparted values (70, 100);
insert into mlparted_def1 values (52, 50);
insert into mlparted_def2 values (34, 50);
--- ok
+
+-- Following statements should complete successfully
create table mlparted_defd partition of mlparted_def default;
insert into mlparted values (70, 100);
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index b2902ab83d..3ff61fea9b 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -125,12 +125,14 @@ update range_parted set b = b - 1 where b = 10;
-- ok
update range_parted set b = b + 1 where b = 10;
--- default
+-- Creating default partition for range
create table part_def partition of range_parted default;
insert into range_parted values ('c', 9);
--- ok
+
+-- Following statements should complete successfully
update part_def set a = 'd' where a = 'c';
--- fail
+
+-- Following statements should fail
update part_def set a = 'a' where a = 'd';
create table list_parted (
Hello,
On Sun, Jun 4, 2017 at 9:26 AM, Rafia Sabih
<rafia.sabih@enterprisedb.com> wrote:
On Fri, Jun 2, 2017 at 5:48 PM, Robert Haas <robertmhaas@gmail.com> wrote:
On Fri, Jun 2, 2017 at 8:09 AM, Amit Kapila <amit.kapila16@gmail.com> wrote:
I think if you have found spelling mistakes unrelated to this patch,
then it is better to submit those as a separate patch in a new thread.+1.
Sure, attached is the version without those changes.
Thank you for your comments. I have applied most of the changes. The
regression comment change from 'fail' -> ' Following statement should
fail' and 'ok' -> 'Following should complete successfully' is ignored
since other tests in the file had similar comments
The new patch is rebased over default_partition_v18.patch
[http://www.mail-archive.com/pgsql-hackers@postgresql.org/msg315831.html]
--
Beena Emerson
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Attachments:
default_range_partition_v3.patchapplication/octet-stream; name=default_range_partition_v3.patchDownload
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 4486989..4915db9 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 */
} RangeDatumContent;
typedef struct PartitionBoundInfoData
@@ -137,7 +138,9 @@ static void get_range_key_properties(PartitionKey key, int keynum,
Expr **keyCol,
Const **lower_val, Const **upper_val);
static List *get_qual_for_list(Relation parent, PartitionBoundSpec *spec);
-static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
+ bool for_default);
+static List *get_range_nulltest(PartitionKey key);
static List *generate_partition_qual(Relation rel);
static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index,
@@ -180,11 +183,11 @@ RelationBuildPartitionDesc(Relation rel)
MemoryContext oldcxt;
int ndatums = 0;
+ int default_index = -1;
/* List partitioning specific */
PartitionListValue **all_values = NULL;
int null_index = -1;
- int default_index = -1;
/* Range partitioning specific */
PartitionRangeBound **rbounds = NULL;
@@ -359,6 +362,9 @@ RelationBuildPartitionDesc(Relation rel)
if (spec->strategy != PARTITION_STRATEGY_RANGE)
elog(ERROR, "invalid strategy in partition bound spec");
+ if (spec->is_default)
+ default_index = i;
+
lower = make_one_range_bound(key, i, spec->lowerdatums,
true);
upper = make_one_range_bound(key, i, spec->upperdatums,
@@ -556,6 +562,7 @@ RelationBuildPartitionDesc(Relation rel)
sizeof(RangeDatumContent *));
boundinfo->indexes = (int *) palloc((ndatums + 1) *
sizeof(int));
+ boundinfo->default_index = -1;
for (i = 0; i < ndatums; i++)
{
@@ -577,6 +584,13 @@ RelationBuildPartitionDesc(Relation rel)
boundinfo->content[i][j] = rbounds[i]->content[j];
}
+ /* Assign mapping index for default partition. */
+ if (default_index != -1 && mapping[default_index] == -1)
+ {
+ mapping[default_index] = next_index++;
+ boundinfo->default_index = mapping[default_index];
+ }
+
/*
* There is no mapping for invalid indexes.
*
@@ -599,10 +613,8 @@ RelationBuildPartitionDesc(Relation rel)
boundinfo->indexes[i] = mapping[orig_index];
}
}
- boundinfo->indexes[i] = -1;
- /* As of now, we do not support default range partition. */
- boundinfo->default_index = -1;
+ boundinfo->indexes[i] = -1;
break;
}
@@ -797,8 +809,9 @@ check_new_partition_bound(char *relname, Relation parent,
* First check if the resulting range would be empty with
* specified lower and upper bounds
*/
- if (partition_rbound_cmp(key, lower->datums, lower->content, true,
- upper) >= 0)
+ if ((lower->content[0] != RANGE_DATUM_DEFAULT) &&
+ 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"),
@@ -815,8 +828,22 @@ check_new_partition_bound(char *relname, Relation parent,
boundinfo->strategy == PARTITION_STRATEGY_RANGE);
/*
- * Firstly, find the greatest range bound that is less
- * than or equal to the new lower bound.
+ * Default partition cannot be added if there already
+ * exists one.
+ */
+ if ((lower->content[0] == RANGE_DATUM_DEFAULT))
+ {
+ if (partition_bound_has_default(partdesc->boundinfo))
+ {
+ overlap = true;
+ with = partdesc->boundinfo->default_index;
+ }
+ break;
+ }
+
+ /*
+ * 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);
@@ -934,11 +961,9 @@ check_default_allows_bound(Relation parent, PartitionBoundSpec *new_spec)
List *all_parts;
ListCell *lc;
- /* Currently default partition is supported only for LIST partition. */
- if (new_spec->strategy != PARTITION_STRATEGY_LIST)
- return;
-
- new_part_constraints = get_qual_for_list(parent, new_spec);
+ new_part_constraints = (new_spec->strategy == PARTITION_STRATEGY_LIST)
+ ? get_qual_for_list(parent, new_spec)
+ : get_qual_for_range(parent, new_spec, false);
new_part_constraints = (List *) eval_const_expressions(NULL,
(Node *) new_part_constraints);
new_part_constraints =
@@ -1101,7 +1126,8 @@ get_qual_from_partbound(Relation rel, Relation parent,
case PARTITION_STRATEGY_RANGE:
Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
- my_qual = get_qual_for_range(key, spec);
+
+ my_qual = get_qual_for_range(parent, spec, false);
break;
default:
@@ -1683,6 +1709,53 @@ get_range_key_properties(PartitionKey key, int keynum,
*upper_val = NULL;
}
+ /*
+ * get_range_nulltest
+ *
+ * A non-default range partition table does not currently allow partition
+ * keys to be null, so emit an IS NOT NULL expression for each key column.
+ */
+static List *
+get_range_nulltest(PartitionKey key)
+{
+ List *result = NIL;
+ NullTest *nulltest;
+ ListCell *partexprs_item;
+ int i;
+
+ partexprs_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ Expr *keyCol;
+
+ if (key->partattrs[i] != 0)
+ {
+ keyCol = (Expr *) makeVar(1,
+ key->partattrs[i],
+ key->parttypid[i],
+ key->parttypmod[i],
+ key->parttypcoll[i],
+ 0);
+ }
+ else
+ {
+ if (partexprs_item == NULL)
+ elog(ERROR, "wrong number of partition key expressions");
+ keyCol = copyObject(lfirst(partexprs_item));
+ partexprs_item = lnext(partexprs_item);
+ }
+
+ nulltest = makeNode(NullTest);
+ nulltest->arg = keyCol;
+ nulltest->nulltesttype = IS_NOT_NULL;
+ nulltest->argisrow = false;
+ nulltest->location = -1;
+ result = lappend(result, nulltest);
+ }
+
+ return result;
+}
+
/*
* get_qual_for_range
*
@@ -1723,11 +1796,15 @@ get_range_key_properties(PartitionKey key, int keynum,
* does not really have a constraint, except the IS NOT NULL constraint for
* partition keys.
*
+ * For default partition, it returns the negation of the constraints of all
+ * the other partitions.
+ *
* If we end up with an empty result list, we return a single-member list
* containing a constant TRUE, because callers expect a non-empty list.
*/
static List *
-get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
+get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
+ bool for_default)
{
List *result = NIL;
ListCell *cell1,
@@ -1738,10 +1815,10 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
j;
PartitionRangeDatum *ldatum,
*udatum;
+ PartitionKey key = RelationGetPartitionKey(parent);
Expr *keyCol;
Const *lower_val,
*upper_val;
- NullTest *nulltest;
List *lower_or_arms,
*upper_or_arms;
int num_or_arms,
@@ -1751,44 +1828,70 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
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;
-
- /*
- * A range-partitioned table does not currently allow partition keys to be
- * null, so emit an IS NOT NULL expression for each key column.
- */
- partexprs_item = list_head(key->partexprs);
- for (i = 0; i < key->partnatts; i++)
+ if (spec->is_default)
{
- Expr *keyCol;
+ List *or_expr_args = NIL;
+ PartitionDesc pdesc = RelationGetPartitionDesc(parent);
+ Oid *inhoids = pdesc->oids;
+ int nparts = pdesc->nparts,
+ i;
- if (key->partattrs[i] != 0)
+ for (i = 0; i < nparts; i++)
{
- keyCol = (Expr *) makeVar(1,
- key->partattrs[i],
- key->parttypid[i],
- key->parttypmod[i],
- key->parttypcoll[i],
- 0);
+ Oid inhrelid = inhoids[i];
+ HeapTuple tuple;
+ Datum datum;
+ bool isnull;
+ PartitionBoundSpec *bspec;
+
+ tuple = SearchSysCache1(RELOID, inhrelid);
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", inhrelid);
+
+ datum = SysCacheGetAttr(RELOID, tuple,
+ Anum_pg_class_relpartbound,
+ &isnull);
+
+ Assert(!isnull);
+ bspec = (PartitionBoundSpec *) stringToNode(TextDatumGetCString(datum));
+
+ if (!bspec->is_default)
+ {
+ List *part_qual = get_qual_for_range(parent, bspec, true);
+
+ /*
+ * AND the constraints of the partition and add to
+ * or_expr_args
+ */
+ or_expr_args = lappend(or_expr_args, list_length(part_qual) > 1
+ ? makeBoolExpr(AND_EXPR, part_qual, -1)
+ : linitial(part_qual));
+ }
+ ReleaseSysCache(tuple);
}
- else
+
+ if (or_expr_args != NIL)
{
- if (partexprs_item == NULL)
- elog(ERROR, "wrong number of partition key expressions");
- keyCol = copyObject(lfirst(partexprs_item));
- partexprs_item = lnext(partexprs_item);
+ /* OR all the non-default partition constraints; then negate it */
+ result = lappend(result,
+ list_length(or_expr_args) > 1
+ ? makeBoolExpr(OR_EXPR, or_expr_args, -1)
+ : linitial(or_expr_args));
+ result = list_make1(makeBoolExpr(NOT_EXPR, result, -1));
}
+ else /* default is the only partition */
+ result = list_make1(makeBoolConst(true, false));
- nulltest = makeNode(NullTest);
- nulltest->arg = keyCol;
- nulltest->nulltesttype = IS_NOT_NULL;
- nulltest->argisrow = false;
- nulltest->location = -1;
- result = lappend(result, nulltest);
+ return result;
}
+ lower_or_start_datum = list_head(spec->lowerdatums);
+ upper_or_start_datum = list_head(spec->upperdatums);
+ num_or_arms = key->partnatts;
+
+ if (!for_default)
+ result = get_range_nulltest(key);
+
/*
* Iterate over the key columns and check if the corresponding lower and
* upper datums are equal using the btree equality operator for the
@@ -2005,7 +2108,9 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
/* As noted above, caller expects the list to be non-empty. */
if (result == NIL)
- result = list_make1(makeBoolConst(true, false));
+ result = for_default
+ ? get_range_nulltest(key)
+ : list_make1(makeBoolConst(true, false));
return result;
}
@@ -2173,8 +2278,7 @@ get_partition_for_tuple(PartitionDispatch *pd,
Datum values[PARTITION_MAX_KEYS];
bool isnull[PARTITION_MAX_KEYS];
int cur_offset,
- cur_index;
- int i,
+ cur_index = -1,
result;
ExprContext *ecxt = GetPerTupleExprContext(estate);
TupleTableSlot *ecxt_scantuple_old = ecxt->ecxt_scantuple;
@@ -2218,24 +2322,6 @@ get_partition_for_tuple(PartitionDispatch *pd,
ecxt->ecxt_scantuple = slot;
FormPartitionKeyDatum(parent, slot, estate, values, isnull);
- if (key->strategy == PARTITION_STRATEGY_RANGE)
- {
- /*
- * Since we cannot route tuples with NULL partition keys through a
- * range-partitioned table, simply return that no partition exists
- */
- for (i = 0; i < key->partnatts; i++)
- {
- if (isnull[i])
- {
- *failed_at = parent;
- *failed_slot = slot;
- result = -1;
- goto error_exit;
- }
- }
- }
-
/*
* Handle NULL partition key here if there's a null-accepting list
* partition. Else it will be routed to the default partition if one
@@ -2347,15 +2433,24 @@ make_one_range_bound(PartitionKey key, int index, List *datums, bool lower)
bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
bound->index = index;
- bound->datums = (Datum *) palloc0(key->partnatts * sizeof(Datum));
- bound->content = (RangeDatumContent *) palloc0(key->partnatts *
- sizeof(RangeDatumContent));
+ bound->datums = datums ? (Datum *) palloc0(key->partnatts * sizeof(Datum))
+ : NULL;
+ bound->content = (RangeDatumContent *) palloc0(
+ (datums ? key->partnatts : 1) * sizeof(RangeDatumContent));
bound->lower = lower;
i = 0;
+
+ /* If default, datums are NULL */
+ if (!datums)
+ bound->content[i] = RANGE_DATUM_DEFAULT;
+
foreach(lc, datums)
{
- PartitionRangeDatum *datum = castNode(PartitionRangeDatum, lfirst(lc));
+ Node *node = lfirst(lc);
+ PartitionRangeDatum *datum;
+
+ datum = castNode(PartitionRangeDatum, node);
/* What's contained in this range datum? */
bound->content[i] = !datum->infinite
@@ -2409,9 +2504,19 @@ 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 -1;
+ else
+ return 1;
+ }
if (content1[i] != RANGE_DATUM_FINITE &&
content2[i] != RANGE_DATUM_FINITE)
@@ -2462,6 +2567,9 @@ partition_rbound_datum_cmp(PartitionKey key,
for (i = 0; i < key->partnatts; i++)
{
+ if (rb_content[i] == RANGE_DATUM_DEFAULT)
+ continue;
+
if (rb_content[i] != RANGE_DATUM_FINITE)
return rb_content[i] == RANGE_DATUM_NEG_INF ? -1 : 1;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ac99859..3b86218 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2681,7 +2681,6 @@ ForValues:
/*
* A default partition, that can be partition of either LIST or
* RANGE partitioned table.
- * Currently this is supported only for LIST partition.
*/
| DEFAULT
{
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e861195..a92d77b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3286,11 +3286,6 @@ transformPartitionBound(ParseState *pstate, Relation parent,
if (spec->is_default)
{
- if (strategy != PARTITION_STRATEGY_LIST)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("default partition is supported only for list partitioned table")));
-
/*
* In case of the default partition, parser had no way to identify the
* partition strategy. Assign the parent strategy to the default
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 7b6bae7..43eab21 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8680,6 +8680,12 @@ get_rule_expr(Node *node, deparse_context *context,
list_length(spec->lowerdatums) ==
list_length(spec->upperdatums));
+ if (spec->is_default)
+ {
+ appendStringInfoString(buf, "DEFAULT");
+ break;
+ }
+
appendStringInfoString(buf, "FOR VALUES FROM (");
sep = "";
foreach(cell, spec->lowerdatums)
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 75f3617..eb483ba 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3340,6 +3340,19 @@ CREATE TABLE part2 (
);
ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
INFO: partition constraint for table "part2" is implied by existing constraints
+-- Create default partition
+CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
+-- Only one default partition is allowed, hence, following should give error
+CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS);
+ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT;
+ERROR: a default partition "partr_def1" already exists
+-- Overlapping partitions cannot be attached, hence, following should give error
+INSERT INTO partr_def1 VALUES (2, 10);
+CREATE TABLE part3 (LIKE range_parted);
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (2, 10) TO (2, 20);
+ERROR: default partition contains row(s) that would overlap with partition being created
+-- Attaching partitions should be successful when there are no overlapping rows
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (3, 10) TO (3, 20);
-- check that leaf partitions are scanned when attaching a partitioned
-- table
CREATE TABLE part_5 (
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 3bd663e..f352662 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -517,9 +517,6 @@ ERROR: TO must specify exactly one value per partitioning column
-- cannot specify null values in range bounds
CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (unbounded);
ERROR: cannot specify NULL in range bound
--- range partition cannot have default partition
-CREATE TABLE fail_part PARTITION OF range_parted DEFAULT;
-ERROR: default partition is supported only for list partitioned table
-- cannot specify finite values after UNBOUNDED has been specified
CREATE TABLE range_parted_multicol (a int, b int, c int) PARTITION BY RANGE (a, b, c);
CREATE TABLE fail_part PARTITION OF range_parted_multicol FOR VALUES FROM (1, UNBOUNDED, 1) TO (UNBOUNDED, 1, 1);
@@ -601,6 +598,16 @@ CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30);
ERROR: partition "fail_part" would overlap partition "part2"
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50);
ERROR: partition "fail_part" would overlap partition "part3"
+-- Create a default partition for range partitioned table
+CREATE TABLE range2_default PARTITION OF range_parted2 DEFAULT;
+-- More than one default partition is not allowed, so this should give error
+CREATE TABLE fail_default_part PARTITION OF range_parted2 DEFAULT;
+ERROR: a default partition "range2_default" already exists
+-- Check if the range for default partitions overlap
+INSERT INTO range_parted2 VALUES (85);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90);
+ERROR: default partition contains row(s) that would overlap with partition being created
+CREATE TABLE part4 PARTITION OF range_parted2 FOR VALUES FROM (90) TO (100);
-- now check for multi-column range partition key
CREATE TABLE range_parted3 (
a int,
@@ -614,6 +621,7 @@ CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10)
CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, unbounded);
CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
ERROR: partition "fail_part" would overlap partition "part12"
+CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
-- cannot create a partition that says column b is allowed to range
-- from -infinity to +infinity, while there exist partitions that have
-- more specific ranges
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index f9eef3f..9e5307a 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -271,6 +271,15 @@ insert into range_parted values ('b', 10);
insert into range_parted values ('a');
ERROR: no partition of relation "range_parted" found for row
DETAIL: Partition key of the failing row contains (a, (b + 0)) = (a, null).
+-- Check default partition
+create table part_def partition of range_parted default;
+-- fail
+insert into part_def values ('b', 10);
+ERROR: new row for relation "part_def" violates partition constraint
+DETAIL: Failing row contains (b, 10).
+-- ok
+insert into part_def values ('c', 10);
+insert into range_parted values (null, null);
select tableoid::regclass, * from range_parted;
tableoid | a | b
----------+---+----
@@ -280,7 +289,9 @@ select tableoid::regclass, * from range_parted;
part3 | b | 1
part4 | b | 10
part4 | b | 10
-(6 rows)
+ part_def | c | 10
+ part_def | |
+(8 rows)
-- ok
insert into list_parted values (null, 1);
@@ -435,6 +446,35 @@ with ins (a, b, c) as
mlparted4 | 1 | 30 | 39
(5 rows)
+-- Check multi-level default partition
+create table mlparted_def partition of mlparted default partition by range(a);
+create table mlparted_def1 partition of mlparted_def for values from (40) to (50);
+create table mlparted_def2 partition of mlparted_def for values from (50) to (60);
+insert into mlparted values (40, 100);
+insert into mlparted_def1 values (42, 100);
+insert into mlparted_def2 values (54, 50);
+-- fail
+insert into mlparted values (70, 100);
+ERROR: no partition of relation "mlparted_def" found for row
+DETAIL: Partition key of the failing row contains (a) = (70).
+insert into mlparted_def1 values (52, 50);
+ERROR: new row for relation "mlparted_def1" violates partition constraint
+DETAIL: Failing row contains (52, 50).
+insert into mlparted_def2 values (34, 50);
+ERROR: new row for relation "mlparted_def2" violates partition constraint
+DETAIL: Failing row contains (34, 50).
+-- ok
+create table mlparted_defd partition of mlparted_def default;
+insert into mlparted values (70, 100);
+select tableoid::regclass, * from mlparted_def;
+ tableoid | a | b
+---------------+----+-----
+ mlparted_def1 | 40 | 100
+ mlparted_def1 | 42 | 100
+ mlparted_def2 | 54 | 50
+ mlparted_defd | 70 | 100
+(4 rows)
+
-- check that message shown after failure to find a partition shows the
-- appropriate key description (or none) in various situations
create table key_desc (a int, b int) partition by list ((a+0));
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 6750152..e996640 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -77,6 +77,10 @@ mlparted12|f
mlparted2|f
mlparted3|f
mlparted4|f
+mlparted_def|f
+mlparted_def1|f
+mlparted_def2|f
+mlparted_defd|f
money_data|f
num_data|f
num_exp_add|t
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index 9912ef2..cfb6aa8 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -218,6 +218,15 @@ ERROR: new row for relation "part_b_10_b_20" violates partition constraint
DETAIL: Failing row contains (b, 9).
-- ok
update range_parted set b = b + 1 where b = 10;
+-- Creating default partition for range
+create table part_def partition of range_parted default;
+insert into range_parted values ('c', 9);
+-- ok
+update part_def set a = 'd' where a = 'c';
+-- fail
+update part_def set a = 'a' where a = 'd';
+ERROR: new row for relation "part_def" violates partition constraint
+DETAIL: Failing row contains (a, 9).
create table list_parted (
a text,
b int
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 36c56aa..f319fbb 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2169,6 +2169,21 @@ CREATE TABLE part2 (
);
ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
+-- Create default partition
+CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
+
+-- Only one default partition is allowed, hence, following should give error
+CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS);
+ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT;
+
+-- Overlapping partitions cannot be attached, hence, following should give error
+INSERT INTO partr_def1 VALUES (2, 10);
+CREATE TABLE part3 (LIKE range_parted);
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (2, 10) TO (2, 20);
+
+-- Attaching partitions should be successful when there are no overlapping rows
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (3, 10) TO (3, 20);
+
-- check that leaf partitions are scanned when attaching a partitioned
-- table
CREATE TABLE part_5 (
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index f44c0e0..2011cbf 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -486,8 +486,6 @@ CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a') TO ('z',
-- cannot specify null values in range bounds
CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (unbounded);
--- range partition cannot have default partition
-CREATE TABLE fail_part PARTITION OF range_parted DEFAULT;
-- cannot specify finite values after UNBOUNDED has been specified
CREATE TABLE range_parted_multicol (a int, b int, c int) PARTITION BY RANGE (a, b, c);
@@ -559,6 +557,17 @@ CREATE TABLE part3 PARTITION OF range_parted2 FOR VALUES FROM (30) TO (40);
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30);
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50);
+-- Create a default partition for range partitioned table
+CREATE TABLE range2_default PARTITION OF range_parted2 DEFAULT;
+
+-- More than one default partition is not allowed, so this should give error
+CREATE TABLE fail_default_part PARTITION OF range_parted2 DEFAULT;
+
+-- Check if the range for default partitions overlap
+INSERT INTO range_parted2 VALUES (85);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90);
+CREATE TABLE part4 PARTITION OF range_parted2 FOR VALUES FROM (90) TO (100);
+
-- now check for multi-column range partition key
CREATE TABLE range_parted3 (
a int,
@@ -572,6 +581,7 @@ CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO
CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10);
CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, unbounded);
CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
+CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
-- cannot create a partition that says column b is allowed to range
-- from -infinity to +infinity, while there exist partitions that have
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index c120713..e72891f 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -168,8 +168,16 @@ insert into range_parted values ('b', 1);
insert into range_parted values ('b', 10);
-- fail (partition key (b+0) is null)
insert into range_parted values ('a');
-select tableoid::regclass, * from range_parted;
+-- Check default partition
+create table part_def partition of range_parted default;
+-- fail
+insert into part_def values ('b', 10);
+-- ok
+insert into part_def values ('c', 10);
+insert into range_parted values (null, null);
+
+select tableoid::regclass, * from range_parted;
-- ok
insert into list_parted values (null, 1);
insert into list_parted (a) values ('aA');
@@ -277,6 +285,23 @@ with ins (a, b, c) as
(insert into mlparted (b, a) select s.a, 1 from generate_series(2, 39) s(a) returning tableoid::regclass, *)
select a, b, min(c), max(c) from ins group by a, b order by 1;
+-- Check multi-level default partition
+create table mlparted_def partition of mlparted default partition by range(a);
+create table mlparted_def1 partition of mlparted_def for values from (40) to (50);
+create table mlparted_def2 partition of mlparted_def for values from (50) to (60);
+insert into mlparted values (40, 100);
+insert into mlparted_def1 values (42, 100);
+insert into mlparted_def2 values (54, 50);
+-- fail
+insert into mlparted values (70, 100);
+insert into mlparted_def1 values (52, 50);
+insert into mlparted_def2 values (34, 50);
+-- ok
+create table mlparted_defd partition of mlparted_def default;
+insert into mlparted values (70, 100);
+
+select tableoid::regclass, * from mlparted_def;
+
-- check that message shown after failure to find a partition shows the
-- appropriate key description (or none) in various situations
create table key_desc (a int, b int) partition by list ((a+0));
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 44fb0dc..1ce7a94 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -125,6 +125,14 @@ update range_parted set b = b - 1 where b = 10;
-- ok
update range_parted set b = b + 1 where b = 10;
+-- Creating default partition for range
+create table part_def partition of range_parted default;
+insert into range_parted values ('c', 9);
+-- ok
+update part_def set a = 'd' where a = 'c';
+-- fail
+update part_def set a = 'a' where a = 'd';
+
create table list_parted (
a text,
b int
On Mon, Jun 5, 2017 at 1:43 AM, Beena Emerson <memissemerson@gmail.com> wrote:
The new patch is rebased over default_partition_v18.patch
[http://www.mail-archive.com/pgsql-hackers@postgresql.org/msg315831.html]
I have done the initial review of the patch, I have few comments.
+ if ((lower->content[0] == RANGE_DATUM_DEFAULT))
+ {
+ if (partition_bound_has_default(partdesc->boundinfo))
+ {
+ overlap = true;
+ with = partdesc->boundinfo->default_index;
+ }
I think we can change if ((lower->content[0] == RANGE_DATUM_DEFAULT))
check to if (spec->is_default) that way it will be consistent with the
check in the PARTITION_STRATEGY_LIST. Or we can move this complete
check outside of the switch.
-------------
- PartitionRangeDatum *datum = castNode(PartitionRangeDatum, lfirst(lc));
+ Node *node = lfirst(lc);
+ PartitionRangeDatum *datum;
+
+ datum = castNode(PartitionRangeDatum, node);
Why do you need to change this?
--------------
+ if (!datums)
+ bound->content[i] = RANGE_DATUM_DEFAULT;
+
Better to check if (datums != NULL) for non boolean types.
-------------
+ 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 -1;
I don't see any comments why default partition should be considered
smallest in the index comparison. For negative infinity, it's pretty
obvious by the enum name itself.
-------------
--
Regards,
Dilip Kumar
EnterpriseDB: http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hello Dilip,
On Mon, Jun 5, 2017 at 8:44 PM, Dilip Kumar <dilipbalaut@gmail.com> wrote:
On Mon, Jun 5, 2017 at 1:43 AM, Beena Emerson <memissemerson@gmail.com> wrote:
The new patch is rebased over default_partition_v18.patch
[http://www.mail-archive.com/pgsql-hackers@postgresql.org/msg315831.html]I have done the initial review of the patch, I have few comments.
Thank you.
+ if ((lower->content[0] == RANGE_DATUM_DEFAULT)) + { + if (partition_bound_has_default(partdesc->boundinfo)) + { + overlap = true; + with = partdesc->boundinfo->default_index; + }I think we can change if ((lower->content[0] == RANGE_DATUM_DEFAULT))
check to if (spec->is_default) that way it will be consistent with the
check in the PARTITION_STRATEGY_LIST. Or we can move this complete
check outside of the switch.
I have now moved the is_default check for both list and range outside
the switch case.
-------------
- PartitionRangeDatum *datum = castNode(PartitionRangeDatum, lfirst(lc)); + Node *node = lfirst(lc); + PartitionRangeDatum *datum; + + datum = castNode(PartitionRangeDatum, node);Why do you need to change this?
I forgot to remove it.
It was needed for previous version of patch but no longer needed and
hence reverted this change.
--------------
+ if (!datums) + bound->content[i] = RANGE_DATUM_DEFAULT; +Better to check if (datums != NULL) for non boolean types.
done.
------------- + 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 -1;I don't see any comments why default partition should be considered
smallest in the index comparison. For negative infinity, it's pretty
obvious by the enum name itself.
Default could be lowest or highest, no specific reason for putting it lowest.
I have not added any comments in this version.
--
Beena Emerson
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Attachments:
default_range_partition_v4.patchapplication/octet-stream; name=default_range_partition_v4.patchDownload
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 0d7b133..c66c38c 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 */
} RangeDatumContent;
typedef struct PartitionBoundInfoData
@@ -137,7 +138,9 @@ static void get_range_key_properties(PartitionKey key, int keynum,
Expr **keyCol,
Const **lower_val, Const **upper_val);
static List *get_qual_for_list(Relation parent, PartitionBoundSpec *spec);
-static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
+ bool for_default);
+static List *get_range_nulltest(PartitionKey key);
static List *generate_partition_qual(Relation rel);
static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index,
@@ -180,11 +183,11 @@ RelationBuildPartitionDesc(Relation rel)
MemoryContext oldcxt;
int ndatums = 0;
+ int default_index = -1;
/* List partitioning specific */
PartitionListValue **all_values = NULL;
int null_index = -1;
- int default_index = -1;
/* Range partitioning specific */
PartitionRangeBound **rbounds = NULL;
@@ -359,6 +362,9 @@ RelationBuildPartitionDesc(Relation rel)
if (spec->strategy != PARTITION_STRATEGY_RANGE)
elog(ERROR, "invalid strategy in partition bound spec");
+ if (spec->is_default)
+ default_index = i;
+
lower = make_one_range_bound(key, i, spec->lowerdatums,
true);
upper = make_one_range_bound(key, i, spec->upperdatums,
@@ -556,6 +562,7 @@ RelationBuildPartitionDesc(Relation rel)
sizeof(RangeDatumContent *));
boundinfo->indexes = (int *) palloc((ndatums + 1) *
sizeof(int));
+ boundinfo->default_index = -1;
for (i = 0; i < ndatums; i++)
{
@@ -577,6 +584,13 @@ RelationBuildPartitionDesc(Relation rel)
boundinfo->content[i][j] = rbounds[i]->content[j];
}
+ /* Assign mapping index for default partition. */
+ if (default_index != -1 && mapping[default_index] == -1)
+ {
+ mapping[default_index] = next_index++;
+ boundinfo->default_index = mapping[default_index];
+ }
+
/*
* There is no mapping for invalid indexes.
*
@@ -599,10 +613,8 @@ RelationBuildPartitionDesc(Relation rel)
boundinfo->indexes[i] = mapping[orig_index];
}
}
- boundinfo->indexes[i] = -1;
- /* As of now, we do not support default range partition. */
- boundinfo->default_index = -1;
+ boundinfo->indexes[i] = -1;
break;
}
@@ -728,7 +740,7 @@ check_new_partition_bound(char *relname, Relation parent,
{
Assert(spec->strategy == PARTITION_STRATEGY_LIST);
- if (partdesc->nparts > 0)
+ if (!spec->is_default && partdesc->nparts > 0)
{
ListCell *cell;
@@ -738,20 +750,6 @@ check_new_partition_bound(char *relname, Relation parent,
partition_bound_accepts_nulls(boundinfo) ||
partition_bound_has_default(boundinfo)));
- /*
- * Default partition cannot be added if there already
- * exists one.
- */
- if (spec->is_default)
- {
- if (partition_bound_has_default(boundinfo))
- {
- overlap = true;
- with = boundinfo->default_index;
- }
-
- break;
- }
foreach(cell, spec->listdatums)
{
@@ -797,14 +795,15 @@ check_new_partition_bound(char *relname, Relation parent,
* First check if the resulting range would be empty with
* specified lower and upper bounds
*/
- if (partition_rbound_cmp(key, lower->datums, lower->content, true,
- upper) >= 0)
+ if (!spec->is_default &&
+ 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 (!spec->is_default && partdesc->nparts > 0)
{
PartitionBoundInfo boundinfo = partdesc->boundinfo;
int off1,
@@ -815,8 +814,8 @@ check_new_partition_bound(char *relname, Relation parent,
boundinfo->strategy == PARTITION_STRATEGY_RANGE);
/*
- * Firstly, find the greatest range bound that is less
- * than or equal to the new lower bound.
+ * 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);
@@ -890,17 +889,19 @@ check_new_partition_bound(char *relname, Relation parent,
(int) key->strategy);
}
- if (overlap)
+ /* Default partition cannot be added if there already exists one. */
+ if (spec->is_default)
{
- Assert(with >= 0);
-
- if (spec->is_default)
+ if (partition_bound_has_default(boundinfo))
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("a default partition \"%s\" already exists",
- get_rel_name(partdesc->oids[with])),
+ get_rel_name(partdesc->oids[boundinfo->default_index])),
parser_errposition(pstate, spec->location)));
-
+ }
+ else if (overlap)
+ {
+ Assert(with >= 0);
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("partition \"%s\" would overlap partition \"%s\"",
@@ -934,11 +935,9 @@ check_default_allows_bound(Relation parent, PartitionBoundSpec *new_spec)
List *all_parts;
ListCell *lc;
- /* Currently default partition is supported only for LIST partition. */
- if (new_spec->strategy != PARTITION_STRATEGY_LIST)
- return;
-
- new_part_constraints = get_qual_for_list(parent, new_spec);
+ new_part_constraints = (new_spec->strategy == PARTITION_STRATEGY_LIST)
+ ? get_qual_for_list(parent, new_spec)
+ : get_qual_for_range(parent, new_spec, false);
new_part_constraints = (List *) eval_const_expressions(NULL,
(Node *) new_part_constraints);
new_part_constraints =
@@ -1102,7 +1101,8 @@ get_qual_from_partbound(Relation rel, Relation parent,
case PARTITION_STRATEGY_RANGE:
Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
- my_qual = get_qual_for_range(key, spec);
+
+ my_qual = get_qual_for_range(parent, spec, false);
break;
default:
@@ -1684,6 +1684,53 @@ get_range_key_properties(PartitionKey key, int keynum,
*upper_val = NULL;
}
+ /*
+ * get_range_nulltest
+ *
+ * A non-default range partition table does not currently allow partition
+ * keys to be null, so emit an IS NOT NULL expression for each key column.
+ */
+static List *
+get_range_nulltest(PartitionKey key)
+{
+ List *result = NIL;
+ NullTest *nulltest;
+ ListCell *partexprs_item;
+ int i;
+
+ partexprs_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ Expr *keyCol;
+
+ if (key->partattrs[i] != 0)
+ {
+ keyCol = (Expr *) makeVar(1,
+ key->partattrs[i],
+ key->parttypid[i],
+ key->parttypmod[i],
+ key->parttypcoll[i],
+ 0);
+ }
+ else
+ {
+ if (partexprs_item == NULL)
+ elog(ERROR, "wrong number of partition key expressions");
+ keyCol = copyObject(lfirst(partexprs_item));
+ partexprs_item = lnext(partexprs_item);
+ }
+
+ nulltest = makeNode(NullTest);
+ nulltest->arg = keyCol;
+ nulltest->nulltesttype = IS_NOT_NULL;
+ nulltest->argisrow = false;
+ nulltest->location = -1;
+ result = lappend(result, nulltest);
+ }
+
+ return result;
+}
+
/*
* get_qual_for_range
*
@@ -1724,11 +1771,15 @@ get_range_key_properties(PartitionKey key, int keynum,
* does not really have a constraint, except the IS NOT NULL constraint for
* partition keys.
*
+ * For default partition, it returns the negation of the constraints of all
+ * the other partitions.
+ *
* If we end up with an empty result list, we return a single-member list
* containing a constant TRUE, because callers expect a non-empty list.
*/
static List *
-get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
+get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
+ bool for_default)
{
List *result = NIL;
ListCell *cell1,
@@ -1739,10 +1790,10 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
j;
PartitionRangeDatum *ldatum,
*udatum;
+ PartitionKey key = RelationGetPartitionKey(parent);
Expr *keyCol;
Const *lower_val,
*upper_val;
- NullTest *nulltest;
List *lower_or_arms,
*upper_or_arms;
int num_or_arms,
@@ -1752,44 +1803,70 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
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;
-
- /*
- * A range-partitioned table does not currently allow partition keys to be
- * null, so emit an IS NOT NULL expression for each key column.
- */
- partexprs_item = list_head(key->partexprs);
- for (i = 0; i < key->partnatts; i++)
+ if (spec->is_default)
{
- Expr *keyCol;
+ List *or_expr_args = NIL;
+ PartitionDesc pdesc = RelationGetPartitionDesc(parent);
+ Oid *inhoids = pdesc->oids;
+ int nparts = pdesc->nparts,
+ i;
- if (key->partattrs[i] != 0)
+ for (i = 0; i < nparts; i++)
{
- keyCol = (Expr *) makeVar(1,
- key->partattrs[i],
- key->parttypid[i],
- key->parttypmod[i],
- key->parttypcoll[i],
- 0);
+ Oid inhrelid = inhoids[i];
+ HeapTuple tuple;
+ Datum datum;
+ bool isnull;
+ PartitionBoundSpec *bspec;
+
+ tuple = SearchSysCache1(RELOID, inhrelid);
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", inhrelid);
+
+ datum = SysCacheGetAttr(RELOID, tuple,
+ Anum_pg_class_relpartbound,
+ &isnull);
+
+ Assert(!isnull);
+ bspec = (PartitionBoundSpec *) stringToNode(TextDatumGetCString(datum));
+
+ if (!bspec->is_default)
+ {
+ List *part_qual = get_qual_for_range(parent, bspec, true);
+
+ /*
+ * AND the constraints of the partition and add to
+ * or_expr_args
+ */
+ or_expr_args = lappend(or_expr_args, list_length(part_qual) > 1
+ ? makeBoolExpr(AND_EXPR, part_qual, -1)
+ : linitial(part_qual));
+ }
+ ReleaseSysCache(tuple);
}
- else
+
+ if (or_expr_args != NIL)
{
- if (partexprs_item == NULL)
- elog(ERROR, "wrong number of partition key expressions");
- keyCol = copyObject(lfirst(partexprs_item));
- partexprs_item = lnext(partexprs_item);
+ /* OR all the non-default partition constraints; then negate it */
+ result = lappend(result,
+ list_length(or_expr_args) > 1
+ ? makeBoolExpr(OR_EXPR, or_expr_args, -1)
+ : linitial(or_expr_args));
+ result = list_make1(makeBoolExpr(NOT_EXPR, result, -1));
}
+ else /* default is the only partition */
+ result = list_make1(makeBoolConst(true, false));
- nulltest = makeNode(NullTest);
- nulltest->arg = keyCol;
- nulltest->nulltesttype = IS_NOT_NULL;
- nulltest->argisrow = false;
- nulltest->location = -1;
- result = lappend(result, nulltest);
+ return result;
}
+ lower_or_start_datum = list_head(spec->lowerdatums);
+ upper_or_start_datum = list_head(spec->upperdatums);
+ num_or_arms = key->partnatts;
+
+ if (!for_default)
+ result = get_range_nulltest(key);
+
/*
* Iterate over the key columns and check if the corresponding lower and
* upper datums are equal using the btree equality operator for the
@@ -2006,7 +2083,9 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
/* As noted above, caller expects the list to be non-empty. */
if (result == NIL)
- result = list_make1(makeBoolConst(true, false));
+ result = for_default
+ ? get_range_nulltest(key)
+ : list_make1(makeBoolConst(true, false));
return result;
}
@@ -2174,8 +2253,7 @@ get_partition_for_tuple(PartitionDispatch *pd,
Datum values[PARTITION_MAX_KEYS];
bool isnull[PARTITION_MAX_KEYS];
int cur_offset,
- cur_index;
- int i,
+ cur_index = -1,
result;
ExprContext *ecxt = GetPerTupleExprContext(estate);
TupleTableSlot *ecxt_scantuple_old = ecxt->ecxt_scantuple;
@@ -2219,24 +2297,6 @@ get_partition_for_tuple(PartitionDispatch *pd,
ecxt->ecxt_scantuple = slot;
FormPartitionKeyDatum(parent, slot, estate, values, isnull);
- if (key->strategy == PARTITION_STRATEGY_RANGE)
- {
- /*
- * Since we cannot route tuples with NULL partition keys through a
- * range-partitioned table, simply return that no partition exists
- */
- for (i = 0; i < key->partnatts; i++)
- {
- if (isnull[i])
- {
- *failed_at = parent;
- *failed_slot = slot;
- result = -1;
- goto error_exit;
- }
- }
- }
-
/*
* Handle NULL partition key here if there's a null-accepting list
* partition. Else it will be routed to the default partition if one
@@ -2348,12 +2408,18 @@ make_one_range_bound(PartitionKey key, int index, List *datums, bool lower)
bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
bound->index = index;
- bound->datums = (Datum *) palloc0(key->partnatts * sizeof(Datum));
- bound->content = (RangeDatumContent *) palloc0(key->partnatts *
- sizeof(RangeDatumContent));
+ bound->datums = datums ? (Datum *) palloc0(key->partnatts * sizeof(Datum))
+ : NULL;
+ bound->content = (RangeDatumContent *) palloc0(
+ (datums ? key->partnatts : 1) * sizeof(RangeDatumContent));
bound->lower = lower;
i = 0;
+
+ /* If default, datums are NULL */
+ if (datums == NULL)
+ bound->content[i] = RANGE_DATUM_DEFAULT;
+
foreach(lc, datums)
{
PartitionRangeDatum *datum = castNode(PartitionRangeDatum, lfirst(lc));
@@ -2410,9 +2476,19 @@ 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 -1;
+ else
+ return 1;
+ }
if (content1[i] != RANGE_DATUM_FINITE &&
content2[i] != RANGE_DATUM_FINITE)
@@ -2463,6 +2539,9 @@ partition_rbound_datum_cmp(PartitionKey key,
for (i = 0; i < key->partnatts; i++)
{
+ if (rb_content[i] == RANGE_DATUM_DEFAULT)
+ continue;
+
if (rb_content[i] != RANGE_DATUM_FINITE)
return rb_content[i] == RANGE_DATUM_NEG_INF ? -1 : 1;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ddb995d..e03192f 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2681,7 +2681,6 @@ ForValues:
/*
* A default partition, that can be partition of either LIST or
* RANGE partitioned table.
- * Currently this is supported only for LIST partition.
*/
| DEFAULT
{
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e861195..8257c47 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3286,11 +3286,6 @@ transformPartitionBound(ParseState *pstate, Relation parent,
if (spec->is_default)
{
- if (strategy != PARTITION_STRATEGY_LIST)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("default partition is supported only for list partitioned table")));
-
/*
* In case of the default partition, parser had no way to identify the
* partition strategy. Assign the parent strategy to the default
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index c3d39c3..441e397 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8680,6 +8680,12 @@ get_rule_expr(Node *node, deparse_context *context,
list_length(spec->lowerdatums) ==
list_length(spec->upperdatums));
+ if (spec->is_default)
+ {
+ appendStringInfoString(buf, "DEFAULT");
+ break;
+ }
+
appendStringInfoString(buf, "FOR VALUES FROM (");
sep = "";
foreach(cell, spec->lowerdatums)
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 75f3617..eb483ba 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3340,6 +3340,19 @@ CREATE TABLE part2 (
);
ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
INFO: partition constraint for table "part2" is implied by existing constraints
+-- Create default partition
+CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
+-- Only one default partition is allowed, hence, following should give error
+CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS);
+ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT;
+ERROR: a default partition "partr_def1" already exists
+-- Overlapping partitions cannot be attached, hence, following should give error
+INSERT INTO partr_def1 VALUES (2, 10);
+CREATE TABLE part3 (LIKE range_parted);
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (2, 10) TO (2, 20);
+ERROR: default partition contains row(s) that would overlap with partition being created
+-- Attaching partitions should be successful when there are no overlapping rows
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (3, 10) TO (3, 20);
-- check that leaf partitions are scanned when attaching a partitioned
-- table
CREATE TABLE part_5 (
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 3bd663e..f352662 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -517,9 +517,6 @@ ERROR: TO must specify exactly one value per partitioning column
-- cannot specify null values in range bounds
CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (unbounded);
ERROR: cannot specify NULL in range bound
--- range partition cannot have default partition
-CREATE TABLE fail_part PARTITION OF range_parted DEFAULT;
-ERROR: default partition is supported only for list partitioned table
-- cannot specify finite values after UNBOUNDED has been specified
CREATE TABLE range_parted_multicol (a int, b int, c int) PARTITION BY RANGE (a, b, c);
CREATE TABLE fail_part PARTITION OF range_parted_multicol FOR VALUES FROM (1, UNBOUNDED, 1) TO (UNBOUNDED, 1, 1);
@@ -601,6 +598,16 @@ CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30);
ERROR: partition "fail_part" would overlap partition "part2"
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50);
ERROR: partition "fail_part" would overlap partition "part3"
+-- Create a default partition for range partitioned table
+CREATE TABLE range2_default PARTITION OF range_parted2 DEFAULT;
+-- More than one default partition is not allowed, so this should give error
+CREATE TABLE fail_default_part PARTITION OF range_parted2 DEFAULT;
+ERROR: a default partition "range2_default" already exists
+-- Check if the range for default partitions overlap
+INSERT INTO range_parted2 VALUES (85);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90);
+ERROR: default partition contains row(s) that would overlap with partition being created
+CREATE TABLE part4 PARTITION OF range_parted2 FOR VALUES FROM (90) TO (100);
-- now check for multi-column range partition key
CREATE TABLE range_parted3 (
a int,
@@ -614,6 +621,7 @@ CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10)
CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, unbounded);
CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
ERROR: partition "fail_part" would overlap partition "part12"
+CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
-- cannot create a partition that says column b is allowed to range
-- from -infinity to +infinity, while there exist partitions that have
-- more specific ranges
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index f9eef3f..9e5307a 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -271,6 +271,15 @@ insert into range_parted values ('b', 10);
insert into range_parted values ('a');
ERROR: no partition of relation "range_parted" found for row
DETAIL: Partition key of the failing row contains (a, (b + 0)) = (a, null).
+-- Check default partition
+create table part_def partition of range_parted default;
+-- fail
+insert into part_def values ('b', 10);
+ERROR: new row for relation "part_def" violates partition constraint
+DETAIL: Failing row contains (b, 10).
+-- ok
+insert into part_def values ('c', 10);
+insert into range_parted values (null, null);
select tableoid::regclass, * from range_parted;
tableoid | a | b
----------+---+----
@@ -280,7 +289,9 @@ select tableoid::regclass, * from range_parted;
part3 | b | 1
part4 | b | 10
part4 | b | 10
-(6 rows)
+ part_def | c | 10
+ part_def | |
+(8 rows)
-- ok
insert into list_parted values (null, 1);
@@ -435,6 +446,35 @@ with ins (a, b, c) as
mlparted4 | 1 | 30 | 39
(5 rows)
+-- Check multi-level default partition
+create table mlparted_def partition of mlparted default partition by range(a);
+create table mlparted_def1 partition of mlparted_def for values from (40) to (50);
+create table mlparted_def2 partition of mlparted_def for values from (50) to (60);
+insert into mlparted values (40, 100);
+insert into mlparted_def1 values (42, 100);
+insert into mlparted_def2 values (54, 50);
+-- fail
+insert into mlparted values (70, 100);
+ERROR: no partition of relation "mlparted_def" found for row
+DETAIL: Partition key of the failing row contains (a) = (70).
+insert into mlparted_def1 values (52, 50);
+ERROR: new row for relation "mlparted_def1" violates partition constraint
+DETAIL: Failing row contains (52, 50).
+insert into mlparted_def2 values (34, 50);
+ERROR: new row for relation "mlparted_def2" violates partition constraint
+DETAIL: Failing row contains (34, 50).
+-- ok
+create table mlparted_defd partition of mlparted_def default;
+insert into mlparted values (70, 100);
+select tableoid::regclass, * from mlparted_def;
+ tableoid | a | b
+---------------+----+-----
+ mlparted_def1 | 40 | 100
+ mlparted_def1 | 42 | 100
+ mlparted_def2 | 54 | 50
+ mlparted_defd | 70 | 100
+(4 rows)
+
-- check that message shown after failure to find a partition shows the
-- appropriate key description (or none) in various situations
create table key_desc (a int, b int) partition by list ((a+0));
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 6750152..e996640 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -77,6 +77,10 @@ mlparted12|f
mlparted2|f
mlparted3|f
mlparted4|f
+mlparted_def|f
+mlparted_def1|f
+mlparted_def2|f
+mlparted_defd|f
money_data|f
num_data|f
num_exp_add|t
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index 9912ef2..cfb6aa8 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -218,6 +218,15 @@ ERROR: new row for relation "part_b_10_b_20" violates partition constraint
DETAIL: Failing row contains (b, 9).
-- ok
update range_parted set b = b + 1 where b = 10;
+-- Creating default partition for range
+create table part_def partition of range_parted default;
+insert into range_parted values ('c', 9);
+-- ok
+update part_def set a = 'd' where a = 'c';
+-- fail
+update part_def set a = 'a' where a = 'd';
+ERROR: new row for relation "part_def" violates partition constraint
+DETAIL: Failing row contains (a, 9).
create table list_parted (
a text,
b int
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 36c56aa..f319fbb 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2169,6 +2169,21 @@ CREATE TABLE part2 (
);
ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
+-- Create default partition
+CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
+
+-- Only one default partition is allowed, hence, following should give error
+CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS);
+ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT;
+
+-- Overlapping partitions cannot be attached, hence, following should give error
+INSERT INTO partr_def1 VALUES (2, 10);
+CREATE TABLE part3 (LIKE range_parted);
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (2, 10) TO (2, 20);
+
+-- Attaching partitions should be successful when there are no overlapping rows
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (3, 10) TO (3, 20);
+
-- check that leaf partitions are scanned when attaching a partitioned
-- table
CREATE TABLE part_5 (
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index f44c0e0..2011cbf 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -486,8 +486,6 @@ CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a') TO ('z',
-- cannot specify null values in range bounds
CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (unbounded);
--- range partition cannot have default partition
-CREATE TABLE fail_part PARTITION OF range_parted DEFAULT;
-- cannot specify finite values after UNBOUNDED has been specified
CREATE TABLE range_parted_multicol (a int, b int, c int) PARTITION BY RANGE (a, b, c);
@@ -559,6 +557,17 @@ CREATE TABLE part3 PARTITION OF range_parted2 FOR VALUES FROM (30) TO (40);
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30);
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50);
+-- Create a default partition for range partitioned table
+CREATE TABLE range2_default PARTITION OF range_parted2 DEFAULT;
+
+-- More than one default partition is not allowed, so this should give error
+CREATE TABLE fail_default_part PARTITION OF range_parted2 DEFAULT;
+
+-- Check if the range for default partitions overlap
+INSERT INTO range_parted2 VALUES (85);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90);
+CREATE TABLE part4 PARTITION OF range_parted2 FOR VALUES FROM (90) TO (100);
+
-- now check for multi-column range partition key
CREATE TABLE range_parted3 (
a int,
@@ -572,6 +581,7 @@ CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO
CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10);
CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, unbounded);
CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
+CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
-- cannot create a partition that says column b is allowed to range
-- from -infinity to +infinity, while there exist partitions that have
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index c120713..e72891f 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -168,8 +168,16 @@ insert into range_parted values ('b', 1);
insert into range_parted values ('b', 10);
-- fail (partition key (b+0) is null)
insert into range_parted values ('a');
-select tableoid::regclass, * from range_parted;
+-- Check default partition
+create table part_def partition of range_parted default;
+-- fail
+insert into part_def values ('b', 10);
+-- ok
+insert into part_def values ('c', 10);
+insert into range_parted values (null, null);
+
+select tableoid::regclass, * from range_parted;
-- ok
insert into list_parted values (null, 1);
insert into list_parted (a) values ('aA');
@@ -277,6 +285,23 @@ with ins (a, b, c) as
(insert into mlparted (b, a) select s.a, 1 from generate_series(2, 39) s(a) returning tableoid::regclass, *)
select a, b, min(c), max(c) from ins group by a, b order by 1;
+-- Check multi-level default partition
+create table mlparted_def partition of mlparted default partition by range(a);
+create table mlparted_def1 partition of mlparted_def for values from (40) to (50);
+create table mlparted_def2 partition of mlparted_def for values from (50) to (60);
+insert into mlparted values (40, 100);
+insert into mlparted_def1 values (42, 100);
+insert into mlparted_def2 values (54, 50);
+-- fail
+insert into mlparted values (70, 100);
+insert into mlparted_def1 values (52, 50);
+insert into mlparted_def2 values (34, 50);
+-- ok
+create table mlparted_defd partition of mlparted_def default;
+insert into mlparted values (70, 100);
+
+select tableoid::regclass, * from mlparted_def;
+
-- check that message shown after failure to find a partition shows the
-- appropriate key description (or none) in various situations
create table key_desc (a int, b int) partition by list ((a+0));
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 44fb0dc..1ce7a94 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -125,6 +125,14 @@ update range_parted set b = b - 1 where b = 10;
-- ok
update range_parted set b = b + 1 where b = 10;
+-- Creating default partition for range
+create table part_def partition of range_parted default;
+insert into range_parted values ('c', 9);
+-- ok
+update part_def set a = 'd' where a = 'c';
+-- fail
+update part_def set a = 'a' where a = 'd';
+
create table list_parted (
a text,
b int
Hello,
PFA the updated patch.
This is rebased over v21 patches of list partition.
(http://www.mail-archive.com/pgsql-hackers@postgresql.org/msg316818.html)
--
Beena Emerson
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Attachments:
default_range_partition_v5.patchapplication/octet-stream; name=default_range_partition_v5.patchDownload
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 8470fae..aa70abd 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -72,6 +72,7 @@
typedef enum RangeDatumContent
{
RANGE_DATUM_FINITE = 0, /* actual datum stored elsewhere */
+ RANGE_DATUM_DEFAULT, /* default */
RANGE_DATUM_NEG_INF, /* negative infinity */
RANGE_DATUM_POS_INF /* positive infinity */
} RangeDatumContent;
@@ -134,7 +135,9 @@ static void get_range_key_properties(PartitionKey key, int keynum,
Expr **keyCol,
Const **lower_val, Const **upper_val);
static List *get_qual_for_list(Relation parent, PartitionBoundSpec *spec);
-static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
+ bool for_default);
+static List *get_range_nulltest(PartitionKey key);
static List *generate_partition_qual(Relation rel);
static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index,
@@ -175,11 +178,11 @@ RelationBuildPartitionDesc(Relation rel)
MemoryContext oldcxt;
int ndatums = 0;
+ int default_index = -1;
/* List partitioning specific */
PartitionListValue **all_values = NULL;
int null_index = -1;
- int default_index = -1;
/* Range partitioning specific */
PartitionRangeBound **rbounds = NULL;
@@ -354,6 +357,9 @@ RelationBuildPartitionDesc(Relation rel)
if (spec->strategy != PARTITION_STRATEGY_RANGE)
elog(ERROR, "invalid strategy in partition bound spec");
+ if (spec->is_default)
+ default_index = i;
+
lower = make_one_range_bound(key, i, spec->lowerdatums,
true);
upper = make_one_range_bound(key, i, spec->upperdatums,
@@ -550,6 +556,7 @@ RelationBuildPartitionDesc(Relation rel)
sizeof(RangeDatumContent *));
boundinfo->indexes = (int *) palloc((ndatums + 1) *
sizeof(int));
+ boundinfo->default_index = -1;
for (i = 0; i < ndatums; i++)
{
@@ -571,6 +578,13 @@ RelationBuildPartitionDesc(Relation rel)
boundinfo->content[i][j] = rbounds[i]->content[j];
}
+ /* Assign mapping index for default partition. */
+ if (default_index != -1 && mapping[default_index] == -1)
+ {
+ mapping[default_index] = next_index++;
+ boundinfo->default_index = mapping[default_index];
+ }
+
/*
* There is no mapping for invalid indexes.
*
@@ -719,7 +733,7 @@ check_new_partition_bound(char *relname, Relation parent,
{
Assert(spec->strategy == PARTITION_STRATEGY_LIST);
- if (partdesc->nparts > 0)
+ if (!spec->is_default && partdesc->nparts > 0)
{
ListCell *cell;
@@ -729,17 +743,6 @@ check_new_partition_bound(char *relname, Relation parent,
partition_bound_accepts_nulls(boundinfo) ||
partition_bound_has_default(boundinfo)));
- /*
- * Default partition cannot be added if there already
- * exists one.
- */
- if (spec->is_default)
- {
- overlap = partition_bound_has_default(boundinfo);
- with = boundinfo->default_index;
- break;
- }
-
foreach(cell, spec->listdatums)
{
Const *val = castNode(Const, lfirst(cell));
@@ -780,6 +783,10 @@ check_new_partition_bound(char *relname, Relation parent,
lower = make_one_range_bound(key, -1, spec->lowerdatums, true);
upper = make_one_range_bound(key, -1, spec->upperdatums, false);
+ /* Default case is not handled here */
+ if (spec->is_default)
+ break;
+
/*
* First check if the resulting range would be empty with
* specified lower and upper bounds
@@ -877,16 +884,20 @@ check_new_partition_bound(char *relname, Relation parent,
(int) key->strategy);
}
- if (overlap)
+ /* Default partition cannot be added if there already exists one. */
+ if (spec->is_default)
{
- Assert(with >= 0);
-
- if (spec->is_default)
+ if (boundinfo && partition_bound_has_default(boundinfo))
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("partition \"%s\" conflicts with existing default partition \"%s\"",
- relname, get_rel_name(partdesc->oids[with])),
+ relname,
+ get_rel_name(partdesc->oids[boundinfo->default_index])),
parser_errposition(pstate, spec->location)));
+ }
+ else if (overlap)
+ {
+ Assert(with >= 0);
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
@@ -918,13 +929,12 @@ check_default_allows_bound(Relation parent, PartitionBoundSpec *new_spec)
if (!boundinfo || !partition_bound_has_default(boundinfo))
return;
- /* Currently default partition is supported only for LIST partition. */
- Assert(new_spec->strategy == PARTITION_STRATEGY_LIST);
-
/* If there exists a default partition, then boundinfo cannot be NULL */
Assert(part_desc->boundinfo != NULL);
- new_part_constraints = get_qual_for_list(parent, new_spec);
+ new_part_constraints = (new_spec->strategy == PARTITION_STRATEGY_LIST)
+ ? get_qual_for_list(parent, new_spec)
+ : get_qual_for_range(parent, new_spec, false);
def_part_constraints = get_default_part_validation_constraint(new_part_constraints);
/*
@@ -1097,7 +1107,7 @@ get_qual_from_partbound(Relation rel, Relation parent,
case PARTITION_STRATEGY_RANGE:
Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
- my_qual = get_qual_for_range(key, spec);
+ my_qual = get_qual_for_range(parent, spec, false);
break;
default:
@@ -1684,6 +1694,53 @@ get_range_key_properties(PartitionKey key, int keynum,
*upper_val = NULL;
}
+ /*
+ * get_range_nulltest
+ *
+ * A non-default range partition table does not currently allow partition
+ * keys to be null, so emit an IS NOT NULL expression for each key column.
+ */
+static List *
+get_range_nulltest(PartitionKey key)
+{
+ List *result = NIL;
+ NullTest *nulltest;
+ ListCell *partexprs_item;
+ int i;
+
+ partexprs_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ Expr *keyCol;
+
+ if (key->partattrs[i] != 0)
+ {
+ keyCol = (Expr *) makeVar(1,
+ key->partattrs[i],
+ key->parttypid[i],
+ key->parttypmod[i],
+ key->parttypcoll[i],
+ 0);
+ }
+ else
+ {
+ if (partexprs_item == NULL)
+ elog(ERROR, "wrong number of partition key expressions");
+ keyCol = copyObject(lfirst(partexprs_item));
+ partexprs_item = lnext(partexprs_item);
+ }
+
+ nulltest = makeNode(NullTest);
+ nulltest->arg = keyCol;
+ nulltest->nulltesttype = IS_NOT_NULL;
+ nulltest->argisrow = false;
+ nulltest->location = -1;
+ result = lappend(result, nulltest);
+ }
+
+ return result;
+}
+
/*
* get_qual_for_range
*
@@ -1724,11 +1781,15 @@ get_range_key_properties(PartitionKey key, int keynum,
* does not really have a constraint, except the IS NOT NULL constraint for
* partition keys.
*
+ * For default partition, it returns the negation of the constraints of all
+ * the other partitions.
+ *
* If we end up with an empty result list, we return a single-member list
* containing a constant TRUE, because callers expect a non-empty list.
*/
static List *
-get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
+get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
+ bool for_default)
{
List *result = NIL;
ListCell *cell1,
@@ -1739,10 +1800,10 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
j;
PartitionRangeDatum *ldatum,
*udatum;
+ PartitionKey key = RelationGetPartitionKey(parent);
Expr *keyCol;
Const *lower_val,
*upper_val;
- NullTest *nulltest;
List *lower_or_arms,
*upper_or_arms;
int num_or_arms,
@@ -1752,44 +1813,70 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
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;
-
- /*
- * A range-partitioned table does not currently allow partition keys to be
- * null, so emit an IS NOT NULL expression for each key column.
- */
- partexprs_item = list_head(key->partexprs);
- for (i = 0; i < key->partnatts; i++)
+ if (spec->is_default)
{
- Expr *keyCol;
+ List *or_expr_args = NIL;
+ PartitionDesc pdesc = RelationGetPartitionDesc(parent);
+ Oid *inhoids = pdesc->oids;
+ int nparts = pdesc->nparts,
+ i;
- if (key->partattrs[i] != 0)
+ for (i = 0; i < nparts; i++)
{
- keyCol = (Expr *) makeVar(1,
- key->partattrs[i],
- key->parttypid[i],
- key->parttypmod[i],
- key->parttypcoll[i],
- 0);
+ Oid inhrelid = inhoids[i];
+ HeapTuple tuple;
+ Datum datum;
+ bool isnull;
+ PartitionBoundSpec *bspec;
+
+ tuple = SearchSysCache1(RELOID, inhrelid);
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", inhrelid);
+
+ datum = SysCacheGetAttr(RELOID, tuple,
+ Anum_pg_class_relpartbound,
+ &isnull);
+
+ Assert(!isnull);
+ bspec = (PartitionBoundSpec *) stringToNode(TextDatumGetCString(datum));
+
+ if (!bspec->is_default)
+ {
+ List *part_qual = get_qual_for_range(parent, bspec, true);
+
+ /*
+ * AND the constraints of the partition and add to
+ * or_expr_args
+ */
+ or_expr_args = lappend(or_expr_args, list_length(part_qual) > 1
+ ? makeBoolExpr(AND_EXPR, part_qual, -1)
+ : linitial(part_qual));
+ }
+ ReleaseSysCache(tuple);
}
- else
+
+ if (or_expr_args != NIL)
{
- if (partexprs_item == NULL)
- elog(ERROR, "wrong number of partition key expressions");
- keyCol = copyObject(lfirst(partexprs_item));
- partexprs_item = lnext(partexprs_item);
+ /* OR all the non-default partition constraints; then negate it */
+ result = lappend(result,
+ list_length(or_expr_args) > 1
+ ? makeBoolExpr(OR_EXPR, or_expr_args, -1)
+ : linitial(or_expr_args));
+ result = list_make1(makeBoolExpr(NOT_EXPR, result, -1));
}
+ else /* default is the only partition */
+ result = list_make1(makeBoolConst(true, false));
- nulltest = makeNode(NullTest);
- nulltest->arg = keyCol;
- nulltest->nulltesttype = IS_NOT_NULL;
- nulltest->argisrow = false;
- nulltest->location = -1;
- result = lappend(result, nulltest);
+ return result;
}
+ lower_or_start_datum = list_head(spec->lowerdatums);
+ upper_or_start_datum = list_head(spec->upperdatums);
+ num_or_arms = key->partnatts;
+
+ if (!for_default)
+ result = get_range_nulltest(key);
+
/*
* Iterate over the key columns and check if the corresponding lower and
* upper datums are equal using the btree equality operator for the
@@ -2003,7 +2090,9 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
/* As noted above, caller expects the list to be non-empty. */
if (result == NIL)
- result = list_make1(makeBoolConst(true, false));
+ result = for_default
+ ? get_range_nulltest(key)
+ : list_make1(makeBoolConst(true, false));
return result;
}
@@ -2171,8 +2260,7 @@ get_partition_for_tuple(PartitionDispatch *pd,
Datum values[PARTITION_MAX_KEYS];
bool isnull[PARTITION_MAX_KEYS];
int cur_offset,
- cur_index;
- int i,
+ cur_index = -1,
result;
ExprContext *ecxt = GetPerTupleExprContext(estate);
TupleTableSlot *ecxt_scantuple_old = ecxt->ecxt_scantuple;
@@ -2216,24 +2304,6 @@ get_partition_for_tuple(PartitionDispatch *pd,
ecxt->ecxt_scantuple = slot;
FormPartitionKeyDatum(parent, slot, estate, values, isnull);
- if (key->strategy == PARTITION_STRATEGY_RANGE)
- {
- /*
- * Since we cannot route tuples with NULL partition keys through a
- * range-partitioned table, simply return that no partition exists
- */
- for (i = 0; i < key->partnatts; i++)
- {
- if (isnull[i])
- {
- *failed_at = parent;
- *failed_slot = slot;
- result = -1;
- goto error_exit;
- }
- }
- }
-
/*
* Handle NULL partition key here if there's a null-accepting list
* partition, else later it will be routed to the default partition if
@@ -2339,12 +2409,18 @@ make_one_range_bound(PartitionKey key, int index, List *datums, bool lower)
bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
bound->index = index;
- bound->datums = (Datum *) palloc0(key->partnatts * sizeof(Datum));
- bound->content = (RangeDatumContent *) palloc0(key->partnatts *
- sizeof(RangeDatumContent));
+ bound->datums = datums ? (Datum *) palloc0(key->partnatts * sizeof(Datum))
+ : NULL;
+ bound->content = (RangeDatumContent *) palloc0(
+ (datums ? key->partnatts : 1) * sizeof(RangeDatumContent));
bound->lower = lower;
i = 0;
+
+ /* If default, datums are NULL */
+ if (datums == NULL)
+ bound->content[i] = RANGE_DATUM_DEFAULT;
+
foreach(lc, datums)
{
PartitionRangeDatum *datum = castNode(PartitionRangeDatum, lfirst(lc));
@@ -2401,23 +2477,31 @@ 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_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 non-finite, so they are arranged according to their
+ * enum sequence - default, negative infinity, positive infinity.
*/
return content1[i] == content2[i] ? 0
: (content1[i] < content2[i] ? -1 : 1);
+
+ /*
+ * Comparing a finite and a non-finite value.
+ *
+ * Positive infinity is higher than finite value and default or
+ * negative infinity is lower than the finite value
+ */
else if (content1[i] != RANGE_DATUM_FINITE)
- return content1[i] == RANGE_DATUM_NEG_INF ? -1 : 1;
+ return content1[i] == RANGE_DATUM_POS_INF ? 1 : -1;
else if (content2[i] != RANGE_DATUM_FINITE)
- return content2[i] == RANGE_DATUM_NEG_INF ? 1 : -1;
+ return content2[i] == RANGE_DATUM_POS_INF ? -1 : 1;
+ /* Both are finite value */
cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
key->partcollation[i],
datums1[i],
@@ -2454,6 +2538,9 @@ partition_rbound_datum_cmp(PartitionKey key,
for (i = 0; i < key->partnatts; i++)
{
+ if (rb_content[i] == RANGE_DATUM_DEFAULT)
+ continue;
+
if (rb_content[i] != RANGE_DATUM_FINITE)
return rb_content[i] == RANGE_DATUM_NEG_INF ? -1 : 1;
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index be4ebd4..ddbf48c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3316,11 +3316,6 @@ transformPartitionBound(ParseState *pstate, Relation parent,
if (spec->is_default)
{
- if (strategy != PARTITION_STRATEGY_LIST)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("default partition is supported only for a list partitioned table")));
-
/*
* In case of the default partition, parser had no way to identify the
* partition strategy. Assign the parent strategy to the default
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 0c1e72a..97d1a9a 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8650,7 +8650,6 @@ get_rule_expr(Node *node, deparse_context *context,
if (spec->is_default)
{
- Assert(strategy == PARTITION_STRATEGY_LIST);
appendStringInfoString(buf, "DEFAULT");
break;
}
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 8315f4d..8f636f9 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3340,6 +3340,19 @@ CREATE TABLE part2 (
);
ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
INFO: partition constraint for table "part2" is implied by existing constraints
+-- Create default partition
+CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
+-- Only one default partition is allowed, hence, following should give error
+CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS);
+ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT;
+ERROR: partition "partr_def2" conflicts with existing default partition "partr_def1"
+-- Overlapping partitions cannot be attached, hence, following should give error
+INSERT INTO partr_def1 VALUES (2, 10);
+CREATE TABLE part3 (LIKE range_parted);
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (2, 10) TO (2, 20);
+ERROR: updated partition constraint for default partition would be violated by some row
+-- Attaching partitions should be successful when there are no overlapping rows
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (3, 10) TO (3, 20);
-- check that leaf partitions are scanned when attaching a partitioned
-- table
CREATE TABLE part_5 (
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index e51a53a..51e2d4c 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -517,9 +517,6 @@ ERROR: TO must specify exactly one value per partitioning column
-- cannot specify null values in range bounds
CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (unbounded);
ERROR: cannot specify NULL in range bound
--- range partition cannot have default partition
-CREATE TABLE fail_part PARTITION OF range_parted DEFAULT;
-ERROR: default partition is supported only for a list partitioned table
-- cannot specify finite values after UNBOUNDED has been specified
CREATE TABLE range_parted_multicol (a int, b int, c int) PARTITION BY RANGE (a, b, c);
CREATE TABLE fail_part PARTITION OF range_parted_multicol FOR VALUES FROM (1, UNBOUNDED, 1) TO (UNBOUNDED, 1, 1);
@@ -601,6 +598,16 @@ CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30);
ERROR: partition "fail_part" would overlap partition "part2"
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50);
ERROR: partition "fail_part" would overlap partition "part3"
+-- Create a default partition for range partitioned table
+CREATE TABLE range2_default PARTITION OF range_parted2 DEFAULT;
+-- More than one default partition is not allowed, so this should give error
+CREATE TABLE fail_default_part PARTITION OF range_parted2 DEFAULT;
+ERROR: partition "fail_default_part" conflicts with existing default partition "range2_default"
+-- Check if the range for default partitions overlap
+INSERT INTO range_parted2 VALUES (85);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90);
+ERROR: updated partition constraint for default partition "range2_default" would be violated by some row
+CREATE TABLE part4 PARTITION OF range_parted2 FOR VALUES FROM (90) TO (100);
-- now check for multi-column range partition key
CREATE TABLE range_parted3 (
a int,
@@ -614,6 +621,7 @@ CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10)
CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, unbounded);
CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
ERROR: partition "fail_part" would overlap partition "part12"
+CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
-- cannot create a partition that says column b is allowed to range
-- from -infinity to +infinity, while there exist partitions that have
-- more specific ranges
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 6d00d4b..963dfc9 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -271,6 +271,15 @@ insert into range_parted values ('b', 10);
insert into range_parted values ('a');
ERROR: no partition of relation "range_parted" found for row
DETAIL: Partition key of the failing row contains (a, (b + 0)) = (a, null).
+-- Check default partition
+create table part_def partition of range_parted default;
+-- fail
+insert into part_def values ('b', 10);
+ERROR: new row for relation "part_def" violates partition constraint
+DETAIL: Failing row contains (b, 10).
+-- ok
+insert into part_def values ('c', 10);
+insert into range_parted values (null, null);
select tableoid::regclass, * from range_parted;
tableoid | a | b
----------+---+----
@@ -280,7 +289,9 @@ select tableoid::regclass, * from range_parted;
part3 | b | 1
part4 | b | 10
part4 | b | 10
-(6 rows)
+ part_def | c | 10
+ part_def | |
+(8 rows)
-- ok
insert into list_parted values (null, 1);
@@ -435,6 +446,35 @@ with ins (a, b, c) as
mlparted4 | 1 | 30 | 39
(5 rows)
+-- Check multi-level default partition
+create table mlparted_def partition of mlparted default partition by range(a);
+create table mlparted_def1 partition of mlparted_def for values from (40) to (50);
+create table mlparted_def2 partition of mlparted_def for values from (50) to (60);
+insert into mlparted values (40, 100);
+insert into mlparted_def1 values (42, 100);
+insert into mlparted_def2 values (54, 50);
+-- fail
+insert into mlparted values (70, 100);
+ERROR: no partition of relation "mlparted_def" found for row
+DETAIL: Partition key of the failing row contains (a) = (70).
+insert into mlparted_def1 values (52, 50);
+ERROR: new row for relation "mlparted_def1" violates partition constraint
+DETAIL: Failing row contains (52, 50).
+insert into mlparted_def2 values (34, 50);
+ERROR: new row for relation "mlparted_def2" violates partition constraint
+DETAIL: Failing row contains (34, 50).
+-- ok
+create table mlparted_defd partition of mlparted_def default;
+insert into mlparted values (70, 100);
+select tableoid::regclass, * from mlparted_def;
+ tableoid | a | b
+---------------+----+-----
+ mlparted_def1 | 40 | 100
+ mlparted_def1 | 42 | 100
+ mlparted_def2 | 54 | 50
+ mlparted_defd | 70 | 100
+(4 rows)
+
-- check that message shown after failure to find a partition shows the
-- appropriate key description (or none) in various situations
create table key_desc (a int, b int) partition by list ((a+0));
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 6750152..e996640 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -77,6 +77,10 @@ mlparted12|f
mlparted2|f
mlparted3|f
mlparted4|f
+mlparted_def|f
+mlparted_def1|f
+mlparted_def2|f
+mlparted_defd|f
money_data|f
num_data|f
num_exp_add|t
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index 9912ef2..cfb6aa8 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -218,6 +218,15 @@ ERROR: new row for relation "part_b_10_b_20" violates partition constraint
DETAIL: Failing row contains (b, 9).
-- ok
update range_parted set b = b + 1 where b = 10;
+-- Creating default partition for range
+create table part_def partition of range_parted default;
+insert into range_parted values ('c', 9);
+-- ok
+update part_def set a = 'd' where a = 'c';
+-- fail
+update part_def set a = 'a' where a = 'd';
+ERROR: new row for relation "part_def" violates partition constraint
+DETAIL: Failing row contains (a, 9).
create table list_parted (
a text,
b int
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 36c56aa..af7aecc 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2169,6 +2169,21 @@ CREATE TABLE part2 (
);
ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
+-- Create default partition
+CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
+
+-- Only one default partition is allowed, hence, following should give error
+CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS);
+ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT;
+
+-- Overlapping partitions cannot be attached, hence, following should give error
+INSERT INTO partr_def1 VALUES (2, 10);
+CREATE TABLE part3 (LIKE range_parted);
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (2, 10) TO (2, 20);
+
+-- Attaching partitions should be successful when there are no overlapping rows
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (3, 10) TO (3, 20);
+
-- check that leaf partitions are scanned when attaching a partitioned
-- table
CREATE TABLE part_5 (
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index f44c0e0..2011cbf 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -486,8 +486,6 @@ CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a') TO ('z',
-- cannot specify null values in range bounds
CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (unbounded);
--- range partition cannot have default partition
-CREATE TABLE fail_part PARTITION OF range_parted DEFAULT;
-- cannot specify finite values after UNBOUNDED has been specified
CREATE TABLE range_parted_multicol (a int, b int, c int) PARTITION BY RANGE (a, b, c);
@@ -559,6 +557,17 @@ CREATE TABLE part3 PARTITION OF range_parted2 FOR VALUES FROM (30) TO (40);
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30);
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50);
+-- Create a default partition for range partitioned table
+CREATE TABLE range2_default PARTITION OF range_parted2 DEFAULT;
+
+-- More than one default partition is not allowed, so this should give error
+CREATE TABLE fail_default_part PARTITION OF range_parted2 DEFAULT;
+
+-- Check if the range for default partitions overlap
+INSERT INTO range_parted2 VALUES (85);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90);
+CREATE TABLE part4 PARTITION OF range_parted2 FOR VALUES FROM (90) TO (100);
+
-- now check for multi-column range partition key
CREATE TABLE range_parted3 (
a int,
@@ -572,6 +581,7 @@ CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO
CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10);
CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, unbounded);
CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
+CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
-- cannot create a partition that says column b is allowed to range
-- from -infinity to +infinity, while there exist partitions that have
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 6c427f2..4a0b15a 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -168,8 +168,16 @@ insert into range_parted values ('b', 1);
insert into range_parted values ('b', 10);
-- fail (partition key (b+0) is null)
insert into range_parted values ('a');
-select tableoid::regclass, * from range_parted;
+-- Check default partition
+create table part_def partition of range_parted default;
+-- fail
+insert into part_def values ('b', 10);
+-- ok
+insert into part_def values ('c', 10);
+insert into range_parted values (null, null);
+
+select tableoid::regclass, * from range_parted;
-- ok
insert into list_parted values (null, 1);
insert into list_parted (a) values ('aA');
@@ -277,6 +285,23 @@ with ins (a, b, c) as
(insert into mlparted (b, a) select s.a, 1 from generate_series(2, 39) s(a) returning tableoid::regclass, *)
select a, b, min(c), max(c) from ins group by a, b order by 1;
+-- Check multi-level default partition
+create table mlparted_def partition of mlparted default partition by range(a);
+create table mlparted_def1 partition of mlparted_def for values from (40) to (50);
+create table mlparted_def2 partition of mlparted_def for values from (50) to (60);
+insert into mlparted values (40, 100);
+insert into mlparted_def1 values (42, 100);
+insert into mlparted_def2 values (54, 50);
+-- fail
+insert into mlparted values (70, 100);
+insert into mlparted_def1 values (52, 50);
+insert into mlparted_def2 values (34, 50);
+-- ok
+create table mlparted_defd partition of mlparted_def default;
+insert into mlparted values (70, 100);
+
+select tableoid::regclass, * from mlparted_def;
+
-- check that message shown after failure to find a partition shows the
-- appropriate key description (or none) in various situations
create table key_desc (a int, b int) partition by list ((a+0));
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 44fb0dc..1ce7a94 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -125,6 +125,14 @@ update range_parted set b = b - 1 where b = 10;
-- ok
update range_parted set b = b + 1 where b = 10;
+-- Creating default partition for range
+create table part_def partition of range_parted default;
+insert into range_parted values ('c', 9);
+-- ok
+update part_def set a = 'd' where a = 'c';
+-- fail
+update part_def set a = 'a' where a = 'd';
+
create table list_parted (
a text,
b int
On Thu, Jun 15, 2017 at 11:20 AM, Beena Emerson <memissemerson@gmail.com> wrote:
Hello,
PFA the updated patch.
This is rebased over v21 patches of list partition.
(http://www.mail-archive.com/pgsql-hackers@postgresql.org/msg316818.html)
While testing I have noticed segmentation fault for a simple case.
create table test(a int, b int) partition by range(a,b);
create table test1 partition of test DEFAULT;
postgres=# drop table test1;
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
This is basically crashing in RelationBuildPartitionDesc, so I think
we don't have any test case for testing DEFAULT range partition where
partition key has more than one attribute. So I suggest we can add
such test case.
--
Regards,
Dilip Kumar
EnterpriseDB: http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Jun 20, 2017 at 6:57 PM, Dilip Kumar <dilipbalaut@gmail.com> wrote:
This is basically crashing in RelationBuildPartitionDesc, so I think
we don't have any test case for testing DEFAULT range partition where
partition key has more than one attribute. So I suggest we can add
such test case.
Some more comments.
<code>
- bound->datums = (Datum *) palloc0(key->partnatts * sizeof(Datum));
- bound->content = (RangeDatumContent *) palloc0(key->partnatts *
- sizeof(RangeDatumContent));
+ bound->datums = datums ? (Datum *) palloc0(key->partnatts * sizeof(Datum))
+ : NULL;
+ bound->content = (RangeDatumContent *) palloc0(
+ (datums ? key->partnatts : 1) * sizeof(RangeDatumContent));
bound->lower = lower;
i = 0;
+
+ /* If default, datums are NULL */
+ if (datums == NULL)
+ bound->content[i] = RANGE_DATUM_DEFAULT;
</code>
For the default partition we are only setting bound->content[0] to
default, but content for others key
attributes are not initialized. But later in the code, if the content
of the first key is RANGE_DATUM_DEFAULT then it should not access the
next content, but I see there are some exceptions. Which can access
uninitialized value?
for example see below code
<code>
for (i = 0; i < key->partnatts; i++)
{
+ if (rb_content[i] == RANGE_DATUM_DEFAULT) --> why it's going to
content for next parttion attribute, we never initialized that?
+ continue;
+
if (rb_content[i] != RANGE_DATUM_FINITE)
return rb_content[i] == RANGE_DATUM_NEG_INF ? -1 : 1;
</code>
Also
In RelatiobBuildPartitionDesc
<code>
for (j = 0; j < key->partnatts; j++)
{
-- Suppose first is RANGE_DATUM_DEFAULT then we should not check next
because that is never initialized. I think this is the cause of
the crash also what I have reported above.
----
if (rbounds[i]->content[j] == RANGE_DATUM_FINITE)
boundinfo->datums[i][j] =
datumCopy(rbounds[i]->datums[j],
key->parttypbyval[j],
key->parttyplen[j]);
/* Remember, we are storing the tri-state value. */
boundinfo->content[i][j] = rbounds[i]->content[j];
</code>
--
Regards,
Dilip Kumar
EnterpriseDB: http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Jun 21, 2017 at 8:57 AM, Dilip Kumar <dilipbalaut@gmail.com> wrote:
For the default partition we are only setting bound->content[0] to
default, but content for others key
attributes are not initialized. But later in the code, if the content
of the first key is RANGE_DATUM_DEFAULT then it should not access the
next content, but I see there are some exceptions. Which can access
uninitialized value?
I think somebody should do some testing of the existing code with
valgrind. And then apply the list-partitioning patch and this patch,
and do some more testing with valgrind. It seems to be really easy to
miss these uninitialized access problems during code review.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Jun 21, 2017 at 8:08 PM, Robert Haas <robertmhaas@gmail.com> wrote:
I think somebody should do some testing of the existing code with
valgrind. And then apply the list-partitioning patch and this patch,
and do some more testing with valgrind. It seems to be really easy to
miss these uninitialized access problems during code review.
I think it will help, but it may not help in all the scenarios
because most of the places we are allocating memory with palloc0 (
initialized with 0) but it never initialized with RANGE_DATUM_DEFAULT
except the first element (in the case of DEFAULT partition). And,
later they may be considered as RANGE_DATUM_FINITE (because its value
is 0).
One solution can be that if bound is DEFAULT then initialize with
RANGE_DATUM_DEFAULT for the complete content array for that partition
bound instead of just first. Otherwise, we need to be careful of
early exiting wherever we are looping the content array of the DEFAULT
bound.
--
Regards,
Dilip Kumar
EnterpriseDB: http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hi Beena,
I started testing and reviewing the patch. Can you update the patch as v5
patch does not apply cleanly on master?
Thank you,
Rahila Syed
On Wed, Jun 21, 2017 at 8:43 PM, Dilip Kumar <dilipbalaut@gmail.com> wrote:
Show quoted text
On Wed, Jun 21, 2017 at 8:08 PM, Robert Haas <robertmhaas@gmail.com>
wrote:I think somebody should do some testing of the existing code with
valgrind. And then apply the list-partitioning patch and this patch,
and do some more testing with valgrind. It seems to be really easy to
miss these uninitialized access problems during code review.I think it will help, but it may not help in all the scenarios
because most of the places we are allocating memory with palloc0 (
initialized with 0) but it never initialized with RANGE_DATUM_DEFAULT
except the first element (in the case of DEFAULT partition). And,
later they may be considered as RANGE_DATUM_FINITE (because its value
is 0).One solution can be that if bound is DEFAULT then initialize with
RANGE_DATUM_DEFAULT for the complete content array for that partition
bound instead of just first. Otherwise, we need to be careful of
early exiting wherever we are looping the content array of the DEFAULT
bound.--
Regards,
Dilip Kumar
EnterpriseDB: http://www.enterprisedb.com--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Jun 28, 2017 at 12:34 PM, Rahila Syed <rahilasyed90@gmail.com> wrote:
Hi Beena,
I started testing and reviewing the patch. Can you update the patch as v5
patch does not apply cleanly on master?
I am currently working on Dilip's comments, I will update the patch soon.
--
Beena Emerson
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hello Rahila,
On Wed, Jun 28, 2017 at 12:34 PM, Rahila Syed <rahilasyed90@gmail.com> wrote:
Hi Beena,
I started testing and reviewing the patch. Can you update the patch as v5
patch does not apply cleanly on master?
Thanks for looking into this.
The patch is to be applied on Jeevn's partition patch
(http://www.mail-archive.com/pgsql-hackers@postgresql.org/msg316818.html)
which can be applied over commit
a12c09ad86e60a8acb269744b8ee86429dda2cd8.
--
Beena Emerson
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hello Dilip,
On Wed, Jun 21, 2017 at 6:27 PM, Dilip Kumar <dilipbalaut@gmail.com> wrote:
On Tue, Jun 20, 2017 at 6:57 PM, Dilip Kumar <dilipbalaut@gmail.com> wrote:
This is basically crashing in RelationBuildPartitionDesc, so I think
we don't have any test case for testing DEFAULT range partition where
partition key has more than one attribute. So I suggest we can add
such test case.Some more comments.
<code> - bound->datums = (Datum *) palloc0(key->partnatts * sizeof(Datum)); - bound->content = (RangeDatumContent *) palloc0(key->partnatts * - sizeof(RangeDatumContent)); + bound->datums = datums ? (Datum *) palloc0(key->partnatts * sizeof(Datum)) + : NULL; + bound->content = (RangeDatumContent *) palloc0( + (datums ? key->partnatts : 1) * sizeof(RangeDatumContent)); bound->lower = lower;i = 0; + + /* If default, datums are NULL */ + if (datums == NULL) + bound->content[i] = RANGE_DATUM_DEFAULT; </code>For the default partition we are only setting bound->content[0] to
default, but content for others key
attributes are not initialized. But later in the code, if the content
of the first key is RANGE_DATUM_DEFAULT then it should not access the
next content, but I see there are some exceptions. Which can access
uninitialized value?for example see below code
<code> for (i = 0; i < key->partnatts; i++) { + if (rb_content[i] == RANGE_DATUM_DEFAULT) --> why it's going to content for next parttion attribute, we never initialized that? + continue; + if (rb_content[i] != RANGE_DATUM_FINITE) return rb_content[i] == RANGE_DATUM_NEG_INF ? -1 : 1; </code>Also
In RelatiobBuildPartitionDesc<code>
for (j = 0; j < key->partnatts; j++)
{
-- Suppose first is RANGE_DATUM_DEFAULT then we should not check next
because that is never initialized. I think this is the cause of
the crash also what I have reported above.
----
if (rbounds[i]->content[j] == RANGE_DATUM_FINITE)
boundinfo->datums[i][j] =
datumCopy(rbounds[i]->datums[j],
key->parttypbyval[j],
key->parttyplen[j]);
/* Remember, we are storing the tri-state value. */
boundinfo->content[i][j] = rbounds[i]->content[j];
</code>
Thank you for your review and analysis.
I have updated the patch.
- bound->content is set to RANGE_DATUM_DEFAULT for each of the keys
and not just the first one.
- Improve the way of handling DEFAULT bounds in RelationBuildPartitionDesc,
There is a test for multiple column range in alter_table.sql
--
Beena Emerson
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Attachments:
default_range_partition_v6.patchapplication/octet-stream; name=default_range_partition_v6.patchDownload
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 099e048..37b7776 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -72,6 +72,7 @@
typedef enum RangeDatumContent
{
RANGE_DATUM_FINITE = 0, /* actual datum stored elsewhere */
+ RANGE_DATUM_DEFAULT, /* default */
RANGE_DATUM_NEG_INF, /* negative infinity */
RANGE_DATUM_POS_INF /* positive infinity */
} RangeDatumContent;
@@ -134,7 +135,9 @@ static void get_range_key_properties(PartitionKey key, int keynum,
Expr **keyCol,
Const **lower_val, Const **upper_val);
static List *get_qual_for_list(Relation parent, PartitionBoundSpec *spec);
-static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
+ bool for_default);
+static List *get_range_nulltest(PartitionKey key);
static List *generate_partition_qual(Relation rel);
static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index,
@@ -175,11 +178,11 @@ RelationBuildPartitionDesc(Relation rel)
MemoryContext oldcxt;
int ndatums = 0;
+ int default_index = -1;
/* List partitioning specific */
PartitionListValue **all_values = NULL;
int null_index = -1;
- int default_index = -1;
/* Range partitioning specific */
PartitionRangeBound **rbounds = NULL;
@@ -354,6 +357,9 @@ RelationBuildPartitionDesc(Relation rel)
if (spec->strategy != PARTITION_STRATEGY_RANGE)
elog(ERROR, "invalid strategy in partition bound spec");
+ if (spec->is_default)
+ default_index = i;
+
lower = make_one_range_bound(key, i, spec->lowerdatums,
true);
upper = make_one_range_bound(key, i, spec->upperdatums,
@@ -394,6 +400,19 @@ RelationBuildPartitionDesc(Relation rel)
break;
}
+ if (cur->content[i] == RANGE_DATUM_DEFAULT)
+ {
+ /*
+ * Both the upper and lower bound of default partition
+ * are same, and the upper bound is used to set the
+ * mapping later, hence only the upper bound is
+ * considered distinct.
+ */
+ if (!cur->lower)
+ is_distinct = true;
+ break;
+ }
+
/*
* If either of them has infinite element, we can't equate
* them. Even when both are infinite, they'd have
@@ -549,6 +568,7 @@ RelationBuildPartitionDesc(Relation rel)
sizeof(RangeDatumContent *));
boundinfo->indexes = (int *) palloc((ndatums + 1) *
sizeof(int));
+ boundinfo->default_index = -1;
for (i = 0; i < ndatums; i++)
{
@@ -593,6 +613,8 @@ RelationBuildPartitionDesc(Relation rel)
}
}
boundinfo->indexes[i] = -1;
+ if (default_index != -1)
+ boundinfo->default_index = mapping[default_index];
break;
}
@@ -718,7 +740,7 @@ check_new_partition_bound(char *relname, Relation parent,
{
Assert(spec->strategy == PARTITION_STRATEGY_LIST);
- if (partdesc->nparts > 0)
+ if (!spec->is_default && partdesc->nparts > 0)
{
ListCell *cell;
@@ -728,17 +750,6 @@ check_new_partition_bound(char *relname, Relation parent,
partition_bound_accepts_nulls(boundinfo) ||
partition_bound_has_default(boundinfo)));
- /*
- * Default partition cannot be added if there already
- * exists one.
- */
- if (spec->is_default)
- {
- overlap = partition_bound_has_default(boundinfo);
- with = boundinfo->default_index;
- break;
- }
-
foreach(cell, spec->listdatums)
{
Const *val = castNode(Const, lfirst(cell));
@@ -779,6 +790,10 @@ check_new_partition_bound(char *relname, Relation parent,
lower = make_one_range_bound(key, -1, spec->lowerdatums, true);
upper = make_one_range_bound(key, -1, spec->upperdatums, false);
+ /* Default case is not handled here */
+ if (spec->is_default)
+ break;
+
/*
* First check if the resulting range would be empty with
* specified lower and upper bounds
@@ -876,16 +891,20 @@ check_new_partition_bound(char *relname, Relation parent,
(int) key->strategy);
}
- if (overlap)
+ /* Default partition cannot be added if there already exists one. */
+ if (spec->is_default)
{
- Assert(with >= 0);
-
- if (spec->is_default)
+ if (boundinfo && partition_bound_has_default(boundinfo))
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("partition \"%s\" conflicts with existing default partition \"%s\"",
- relname, get_rel_name(partdesc->oids[with])),
+ relname,
+ get_rel_name(partdesc->oids[boundinfo->default_index])),
parser_errposition(pstate, spec->location)));
+ }
+ else if (overlap)
+ {
+ Assert(with >= 0);
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
@@ -917,13 +936,12 @@ check_default_allows_bound(Relation parent, PartitionBoundSpec *new_spec)
if (!boundinfo || !partition_bound_has_default(boundinfo))
return;
- /* Currently default partition is supported only for LIST partition. */
- Assert(new_spec->strategy == PARTITION_STRATEGY_LIST);
-
/* If there exists a default partition, then boundinfo cannot be NULL */
Assert(part_desc->boundinfo != NULL);
- new_part_constraints = get_qual_for_list(parent, new_spec);
+ new_part_constraints = (new_spec->strategy == PARTITION_STRATEGY_LIST)
+ ? get_qual_for_list(parent, new_spec)
+ : get_qual_for_range(parent, new_spec, false);
def_part_constraints = get_default_part_validation_constraint(new_part_constraints);
/*
@@ -1096,7 +1114,7 @@ get_qual_from_partbound(Relation rel, Relation parent,
case PARTITION_STRATEGY_RANGE:
Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
- my_qual = get_qual_for_range(key, spec);
+ my_qual = get_qual_for_range(parent, spec, false);
break;
default:
@@ -1683,6 +1701,53 @@ get_range_key_properties(PartitionKey key, int keynum,
*upper_val = NULL;
}
+ /*
+ * get_range_nulltest
+ *
+ * A non-default range partition table does not currently allow partition
+ * keys to be null, so emit an IS NOT NULL expression for each key column.
+ */
+static List *
+get_range_nulltest(PartitionKey key)
+{
+ List *result = NIL;
+ NullTest *nulltest;
+ ListCell *partexprs_item;
+ int i;
+
+ partexprs_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ Expr *keyCol;
+
+ if (key->partattrs[i] != 0)
+ {
+ keyCol = (Expr *) makeVar(1,
+ key->partattrs[i],
+ key->parttypid[i],
+ key->parttypmod[i],
+ key->parttypcoll[i],
+ 0);
+ }
+ else
+ {
+ if (partexprs_item == NULL)
+ elog(ERROR, "wrong number of partition key expressions");
+ keyCol = copyObject(lfirst(partexprs_item));
+ partexprs_item = lnext(partexprs_item);
+ }
+
+ nulltest = makeNode(NullTest);
+ nulltest->arg = keyCol;
+ nulltest->nulltesttype = IS_NOT_NULL;
+ nulltest->argisrow = false;
+ nulltest->location = -1;
+ result = lappend(result, nulltest);
+ }
+
+ return result;
+}
+
/*
* get_qual_for_range
*
@@ -1723,11 +1788,15 @@ get_range_key_properties(PartitionKey key, int keynum,
* does not really have a constraint, except the IS NOT NULL constraint for
* partition keys.
*
+ * For default partition, it returns the negation of the constraints of all
+ * the other partitions.
+ *
* If we end up with an empty result list, we return a single-member list
* containing a constant TRUE, because callers expect a non-empty list.
*/
static List *
-get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
+get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
+ bool for_default)
{
List *result = NIL;
ListCell *cell1,
@@ -1738,10 +1807,10 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
j;
PartitionRangeDatum *ldatum,
*udatum;
+ PartitionKey key = RelationGetPartitionKey(parent);
Expr *keyCol;
Const *lower_val,
*upper_val;
- NullTest *nulltest;
List *lower_or_arms,
*upper_or_arms;
int num_or_arms,
@@ -1751,44 +1820,70 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
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;
-
- /*
- * A range-partitioned table does not currently allow partition keys to be
- * null, so emit an IS NOT NULL expression for each key column.
- */
- partexprs_item = list_head(key->partexprs);
- for (i = 0; i < key->partnatts; i++)
+ if (spec->is_default)
{
- Expr *keyCol;
+ List *or_expr_args = NIL;
+ PartitionDesc pdesc = RelationGetPartitionDesc(parent);
+ Oid *inhoids = pdesc->oids;
+ int nparts = pdesc->nparts,
+ i;
- if (key->partattrs[i] != 0)
+ for (i = 0; i < nparts; i++)
{
- keyCol = (Expr *) makeVar(1,
- key->partattrs[i],
- key->parttypid[i],
- key->parttypmod[i],
- key->parttypcoll[i],
- 0);
+ Oid inhrelid = inhoids[i];
+ HeapTuple tuple;
+ Datum datum;
+ bool isnull;
+ PartitionBoundSpec *bspec;
+
+ tuple = SearchSysCache1(RELOID, inhrelid);
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", inhrelid);
+
+ datum = SysCacheGetAttr(RELOID, tuple,
+ Anum_pg_class_relpartbound,
+ &isnull);
+
+ Assert(!isnull);
+ bspec = (PartitionBoundSpec *) stringToNode(TextDatumGetCString(datum));
+
+ if (!bspec->is_default)
+ {
+ List *part_qual = get_qual_for_range(parent, bspec, true);
+
+ /*
+ * AND the constraints of the partition and add to
+ * or_expr_args
+ */
+ or_expr_args = lappend(or_expr_args, list_length(part_qual) > 1
+ ? makeBoolExpr(AND_EXPR, part_qual, -1)
+ : linitial(part_qual));
+ }
+ ReleaseSysCache(tuple);
}
- else
+
+ if (or_expr_args != NIL)
{
- if (partexprs_item == NULL)
- elog(ERROR, "wrong number of partition key expressions");
- keyCol = copyObject(lfirst(partexprs_item));
- partexprs_item = lnext(partexprs_item);
+ /* OR all the non-default partition constraints; then negate it */
+ result = lappend(result,
+ list_length(or_expr_args) > 1
+ ? makeBoolExpr(OR_EXPR, or_expr_args, -1)
+ : linitial(or_expr_args));
+ result = list_make1(makeBoolExpr(NOT_EXPR, result, -1));
}
+ else /* default is the only partition */
+ result = list_make1(makeBoolConst(true, false));
- nulltest = makeNode(NullTest);
- nulltest->arg = keyCol;
- nulltest->nulltesttype = IS_NOT_NULL;
- nulltest->argisrow = false;
- nulltest->location = -1;
- result = lappend(result, nulltest);
+ return result;
}
+ lower_or_start_datum = list_head(spec->lowerdatums);
+ upper_or_start_datum = list_head(spec->upperdatums);
+ num_or_arms = key->partnatts;
+
+ if (!for_default)
+ result = get_range_nulltest(key);
+
/*
* Iterate over the key columns and check if the corresponding lower and
* upper datums are equal using the btree equality operator for the
@@ -2002,7 +2097,9 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
/* As noted above, caller expects the list to be non-empty. */
if (result == NIL)
- result = list_make1(makeBoolConst(true, false));
+ result = for_default
+ ? get_range_nulltest(key)
+ : list_make1(makeBoolConst(true, false));
return result;
}
@@ -2170,8 +2267,7 @@ get_partition_for_tuple(PartitionDispatch *pd,
Datum values[PARTITION_MAX_KEYS];
bool isnull[PARTITION_MAX_KEYS];
int cur_offset,
- cur_index;
- int i,
+ cur_index = -1,
result;
ExprContext *ecxt = GetPerTupleExprContext(estate);
TupleTableSlot *ecxt_scantuple_old = ecxt->ecxt_scantuple;
@@ -2215,24 +2311,6 @@ get_partition_for_tuple(PartitionDispatch *pd,
ecxt->ecxt_scantuple = slot;
FormPartitionKeyDatum(parent, slot, estate, values, isnull);
- if (key->strategy == PARTITION_STRATEGY_RANGE)
- {
- /*
- * Since we cannot route tuples with NULL partition keys through a
- * range-partitioned table, simply return that no partition exists
- */
- for (i = 0; i < key->partnatts; i++)
- {
- if (isnull[i])
- {
- *failed_at = parent;
- *failed_slot = slot;
- result = -1;
- goto error_exit;
- }
- }
- }
-
/*
* Handle NULL partition key here if there's a null-accepting list
* partition, else later it will be routed to the default partition if
@@ -2338,12 +2416,19 @@ make_one_range_bound(PartitionKey key, int index, List *datums, bool lower)
bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
bound->index = index;
- bound->datums = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+ bound->datums = datums ? (Datum *) palloc0(key->partnatts * sizeof(Datum))
+ : NULL;
bound->content = (RangeDatumContent *) palloc0(key->partnatts *
sizeof(RangeDatumContent));
bound->lower = lower;
i = 0;
+
+ /* datums are NULL for default */
+ if (datums == NULL)
+ for (i = 0; i < key->partnatts; i++)
+ bound->content[i] = RANGE_DATUM_DEFAULT;
+
foreach(lc, datums)
{
PartitionRangeDatum *datum = castNode(PartitionRangeDatum, lfirst(lc));
@@ -2400,23 +2485,31 @@ 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_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 non-finite, so they are arranged according to their
+ * enum sequence - default, negative infinity, positive infinity.
*/
return content1[i] == content2[i] ? 0
: (content1[i] < content2[i] ? -1 : 1);
+
+ /*
+ * Comparing a finite and a non-finite value.
+ *
+ * Positive infinity is higher than finite value and default or
+ * negative infinity is lower than the finite value
+ */
else if (content1[i] != RANGE_DATUM_FINITE)
- return content1[i] == RANGE_DATUM_NEG_INF ? -1 : 1;
+ return content1[i] == RANGE_DATUM_POS_INF ? 1 : -1;
else if (content2[i] != RANGE_DATUM_FINITE)
- return content2[i] == RANGE_DATUM_NEG_INF ? 1 : -1;
+ return content2[i] == RANGE_DATUM_POS_INF ? -1 : 1;
+ /* Both are finite value */
cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
key->partcollation[i],
datums1[i],
@@ -2453,6 +2546,9 @@ partition_rbound_datum_cmp(PartitionKey key,
for (i = 0; i < key->partnatts; i++)
{
+ if (rb_content[i] == RANGE_DATUM_DEFAULT)
+ continue;
+
if (rb_content[i] != RANGE_DATUM_FINITE)
return rb_content[i] == RANGE_DATUM_NEG_INF ? -1 : 1;
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 0fe6adf..4ab033d 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3307,11 +3307,6 @@ transformPartitionBound(ParseState *pstate, Relation parent,
if (spec->is_default)
{
- if (strategy != PARTITION_STRATEGY_LIST)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("default partition is supported only for a list partitioned table")));
-
/*
* In case of the default partition, parser had no way to identify the
* partition strategy. Assign the parent strategy to the default
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 0c1e72a..97d1a9a 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8650,7 +8650,6 @@ get_rule_expr(Node *node, deparse_context *context,
if (spec->is_default)
{
- Assert(strategy == PARTITION_STRATEGY_LIST);
appendStringInfoString(buf, "DEFAULT");
break;
}
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 1cfe1bd..e29f240 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3340,6 +3340,19 @@ CREATE TABLE part2 (
);
ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
INFO: partition constraint for table "part2" is implied by existing constraints
+-- Create default partition
+CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
+-- Only one default partition is allowed, hence, following should give error
+CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS);
+ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT;
+ERROR: partition "partr_def2" conflicts with existing default partition "partr_def1"
+-- Overlapping partitions cannot be attached, hence, following should give error
+INSERT INTO partr_def1 VALUES (2, 10);
+CREATE TABLE part3 (LIKE range_parted);
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (2, 10) TO (2, 20);
+ERROR: updated partition constraint for default partition would be violated by some row
+-- Attaching partitions should be successful when there are no overlapping rows
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (3, 10) TO (3, 20);
-- check that leaf partitions are scanned when attaching a partitioned
-- table
CREATE TABLE part_5 (
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 5d09c9f..43c5dd5 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -517,9 +517,6 @@ ERROR: TO must specify exactly one value per partitioning column
-- cannot specify null values in range bounds
CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (unbounded);
ERROR: cannot specify NULL in range bound
--- range partition cannot have default partition
-CREATE TABLE fail_part PARTITION OF range_parted DEFAULT;
-ERROR: default partition is supported only for a list partitioned table
-- cannot specify finite values after UNBOUNDED has been specified
CREATE TABLE range_parted_multicol (a int, b int, c int) PARTITION BY RANGE (a, b, c);
CREATE TABLE fail_part PARTITION OF range_parted_multicol FOR VALUES FROM (1, UNBOUNDED, 1) TO (UNBOUNDED, 1, 1);
@@ -601,6 +598,16 @@ CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30);
ERROR: partition "fail_part" would overlap partition "part2"
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50);
ERROR: partition "fail_part" would overlap partition "part3"
+-- Create a default partition for range partitioned table
+CREATE TABLE range2_default PARTITION OF range_parted2 DEFAULT;
+-- More than one default partition is not allowed, so this should give error
+CREATE TABLE fail_default_part PARTITION OF range_parted2 DEFAULT;
+ERROR: partition "fail_default_part" conflicts with existing default partition "range2_default"
+-- Check if the range for default partitions overlap
+INSERT INTO range_parted2 VALUES (85);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90);
+ERROR: updated partition constraint for default partition "range2_default" would be violated by some row
+CREATE TABLE part4 PARTITION OF range_parted2 FOR VALUES FROM (90) TO (100);
-- now check for multi-column range partition key
CREATE TABLE range_parted3 (
a int,
@@ -614,6 +621,7 @@ CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10)
CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, unbounded);
CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
ERROR: partition "fail_part" would overlap partition "part12"
+CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
-- cannot create a partition that says column b is allowed to range
-- from -infinity to +infinity, while there exist partitions that have
-- more specific ranges
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 6d00d4b..963dfc9 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -271,6 +271,15 @@ insert into range_parted values ('b', 10);
insert into range_parted values ('a');
ERROR: no partition of relation "range_parted" found for row
DETAIL: Partition key of the failing row contains (a, (b + 0)) = (a, null).
+-- Check default partition
+create table part_def partition of range_parted default;
+-- fail
+insert into part_def values ('b', 10);
+ERROR: new row for relation "part_def" violates partition constraint
+DETAIL: Failing row contains (b, 10).
+-- ok
+insert into part_def values ('c', 10);
+insert into range_parted values (null, null);
select tableoid::regclass, * from range_parted;
tableoid | a | b
----------+---+----
@@ -280,7 +289,9 @@ select tableoid::regclass, * from range_parted;
part3 | b | 1
part4 | b | 10
part4 | b | 10
-(6 rows)
+ part_def | c | 10
+ part_def | |
+(8 rows)
-- ok
insert into list_parted values (null, 1);
@@ -435,6 +446,35 @@ with ins (a, b, c) as
mlparted4 | 1 | 30 | 39
(5 rows)
+-- Check multi-level default partition
+create table mlparted_def partition of mlparted default partition by range(a);
+create table mlparted_def1 partition of mlparted_def for values from (40) to (50);
+create table mlparted_def2 partition of mlparted_def for values from (50) to (60);
+insert into mlparted values (40, 100);
+insert into mlparted_def1 values (42, 100);
+insert into mlparted_def2 values (54, 50);
+-- fail
+insert into mlparted values (70, 100);
+ERROR: no partition of relation "mlparted_def" found for row
+DETAIL: Partition key of the failing row contains (a) = (70).
+insert into mlparted_def1 values (52, 50);
+ERROR: new row for relation "mlparted_def1" violates partition constraint
+DETAIL: Failing row contains (52, 50).
+insert into mlparted_def2 values (34, 50);
+ERROR: new row for relation "mlparted_def2" violates partition constraint
+DETAIL: Failing row contains (34, 50).
+-- ok
+create table mlparted_defd partition of mlparted_def default;
+insert into mlparted values (70, 100);
+select tableoid::regclass, * from mlparted_def;
+ tableoid | a | b
+---------------+----+-----
+ mlparted_def1 | 40 | 100
+ mlparted_def1 | 42 | 100
+ mlparted_def2 | 54 | 50
+ mlparted_defd | 70 | 100
+(4 rows)
+
-- check that message shown after failure to find a partition shows the
-- appropriate key description (or none) in various situations
create table key_desc (a int, b int) partition by list ((a+0));
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 6750152..e996640 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -77,6 +77,10 @@ mlparted12|f
mlparted2|f
mlparted3|f
mlparted4|f
+mlparted_def|f
+mlparted_def1|f
+mlparted_def2|f
+mlparted_defd|f
money_data|f
num_data|f
num_exp_add|t
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index 9912ef2..cfb6aa8 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -218,6 +218,15 @@ ERROR: new row for relation "part_b_10_b_20" violates partition constraint
DETAIL: Failing row contains (b, 9).
-- ok
update range_parted set b = b + 1 where b = 10;
+-- Creating default partition for range
+create table part_def partition of range_parted default;
+insert into range_parted values ('c', 9);
+-- ok
+update part_def set a = 'd' where a = 'c';
+-- fail
+update part_def set a = 'a' where a = 'd';
+ERROR: new row for relation "part_def" violates partition constraint
+DETAIL: Failing row contains (a, 9).
create table list_parted (
a text,
b int
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 7e6f9d7..28dd714 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2169,6 +2169,21 @@ CREATE TABLE part2 (
);
ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
+-- Create default partition
+CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
+
+-- Only one default partition is allowed, hence, following should give error
+CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS);
+ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT;
+
+-- Overlapping partitions cannot be attached, hence, following should give error
+INSERT INTO partr_def1 VALUES (2, 10);
+CREATE TABLE part3 (LIKE range_parted);
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (2, 10) TO (2, 20);
+
+-- Attaching partitions should be successful when there are no overlapping rows
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (3, 10) TO (3, 20);
+
-- check that leaf partitions are scanned when attaching a partitioned
-- table
CREATE TABLE part_5 (
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index f44c0e0..2011cbf 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -486,8 +486,6 @@ CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a') TO ('z',
-- cannot specify null values in range bounds
CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (unbounded);
--- range partition cannot have default partition
-CREATE TABLE fail_part PARTITION OF range_parted DEFAULT;
-- cannot specify finite values after UNBOUNDED has been specified
CREATE TABLE range_parted_multicol (a int, b int, c int) PARTITION BY RANGE (a, b, c);
@@ -559,6 +557,17 @@ CREATE TABLE part3 PARTITION OF range_parted2 FOR VALUES FROM (30) TO (40);
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30);
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50);
+-- Create a default partition for range partitioned table
+CREATE TABLE range2_default PARTITION OF range_parted2 DEFAULT;
+
+-- More than one default partition is not allowed, so this should give error
+CREATE TABLE fail_default_part PARTITION OF range_parted2 DEFAULT;
+
+-- Check if the range for default partitions overlap
+INSERT INTO range_parted2 VALUES (85);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90);
+CREATE TABLE part4 PARTITION OF range_parted2 FOR VALUES FROM (90) TO (100);
+
-- now check for multi-column range partition key
CREATE TABLE range_parted3 (
a int,
@@ -572,6 +581,7 @@ CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO
CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10);
CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, unbounded);
CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
+CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
-- cannot create a partition that says column b is allowed to range
-- from -infinity to +infinity, while there exist partitions that have
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 6c427f2..4a0b15a 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -168,8 +168,16 @@ insert into range_parted values ('b', 1);
insert into range_parted values ('b', 10);
-- fail (partition key (b+0) is null)
insert into range_parted values ('a');
-select tableoid::regclass, * from range_parted;
+-- Check default partition
+create table part_def partition of range_parted default;
+-- fail
+insert into part_def values ('b', 10);
+-- ok
+insert into part_def values ('c', 10);
+insert into range_parted values (null, null);
+
+select tableoid::regclass, * from range_parted;
-- ok
insert into list_parted values (null, 1);
insert into list_parted (a) values ('aA');
@@ -277,6 +285,23 @@ with ins (a, b, c) as
(insert into mlparted (b, a) select s.a, 1 from generate_series(2, 39) s(a) returning tableoid::regclass, *)
select a, b, min(c), max(c) from ins group by a, b order by 1;
+-- Check multi-level default partition
+create table mlparted_def partition of mlparted default partition by range(a);
+create table mlparted_def1 partition of mlparted_def for values from (40) to (50);
+create table mlparted_def2 partition of mlparted_def for values from (50) to (60);
+insert into mlparted values (40, 100);
+insert into mlparted_def1 values (42, 100);
+insert into mlparted_def2 values (54, 50);
+-- fail
+insert into mlparted values (70, 100);
+insert into mlparted_def1 values (52, 50);
+insert into mlparted_def2 values (34, 50);
+-- ok
+create table mlparted_defd partition of mlparted_def default;
+insert into mlparted values (70, 100);
+
+select tableoid::regclass, * from mlparted_def;
+
-- check that message shown after failure to find a partition shows the
-- appropriate key description (or none) in various situations
create table key_desc (a int, b int) partition by list ((a+0));
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 44fb0dc..1ce7a94 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -125,6 +125,14 @@ update range_parted set b = b - 1 where b = 10;
-- ok
update range_parted set b = b + 1 where b = 10;
+-- Creating default partition for range
+create table part_def partition of range_parted default;
+insert into range_parted values ('c', 9);
+-- ok
+update part_def set a = 'd' where a = 'c';
+-- fail
+update part_def set a = 'a' where a = 'd';
+
create table list_parted (
a text,
b int
On Fri, Jun 30, 2017 at 11:56 AM, Beena Emerson <memissemerson@gmail.com> wrote:
Hello Dilip,
On Wed, Jun 21, 2017 at 6:27 PM, Dilip Kumar <dilipbalaut@gmail.com> wrote:
On Tue, Jun 20, 2017 at 6:57 PM, Dilip Kumar <dilipbalaut@gmail.com> wrote:
This is basically crashing in RelationBuildPartitionDesc, so I think
Thank you for your review and analysis.
I have updated the patch.
- bound->content is set to RANGE_DATUM_DEFAULT for each of the keys
and not just the first one.
- Improve the way of handling DEFAULT bounds in RelationBuildPartitionDesc,There is a test for multiple column range in alter_table.sql
Thanks for update patch, After this fix segmentation fault is solved.
I have some more comments.
1.
lower = make_one_range_bound(key, -1, spec->lowerdatums, true);
upper = make_one_range_bound(key, -1, spec->upperdatums, false);
+ /* Default case is not handled here */
+ if (spec->is_default)
+ break;
+
I think we can move default check just above the make range bound it
will avoid unnecessary processing.
2.
RelationBuildPartitionDesc
{
....
/*
* If either of them has infinite element, we can't equate
* them. Even when both are infinite, they'd have
* opposite signs, because only one of cur and prev is a
* lower bound).
*/
if (cur->content[j] != RANGE_DATUM_FINITE ||
prev->content[j] != RANGE_DATUM_FINITE)
{
is_distinct = true;
break;
}
After default range partitioning patch the above comment doesn't sound
very accurate and
can lead to the confusion, now here we are not only considering
infinite bounds but
there is also a possibility that prev bound can be DEFAULT, so
comments should clearly
mention that.
3.
+ bound->datums = datums ? (Datum *) palloc0(key->partnatts * sizeof(Datum))
+ : NULL;
bound->content = (RangeDatumContent *) palloc0(key->partnatts *
sizeof(RangeDatumContent));
bound->lower = lower;
i = 0;
+
+ /* datums are NULL for default */
+ if (datums == NULL)
+ for (i = 0; i < key->partnatts; i++)
+ bound->content[i] = RANGE_DATUM_DEFAULT;
To me, it will look cleaner if we keep bound->content=NULL for
default, instead of allocating memory
and initializing each element, But it's just a suggestions and you
can decide whichever way
seems better to you. Then the other places e.g.
+ if (cur->content[i] == RANGE_DATUM_DEFAULT)
+ {
+ /*
will change like
+ if (cur->content == NULL)
+ {
+ /*
--
Regards,
Dilip Kumar
EnterpriseDB: http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hello Beena,
Thanks for the updated patch. It passes the basic tests which I performed.
Few comments:
1. In check_new_partition_bound(),
/* Default case is not handled here */
if (spec->is_default)
break;
The default partition check here can be performed in common for both range
and list partition.
2. RANGE_DATUM_FINITE = 0, /* actual datum stored elsewhere */
+ RANGE_DATUM_DEFAULT, /* default */
More elaborative comment?
3. Inside make_one_range_bound(),
+ + /* datums are NULL for default */ + if (datums == NULL) + for (i = 0; i < key->partnatts; i++) + bound->content[i] = RANGE_DATUM_DEFAULT; +
IMO, returning bound at this stage will make code clearer as further
processing
does not happen for default partition.
4.
@@ -549,6 +568,7 @@ RelationBuildPartitionDesc(Relation rel)
sizeof(RangeDatumContent
*));
boundinfo->indexes = (int *) palloc((ndatums + 1) *
sizeof(int));
+ boundinfo->default_index = -1;
This should also be done commonly for both default list and range partition.
5.
if (!spec->is_default && partdesc->nparts > 0)
{
ListCell *cell;
Assert(boundinfo &&
boundinfo->strategy == PARTITION_STRATEGY_LIST &&
(boundinfo->ndatums > 0 ||
partition_bound_accepts_nulls(boundinfo) ||
partition_bound_has_default(boundinfo)));
The assertion condition partition_bound_has_default(boundinfo) is rendered
useless
because of if condition change and can be removed perhaps.
6. I think its better to have following regression tests coverage
--Testing with inserting one NULL and other non NULL values for multicolumn
range partitioned table.
postgres=# CREATE TABLE range_parted (
postgres(# a int,
postgres(# b int
postgres(# ) PARTITION BY RANGE (a, b);
CREATE TABLE
postgres=#
postgres=# CREATE TABLE part1 (
postgres(# a int NOT NULL CHECK (a = 1),
postgres(# b int NOT NULL CHECK (b >= 1 AND b <= 10)
postgres(# );
CREATE TABLE
postgres=# ALTER TABLE range_parted ATTACH PARTITION part1 FOR VALUES FROM
(1, 1) TO (1, 10);
ALTER TABLE
postgres=# CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
CREATE TABLE
postgres=# insert into range_parted values (1,NULL);
INSERT 0 1
postgres=# insert into range_parted values (NULL,9);
INSERT 0 1
--Testing the boundary conditions like in the above example
following should pass.
postgres=# insert into partr_def1 values (1,10);
INSERT 0 1
Thank you,
Rahila Syed
On Mon, Jul 3, 2017 at 8:00 PM, Dilip Kumar <dilipbalaut@gmail.com> wrote:
Show quoted text
On Fri, Jun 30, 2017 at 11:56 AM, Beena Emerson <memissemerson@gmail.com>
wrote:Hello Dilip,
On Wed, Jun 21, 2017 at 6:27 PM, Dilip Kumar <dilipbalaut@gmail.com>
wrote:
On Tue, Jun 20, 2017 at 6:57 PM, Dilip Kumar <dilipbalaut@gmail.com>
wrote:
This is basically crashing in RelationBuildPartitionDesc, so I think
Thank you for your review and analysis.
I have updated the patch.
- bound->content is set to RANGE_DATUM_DEFAULT for each of the keys
and not just the first one.
- Improve the way of handling DEFAULT bounds inRelationBuildPartitionDesc,
There is a test for multiple column range in alter_table.sql
Thanks for update patch, After this fix segmentation fault is solved.
I have some more comments.
1.
lower = make_one_range_bound(key, -1, spec->lowerdatums, true);
upper = make_one_range_bound(key, -1, spec->upperdatums, false);+ /* Default case is not handled here */ + if (spec->is_default) + break; +I think we can move default check just above the make range bound it
will avoid unnecessary processing.2.
RelationBuildPartitionDesc
{
..../*
* If either of them has infinite element, we can't equate
* them. Even when both are infinite, they'd have
* opposite signs, because only one of cur and prev is a
* lower bound).
*/
if (cur->content[j] != RANGE_DATUM_FINITE ||
prev->content[j] != RANGE_DATUM_FINITE)
{
is_distinct = true;
break;
}
After default range partitioning patch the above comment doesn't sound
very accurate and
can lead to the confusion, now here we are not only considering
infinite bounds but
there is also a possibility that prev bound can be DEFAULT, so
comments should clearly
mention that.3.
+ bound->datums = datums ? (Datum *) palloc0(key->partnatts * sizeof(Datum)) + : NULL; bound->content = (RangeDatumContent *) palloc0(key->partnatts * sizeof(RangeDatumContent)); bound->lower = lower;i = 0; + + /* datums are NULL for default */ + if (datums == NULL) + for (i = 0; i < key->partnatts; i++) + bound->content[i] = RANGE_DATUM_DEFAULT;To me, it will look cleaner if we keep bound->content=NULL for default, instead of allocating memory and initializing each element, But it's just a suggestions and you can decide whichever way seems better to you. Then the other places e.g. + if (cur->content[i] == RANGE_DATUM_DEFAULT) + { + /*will change like
+ if (cur->content == NULL) + { + /*--
Regards,
Dilip Kumar
EnterpriseDB: http://www.enterprisedb.com
Thank you for your review Dilip and Rahila.
PFA the updated patch which is rebased to Jeevan's latest list
partition patch [1] and also handles your comments.
/messages/by-id/CAOgcT0OARciE2X+U0rjSKp9VuC279dYcCGkc3nCWKhHQ1_m2rw@mail.gmail.com
--
Beena Emerson
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Attachments:
default_range_partition_v7.patchapplication/octet-stream; name=default_range_partition_v7.patchDownload
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index b31c272..47e5495 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -70,10 +70,11 @@
* is an upper bound.
*/
-/* Ternary value to represent what's contained in a range bound datum */
+/* Value to represent what's contained in a range bound datum */
typedef enum RangeDatumContent
{
RANGE_DATUM_FINITE = 0, /* actual datum stored elsewhere */
+ RANGE_DATUM_DEFAULT, /* default */
RANGE_DATUM_NEG_INF, /* negative infinity */
RANGE_DATUM_POS_INF /* positive infinity */
} RangeDatumContent;
@@ -136,7 +137,9 @@ static void get_range_key_properties(PartitionKey key, int keynum,
Expr **keyCol,
Const **lower_val, Const **upper_val);
static List *get_qual_for_list(Relation parent, PartitionBoundSpec *spec);
-static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
+ bool for_default);
+static List *get_range_nulltest(PartitionKey key);
static List *generate_partition_qual(Relation rel);
static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index,
@@ -177,11 +180,11 @@ RelationBuildPartitionDesc(Relation rel)
MemoryContext oldcxt;
int ndatums = 0;
+ int default_index = -1;
/* List partitioning specific */
PartitionListValue **all_values = NULL;
int null_index = -1;
- int default_index = -1;
/* Range partitioning specific */
PartitionRangeBound **rbounds = NULL;
@@ -356,6 +359,9 @@ RelationBuildPartitionDesc(Relation rel)
if (spec->strategy != PARTITION_STRATEGY_RANGE)
elog(ERROR, "invalid strategy in partition bound spec");
+ if (spec->is_default)
+ default_index = i;
+
lower = make_one_range_bound(key, i, spec->lowerdatums,
true);
upper = make_one_range_bound(key, i, spec->upperdatums,
@@ -396,9 +402,23 @@ RelationBuildPartitionDesc(Relation rel)
break;
}
+ if (cur->content[i] == RANGE_DATUM_DEFAULT)
+ {
+ /*
+ * Both the upper and lower bound of default partition
+ * are same, and the upper bound is used to set the
+ * mapping later, hence only the upper bound is
+ * considered distinct.
+ */
+ if (!cur->lower)
+ is_distinct = true;
+ break;
+ }
+
/*
* If either of them has infinite element, we can't equate
- * them. Even when both are infinite, they'd have
+ * them. One of them could be default and the other
+ * infinte and even if both are infinite, they'd have
* opposite signs, because only one of cur and prev is a
* lower bound).
*/
@@ -595,6 +615,8 @@ RelationBuildPartitionDesc(Relation rel)
}
}
boundinfo->indexes[i] = -1;
+ if (default_index != -1)
+ boundinfo->default_index = mapping[default_index];
break;
}
@@ -783,6 +805,7 @@ check_new_partition_bound(char *relname, Relation parent,
*upper;
Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
+
lower = make_one_range_bound(key, -1, spec->lowerdatums, true);
upper = make_one_range_bound(key, -1, spec->upperdatums, false);
@@ -895,10 +918,10 @@ check_default_allows_bound(Relation parent, Relation default_rel,
List *all_parts;
ListCell *lc;
- /* Currently default partition is supported only for LIST partition. */
- Assert(new_spec->strategy == PARTITION_STRATEGY_LIST);
- new_part_constraints = get_qual_for_list(parent, new_spec);
+ new_part_constraints = (new_spec->strategy == PARTITION_STRATEGY_LIST)
+ ? get_qual_for_list(parent, new_spec)
+ : get_qual_for_range(parent, new_spec, false);
def_part_constraints =
get_default_part_validation_constraint(new_part_constraints);
@@ -1098,7 +1121,7 @@ get_qual_from_partbound(Relation rel, Relation parent,
case PARTITION_STRATEGY_RANGE:
Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
- my_qual = get_qual_for_range(key, spec);
+ my_qual = get_qual_for_range(parent, spec, false);
break;
default:
@@ -1720,6 +1743,53 @@ get_range_key_properties(PartitionKey key, int keynum,
*upper_val = NULL;
}
+ /*
+ * get_range_nulltest
+ *
+ * A non-default range partition table does not currently allow partition
+ * keys to be null, so emit an IS NOT NULL expression for each key column.
+ */
+static List *
+get_range_nulltest(PartitionKey key)
+{
+ List *result = NIL;
+ NullTest *nulltest;
+ ListCell *partexprs_item;
+ int i;
+
+ partexprs_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ Expr *keyCol;
+
+ if (key->partattrs[i] != 0)
+ {
+ keyCol = (Expr *) makeVar(1,
+ key->partattrs[i],
+ key->parttypid[i],
+ key->parttypmod[i],
+ key->parttypcoll[i],
+ 0);
+ }
+ else
+ {
+ if (partexprs_item == NULL)
+ elog(ERROR, "wrong number of partition key expressions");
+ keyCol = copyObject(lfirst(partexprs_item));
+ partexprs_item = lnext(partexprs_item);
+ }
+
+ nulltest = makeNode(NullTest);
+ nulltest->arg = keyCol;
+ nulltest->nulltesttype = IS_NOT_NULL;
+ nulltest->argisrow = false;
+ nulltest->location = -1;
+ result = lappend(result, nulltest);
+ }
+
+ return result;
+}
+
/*
* get_qual_for_range
*
@@ -1760,11 +1830,15 @@ get_range_key_properties(PartitionKey key, int keynum,
* does not really have a constraint, except the IS NOT NULL constraint for
* partition keys.
*
+ * For default partition, it returns the negation of the constraints of all
+ * the other partitions.
+ *
* If we end up with an empty result list, we return a single-member list
* containing a constant TRUE, because callers expect a non-empty list.
*/
static List *
-get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
+get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
+ bool for_default)
{
List *result = NIL;
ListCell *cell1,
@@ -1775,10 +1849,10 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
j;
PartitionRangeDatum *ldatum,
*udatum;
+ PartitionKey key = RelationGetPartitionKey(parent);
Expr *keyCol;
Const *lower_val,
*upper_val;
- NullTest *nulltest;
List *lower_or_arms,
*upper_or_arms;
int num_or_arms,
@@ -1788,44 +1862,70 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
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;
-
- /*
- * A range-partitioned table does not currently allow partition keys to be
- * null, so emit an IS NOT NULL expression for each key column.
- */
- partexprs_item = list_head(key->partexprs);
- for (i = 0; i < key->partnatts; i++)
+ if (spec->is_default)
{
- Expr *keyCol;
+ List *or_expr_args = NIL;
+ PartitionDesc pdesc = RelationGetPartitionDesc(parent);
+ Oid *inhoids = pdesc->oids;
+ int nparts = pdesc->nparts,
+ i;
- if (key->partattrs[i] != 0)
+ for (i = 0; i < nparts; i++)
{
- keyCol = (Expr *) makeVar(1,
- key->partattrs[i],
- key->parttypid[i],
- key->parttypmod[i],
- key->parttypcoll[i],
- 0);
+ Oid inhrelid = inhoids[i];
+ HeapTuple tuple;
+ Datum datum;
+ bool isnull;
+ PartitionBoundSpec *bspec;
+
+ tuple = SearchSysCache1(RELOID, inhrelid);
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", inhrelid);
+
+ datum = SysCacheGetAttr(RELOID, tuple,
+ Anum_pg_class_relpartbound,
+ &isnull);
+
+ Assert(!isnull);
+ bspec = (PartitionBoundSpec *) stringToNode(TextDatumGetCString(datum));
+
+ if (!bspec->is_default)
+ {
+ List *part_qual = get_qual_for_range(parent, bspec, true);
+
+ /*
+ * AND the constraints of the partition and add to
+ * or_expr_args
+ */
+ or_expr_args = lappend(or_expr_args, list_length(part_qual) > 1
+ ? makeBoolExpr(AND_EXPR, part_qual, -1)
+ : linitial(part_qual));
+ }
+ ReleaseSysCache(tuple);
}
- else
+
+ if (or_expr_args != NIL)
{
- if (partexprs_item == NULL)
- elog(ERROR, "wrong number of partition key expressions");
- keyCol = copyObject(lfirst(partexprs_item));
- partexprs_item = lnext(partexprs_item);
+ /* OR all the non-default partition constraints; then negate it */
+ result = lappend(result,
+ list_length(or_expr_args) > 1
+ ? makeBoolExpr(OR_EXPR, or_expr_args, -1)
+ : linitial(or_expr_args));
+ result = list_make1(makeBoolExpr(NOT_EXPR, result, -1));
}
+ else /* default is the only partition */
+ result = list_make1(makeBoolConst(true, false));
- nulltest = makeNode(NullTest);
- nulltest->arg = keyCol;
- nulltest->nulltesttype = IS_NOT_NULL;
- nulltest->argisrow = false;
- nulltest->location = -1;
- result = lappend(result, nulltest);
+ return result;
}
+ lower_or_start_datum = list_head(spec->lowerdatums);
+ upper_or_start_datum = list_head(spec->upperdatums);
+ num_or_arms = key->partnatts;
+
+ if (!for_default)
+ result = get_range_nulltest(key);
+
/*
* Iterate over the key columns and check if the corresponding lower and
* upper datums are equal using the btree equality operator for the
@@ -2039,7 +2139,9 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
/* As noted above, caller expects the list to be non-empty. */
if (result == NIL)
- result = list_make1(makeBoolConst(true, false));
+ result = for_default
+ ? get_range_nulltest(key)
+ : list_make1(makeBoolConst(true, false));
return result;
}
@@ -2207,8 +2309,7 @@ get_partition_for_tuple(PartitionDispatch *pd,
Datum values[PARTITION_MAX_KEYS];
bool isnull[PARTITION_MAX_KEYS];
int cur_offset,
- cur_index;
- int i,
+ cur_index = -1,
result;
ExprContext *ecxt = GetPerTupleExprContext(estate);
TupleTableSlot *ecxt_scantuple_old = ecxt->ecxt_scantuple;
@@ -2252,24 +2353,6 @@ get_partition_for_tuple(PartitionDispatch *pd,
ecxt->ecxt_scantuple = slot;
FormPartitionKeyDatum(parent, slot, estate, values, isnull);
- if (key->strategy == PARTITION_STRATEGY_RANGE)
- {
- /*
- * Since we cannot route tuples with NULL partition keys through a
- * range-partitioned table, simply return that no partition exists
- */
- for (i = 0; i < key->partnatts; i++)
- {
- if (isnull[i])
- {
- *failed_at = parent;
- *failed_slot = slot;
- result = -1;
- goto error_exit;
- }
- }
- }
-
/*
* If this is a NULL, route it to the null-accepting partition.
* Otherwise, route by searching the array of partition bounds.
@@ -2374,12 +2457,22 @@ make_one_range_bound(PartitionKey key, int index, List *datums, bool lower)
bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
bound->index = index;
- bound->datums = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+ bound->datums = datums ? (Datum *) palloc0(key->partnatts * sizeof(Datum))
+ : NULL;
bound->content = (RangeDatumContent *) palloc0(key->partnatts *
sizeof(RangeDatumContent));
bound->lower = lower;
i = 0;
+
+ /* datums are NULL for default */
+ if (datums == NULL)
+ {
+ for (i = 0; i < key->partnatts; i++)
+ bound->content[i] = RANGE_DATUM_DEFAULT;
+ return bound;
+ }
+
foreach(lc, datums)
{
PartitionRangeDatum *datum = castNode(PartitionRangeDatum, lfirst(lc));
@@ -2443,13 +2536,19 @@ partition_rbound_cmp(PartitionKey key,
for (i = 0; i < key->partnatts; i++)
{
/*
- * First, handle cases where the column is unbounded, which should not
- * invoke the comparison procedure, and should not consider any later
- * columns.
+ * First, handle cases where the column is unbounded or default, which
+ * should not invoke the comparison procedure, and should not consider
+ * any later columns.
*/
if (content1[i] != RANGE_DATUM_FINITE ||
content2[i] != RANGE_DATUM_FINITE)
{
+
+ /* Return zero if both are default */
+ if (content1[i] == RANGE_DATUM_DEFAULT &&
+ content2[i] == RANGE_DATUM_DEFAULT)
+ return 0;
+
/*
* If the bound values are equal, fall through and compare whether
* they are upper or lower bounds.
@@ -2457,15 +2556,20 @@ partition_rbound_cmp(PartitionKey key,
if (content1[i] == content2[i])
break;
- /* Otherwise, one bound is definitely larger than the other */
- if (content1[i] == RANGE_DATUM_NEG_INF)
+ /*
+ * Otherwise, one bound is definitely larger than the other. The
+ * order is - DEFAULT, NEG_INF, FINTE, POS_INF
+ */
+ if (content1[i] == RANGE_DATUM_DEFAULT)
return -1;
else if (content1[i] == RANGE_DATUM_POS_INF)
return 1;
- else if (content2[i] == RANGE_DATUM_NEG_INF)
+ else if (content2[i] == RANGE_DATUM_DEFAULT)
return 1;
else if (content2[i] == RANGE_DATUM_POS_INF)
return -1;
+ else /* compare NEG_INF and FINITE */
+ return (content1[i] == RANGE_DATUM_NEG_INF ? -1 : 1);
}
cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
@@ -2504,6 +2608,9 @@ partition_rbound_datum_cmp(PartitionKey key,
for (i = 0; i < key->partnatts; i++)
{
+ if (rb_content[i] == RANGE_DATUM_DEFAULT)
+ continue;
+
if (rb_content[i] != RANGE_DATUM_FINITE)
return rb_content[i] == RANGE_DATUM_NEG_INF ? -1 : 1;
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 57c09c2..0b70919 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3307,11 +3307,6 @@ transformPartitionBound(ParseState *pstate, Relation parent,
if (spec->is_default)
{
- if (strategy != PARTITION_STRATEGY_LIST)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("default partition is supported only for a list partitioned table")));
-
/*
* In case of the default partition, parser had no way to identify the
* partition strategy. Assign the parent strategy to the default
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index b59ea5c..d5f66ef 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8668,7 +8668,6 @@ get_rule_expr(Node *node, deparse_context *context,
if (spec->is_default)
{
- Assert(strategy == PARTITION_STRATEGY_LIST);
appendStringInfoString(buf, "DEFAULT");
break;
}
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 9a5f6bf..6855d71 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3356,6 +3356,19 @@ CREATE TABLE part2 (
);
ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
INFO: partition constraint for table "part2" is implied by existing constraints
+-- Create default partition
+CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
+-- Only one default partition is allowed, hence, following should give error
+CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS);
+ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT;
+ERROR: partition "partr_def2" conflicts with existing default partition "partr_def1"
+-- Overlapping partitions cannot be attached, hence, following should give error
+INSERT INTO partr_def1 VALUES (2, 10);
+CREATE TABLE part3 (LIKE range_parted);
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (2, 10) TO (2, 20);
+ERROR: updated partition constraint for default partition would be violated by some row
+-- Attaching partitions should be successful when there are no overlapping rows
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (3, 10) TO (3, 20);
-- check that leaf partitions are scanned when attaching a partitioned
-- table
CREATE TABLE part_5 (
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index c8e0bf4..b7da692 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -517,9 +517,6 @@ ERROR: TO must specify exactly one value per partitioning column
-- cannot specify null values in range bounds
CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (unbounded);
ERROR: cannot specify NULL in range bound
--- range partition cannot have default partition
-CREATE TABLE fail_part PARTITION OF range_parted DEFAULT;
-ERROR: default partition is supported only for a list partitioned table
-- cannot specify finite values after UNBOUNDED has been specified
CREATE TABLE range_parted_multicol (a int, b int, c int) PARTITION BY RANGE (a, b, c);
CREATE TABLE fail_part PARTITION OF range_parted_multicol FOR VALUES FROM (1, UNBOUNDED, 1) TO (UNBOUNDED, 1, 1);
@@ -601,6 +598,16 @@ CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30);
ERROR: partition "fail_part" would overlap partition "part2"
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50);
ERROR: partition "fail_part" would overlap partition "part2"
+-- Create a default partition for range partitioned table
+CREATE TABLE range2_default PARTITION OF range_parted2 DEFAULT;
+-- More than one default partition is not allowed, so this should give error
+CREATE TABLE fail_default_part PARTITION OF range_parted2 DEFAULT;
+ERROR: partition "fail_default_part" conflicts with existing default partition "range2_default"
+-- Check if the range for default partitions overlap
+INSERT INTO range_parted2 VALUES (85);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90);
+ERROR: updated partition constraint for default partition "range2_default" would be violated by some row
+CREATE TABLE part4 PARTITION OF range_parted2 FOR VALUES FROM (90) TO (100);
-- now check for multi-column range partition key
CREATE TABLE range_parted3 (
a int,
@@ -614,6 +621,7 @@ CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10)
CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, unbounded);
CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
ERROR: partition "fail_part" would overlap partition "part12"
+CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
-- cannot create a partition that says column b is allowed to range
-- from -infinity to +infinity, while there exist partitions that have
-- more specific ranges
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 72290cc..76a246d 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -271,6 +271,18 @@ insert into range_parted values ('b', 10);
insert into range_parted values ('a');
ERROR: no partition of relation "range_parted" found for row
DETAIL: Partition key of the failing row contains (a, (b + 0)) = (a, null).
+-- Check default partition
+create table part_def partition of range_parted default;
+-- fail
+insert into part_def values ('b', 10);
+ERROR: new row for relation "part_def" violates partition constraint
+DETAIL: Failing row contains (b, 10).
+-- ok
+insert into part_def values ('c', 10);
+insert into range_parted values (null, null);
+insert into range_parted values ('a', null);
+insert into range_parted values (null, 19);
+insert into range_parted values ('b', 20);
select tableoid::regclass, * from range_parted;
tableoid | a | b
----------+---+----
@@ -280,7 +292,12 @@ select tableoid::regclass, * from range_parted;
part3 | b | 1
part4 | b | 10
part4 | b | 10
-(6 rows)
+ part_def | c | 10
+ part_def | |
+ part_def | a |
+ part_def | | 19
+ part_def | b | 20
+(11 rows)
-- ok
insert into list_parted values (null, 1);
@@ -452,6 +469,35 @@ with ins (a, b, c) as
mlparted4 | 1 | 30 | 39
(5 rows)
+-- Check multi-level default partition
+create table mlparted_def partition of mlparted default partition by range(a);
+create table mlparted_def1 partition of mlparted_def for values from (40) to (50);
+create table mlparted_def2 partition of mlparted_def for values from (50) to (60);
+insert into mlparted values (40, 100);
+insert into mlparted_def1 values (42, 100);
+insert into mlparted_def2 values (54, 50);
+-- fail
+insert into mlparted values (70, 100);
+ERROR: no partition of relation "mlparted_def" found for row
+DETAIL: Partition key of the failing row contains (a) = (70).
+insert into mlparted_def1 values (52, 50);
+ERROR: new row for relation "mlparted_def1" violates partition constraint
+DETAIL: Failing row contains (52, 50).
+insert into mlparted_def2 values (34, 50);
+ERROR: new row for relation "mlparted_def2" violates partition constraint
+DETAIL: Failing row contains (34, 50).
+-- ok
+create table mlparted_defd partition of mlparted_def default;
+insert into mlparted values (70, 100);
+select tableoid::regclass, * from mlparted_def;
+ tableoid | a | b
+---------------+----+-----
+ mlparted_def1 | 40 | 100
+ mlparted_def1 | 42 | 100
+ mlparted_def2 | 54 | 50
+ mlparted_defd | 70 | 100
+(4 rows)
+
-- check that message shown after failure to find a partition shows the
-- appropriate key description (or none) in various situations
create table key_desc (a int, b int) partition by list ((a+0));
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 6750152..e996640 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -77,6 +77,10 @@ mlparted12|f
mlparted2|f
mlparted3|f
mlparted4|f
+mlparted_def|f
+mlparted_def1|f
+mlparted_def2|f
+mlparted_defd|f
money_data|f
num_data|f
num_exp_add|t
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index 9912ef2..cfb6aa8 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -218,6 +218,15 @@ ERROR: new row for relation "part_b_10_b_20" violates partition constraint
DETAIL: Failing row contains (b, 9).
-- ok
update range_parted set b = b + 1 where b = 10;
+-- Creating default partition for range
+create table part_def partition of range_parted default;
+insert into range_parted values ('c', 9);
+-- ok
+update part_def set a = 'd' where a = 'c';
+-- fail
+update part_def set a = 'a' where a = 'd';
+ERROR: new row for relation "part_def" violates partition constraint
+DETAIL: Failing row contains (a, 9).
create table list_parted (
a text,
b int
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 935b24b..d4ca7f1 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2181,6 +2181,21 @@ CREATE TABLE part2 (
);
ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
+-- Create default partition
+CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
+
+-- Only one default partition is allowed, hence, following should give error
+CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS);
+ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT;
+
+-- Overlapping partitions cannot be attached, hence, following should give error
+INSERT INTO partr_def1 VALUES (2, 10);
+CREATE TABLE part3 (LIKE range_parted);
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (2, 10) TO (2, 20);
+
+-- Attaching partitions should be successful when there are no overlapping rows
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (3, 10) TO (3, 20);
+
-- check that leaf partitions are scanned when attaching a partitioned
-- table
CREATE TABLE part_5 (
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index f44c0e0..2011cbf 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -486,8 +486,6 @@ CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a') TO ('z',
-- cannot specify null values in range bounds
CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (unbounded);
--- range partition cannot have default partition
-CREATE TABLE fail_part PARTITION OF range_parted DEFAULT;
-- cannot specify finite values after UNBOUNDED has been specified
CREATE TABLE range_parted_multicol (a int, b int, c int) PARTITION BY RANGE (a, b, c);
@@ -559,6 +557,17 @@ CREATE TABLE part3 PARTITION OF range_parted2 FOR VALUES FROM (30) TO (40);
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30);
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50);
+-- Create a default partition for range partitioned table
+CREATE TABLE range2_default PARTITION OF range_parted2 DEFAULT;
+
+-- More than one default partition is not allowed, so this should give error
+CREATE TABLE fail_default_part PARTITION OF range_parted2 DEFAULT;
+
+-- Check if the range for default partitions overlap
+INSERT INTO range_parted2 VALUES (85);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90);
+CREATE TABLE part4 PARTITION OF range_parted2 FOR VALUES FROM (90) TO (100);
+
-- now check for multi-column range partition key
CREATE TABLE range_parted3 (
a int,
@@ -572,6 +581,7 @@ CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO
CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10);
CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, unbounded);
CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
+CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
-- cannot create a partition that says column b is allowed to range
-- from -infinity to +infinity, while there exist partitions that have
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 624f171..e2f6dff 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -168,8 +168,19 @@ insert into range_parted values ('b', 1);
insert into range_parted values ('b', 10);
-- fail (partition key (b+0) is null)
insert into range_parted values ('a');
-select tableoid::regclass, * from range_parted;
+-- Check default partition
+create table part_def partition of range_parted default;
+-- fail
+insert into part_def values ('b', 10);
+-- ok
+insert into part_def values ('c', 10);
+insert into range_parted values (null, null);
+insert into range_parted values ('a', null);
+insert into range_parted values (null, 19);
+insert into range_parted values ('b', 20);
+
+select tableoid::regclass, * from range_parted;
-- ok
insert into list_parted values (null, 1);
insert into list_parted (a) values ('aA');
@@ -288,6 +299,23 @@ with ins (a, b, c) as
(insert into mlparted (b, a) select s.a, 1 from generate_series(2, 39) s(a) returning tableoid::regclass, *)
select a, b, min(c), max(c) from ins group by a, b order by 1;
+-- Check multi-level default partition
+create table mlparted_def partition of mlparted default partition by range(a);
+create table mlparted_def1 partition of mlparted_def for values from (40) to (50);
+create table mlparted_def2 partition of mlparted_def for values from (50) to (60);
+insert into mlparted values (40, 100);
+insert into mlparted_def1 values (42, 100);
+insert into mlparted_def2 values (54, 50);
+-- fail
+insert into mlparted values (70, 100);
+insert into mlparted_def1 values (52, 50);
+insert into mlparted_def2 values (34, 50);
+-- ok
+create table mlparted_defd partition of mlparted_def default;
+insert into mlparted values (70, 100);
+
+select tableoid::regclass, * from mlparted_def;
+
-- check that message shown after failure to find a partition shows the
-- appropriate key description (or none) in various situations
create table key_desc (a int, b int) partition by list ((a+0));
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 44fb0dc..1ce7a94 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -125,6 +125,14 @@ update range_parted set b = b - 1 where b = 10;
-- ok
update range_parted set b = b + 1 where b = 10;
+-- Creating default partition for range
+create table part_def partition of range_parted default;
+insert into range_parted values ('c', 9);
+-- ok
+update part_def set a = 'd' where a = 'c';
+-- fail
+update part_def set a = 'a' where a = 'd';
+
create table list_parted (
a text,
b int
On Mon, Jul 3, 2017 at 8:00 PM, Dilip Kumar <dilipbalaut@gmail.com> wrote:
On Fri, Jun 30, 2017 at 11:56 AM, Beena Emerson <memissemerson@gmail.com> wrote:
Hello Dilip,
On Wed, Jun 21, 2017 at 6:27 PM, Dilip Kumar <dilipbalaut@gmail.com> wrote:
On Tue, Jun 20, 2017 at 6:57 PM, Dilip Kumar <dilipbalaut@gmail.com> wrote:
This is basically crashing in RelationBuildPartitionDesc, so I think
Thank you for your review and analysis.
I have updated the patch.
- bound->content is set to RANGE_DATUM_DEFAULT for each of the keys
and not just the first one.
- Improve the way of handling DEFAULT bounds in RelationBuildPartitionDesc,There is a test for multiple column range in alter_table.sql
Thanks for update patch, After this fix segmentation fault is solved.
I have some more comments.
1.
lower = make_one_range_bound(key, -1, spec->lowerdatums, true);
upper = make_one_range_bound(key, -1, spec->upperdatums, false);+ /* Default case is not handled here */ + if (spec->is_default) + break; +I think we can move default check just above the make range bound it
will avoid unnecessary processing.
Removed the check here. Default is checked beforehand.
2.
RelationBuildPartitionDesc
{
..../*
* If either of them has infinite element, we can't equate
* them. Even when both are infinite, they'd have
* opposite signs, because only one of cur and prev is a
* lower bound).
*/
if (cur->content[j] != RANGE_DATUM_FINITE ||
prev->content[j] != RANGE_DATUM_FINITE)
{
is_distinct = true;
break;
}
After default range partitioning patch the above comment doesn't sound
very accurate and
can lead to the confusion, now here we are not only considering
infinite bounds but
there is also a possibility that prev bound can be DEFAULT, so
comments should clearly
mention that.
Modified.
3.
+ bound->datums = datums ? (Datum *) palloc0(key->partnatts * sizeof(Datum)) + : NULL; bound->content = (RangeDatumContent *) palloc0(key->partnatts * sizeof(RangeDatumContent)); bound->lower = lower;i = 0; + + /* datums are NULL for default */ + if (datums == NULL) + for (i = 0; i < key->partnatts; i++) + bound->content[i] = RANGE_DATUM_DEFAULT;To me, it will look cleaner if we keep bound->content=NULL for default, instead of allocating memory and initializing each element, But it's just a suggestions and you can decide whichever way seems better to you. Then the other places e.g. + if (cur->content[i] == RANGE_DATUM_DEFAULT) + { + /*will change like
+ if (cur->content == NULL) + { + /*
I disagree. We use the content value RANGE_DATUM_DEFAULT during
comparing in partition_rbound_cmp and the code will not be very
intuiative if we use NULL instead of DEFAULT.
--
Beena Emerson
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Jul 4, 2017 at 4:21 PM, Rahila Syed <rahilasyed90@gmail.com> wrote:
Hello Beena,
Thanks for the updated patch. It passes the basic tests which I performed.
Few comments:
1. In check_new_partition_bound(),/* Default case is not handled here */
if (spec->is_default)
break;The default partition check here can be performed in common for both range
and list partition.
Removed the check here.
2. RANGE_DATUM_FINITE = 0, /* actual datum stored elsewhere */
+ RANGE_DATUM_DEFAULT, /* default */More elaborative comment?
I am not sure what to add. Do you have something in mind?
3. Inside make_one_range_bound(),
+ + /* datums are NULL for default */ + if (datums == NULL) + for (i = 0; i < key->partnatts; i++) + bound->content[i] = RANGE_DATUM_DEFAULT; +IMO, returning bound at this stage will make code clearer as further
processing
does not happen for default partition.
Done.
4. @@ -549,6 +568,7 @@ RelationBuildPartitionDesc(Relation rel) sizeof(RangeDatumContent *)); boundinfo->indexes = (int *) palloc((ndatums + 1) * sizeof(int)); + boundinfo->default_index = -1; This should also be done commonly for both default list and range partition.
Removed the line here. Common allocation is done by Jeevan's patch.
5.
if (!spec->is_default && partdesc->nparts > 0)
{
ListCell *cell;Assert(boundinfo &&
boundinfo->strategy == PARTITION_STRATEGY_LIST &&
(boundinfo->ndatums > 0 ||
partition_bound_accepts_nulls(boundinfo) ||
partition_bound_has_default(boundinfo)));
The assertion condition partition_bound_has_default(boundinfo) is rendered
useless
because of if condition change and can be removed perhaps.
This cannot be removed.
This check is required when this code is run for a non-default
partition and default is the only existing partition.
6. I think its better to have following regression tests coverage
--Testing with inserting one NULL and other non NULL values for multicolumn
range partitioned table.postgres=# CREATE TABLE range_parted (
postgres(# a int,
postgres(# b int
postgres(# ) PARTITION BY RANGE (a, b);
CREATE TABLE
postgres=#
postgres=# CREATE TABLE part1 (
postgres(# a int NOT NULL CHECK (a = 1),
postgres(# b int NOT NULL CHECK (b >= 1 AND b <= 10)
postgres(# );
CREATE TABLEpostgres=# ALTER TABLE range_parted ATTACH PARTITION part1 FOR VALUES FROM
(1, 1) TO (1, 10);
ALTER TABLE
postgres=# CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
CREATE TABLEpostgres=# insert into range_parted values (1,NULL);
INSERT 0 1
postgres=# insert into range_parted values (NULL,9);
INSERT 0 1
added.
--
Beena Emerson
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hi Beena,
I have posted the rebased patches[1]/messages/by-id/CAOgcT0OVwDu+beChWb5R5s6rfKLCiWcZT5617hqu7T3GdA1hAw@mail.gmail.com for default list partition.
Your patch also needs a rebase.
[1]: /messages/by-id/CAOgcT0OVwDu+beChWb5R5s6rfKLCiWcZT5617hqu7T3GdA1hAw@mail.gmail.com
/messages/by-id/CAOgcT0OVwDu+beChWb5R5s6rfKLCiWcZT5617hqu7T3GdA1hAw@mail.gmail.com
Regards,
Jeevan Ladhe
On Fri, Jul 14, 2017 at 3:28 PM, Beena Emerson <memissemerson@gmail.com>
wrote:
Show quoted text
On Tue, Jul 4, 2017 at 4:21 PM, Rahila Syed <rahilasyed90@gmail.com>
wrote:Hello Beena,
Thanks for the updated patch. It passes the basic tests which I
performed.
Few comments:
1. In check_new_partition_bound(),/* Default case is not handled here */
if (spec->is_default)
break;The default partition check here can be performed in common for both
range
and list partition.
Removed the check here.
2. RANGE_DATUM_FINITE = 0, /* actual datum stored elsewhere */
+ RANGE_DATUM_DEFAULT, /* default */More elaborative comment?
I am not sure what to add. Do you have something in mind?
3. Inside make_one_range_bound(),
+ + /* datums are NULL for default */ + if (datums == NULL) + for (i = 0; i < key->partnatts; i++) + bound->content[i] = RANGE_DATUM_DEFAULT; +IMO, returning bound at this stage will make code clearer as further
processing
does not happen for default partition.Done.
4. @@ -549,6 +568,7 @@ RelationBuildPartitionDesc(Relation rel) sizeof(RangeDatumContent *)); boundinfo->indexes = (int *) palloc((ndatums + 1) * sizeof(int)); + boundinfo->default_index = -1; This should also be done commonly for both default list and rangepartition.
Removed the line here. Common allocation is done by Jeevan's patch.
5.
if (!spec->is_default && partdesc->nparts > 0)
{
ListCell *cell;Assert(boundinfo &&
boundinfo->strategy ==PARTITION_STRATEGY_LIST &&
(boundinfo->ndatums > 0 ||
partition_bound_accepts_nulls(boundinfo) ||
partition_bound_has_default(boundinfo)));
The assertion condition partition_bound_has_default(boundinfo) isrendered
useless
because of if condition change and can be removed perhaps.This cannot be removed.
This check is required when this code is run for a non-default
partition and default is the only existing partition.6. I think its better to have following regression tests coverage
--Testing with inserting one NULL and other non NULL values for
multicolumn
range partitioned table.
postgres=# CREATE TABLE range_parted (
postgres(# a int,
postgres(# b int
postgres(# ) PARTITION BY RANGE (a, b);
CREATE TABLE
postgres=#
postgres=# CREATE TABLE part1 (
postgres(# a int NOT NULL CHECK (a = 1),
postgres(# b int NOT NULL CHECK (b >= 1 AND b <= 10)
postgres(# );
CREATE TABLEpostgres=# ALTER TABLE range_parted ATTACH PARTITION part1 FOR VALUES
FROM
(1, 1) TO (1, 10);
ALTER TABLE
postgres=# CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
CREATE TABLEpostgres=# insert into range_parted values (1,NULL);
INSERT 0 1
postgres=# insert into range_parted values (NULL,9);
INSERT 0 1added.
--
Beena Emerson
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On Wed, Jul 26, 2017 at 7:05 PM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:
Hi Beena,
I have posted the rebased patches[1] for default list partition.
Your patch also needs a rebase.[1]
/messages/by-id/CAOgcT0OVwDu+beChWb5R5s6rfKLCiWcZT5617hqu7T3GdA1hAw@mail.gmail.com
Thanks for informing.
PFA the updated patch.
I have changed the numbering of enum PartitionRangeDatumKind since I
have to include DEFAULT as well. If you have better ideas, let me
know.
--
Beena Emerson
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Attachments:
default_range_partition_v8.patchapplication/octet-stream; name=default_range_partition_v8.patchDownload
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 45de96b..0d38107 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -127,7 +127,9 @@ static void get_range_key_properties(PartitionKey key, int keynum,
Expr **keyCol,
Const **lower_val, Const **upper_val);
static List *get_qual_for_list(Relation parent, PartitionBoundSpec *spec);
-static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
+ bool for_default);
+static List *get_range_nulltest(PartitionKey key);
static List *generate_partition_qual(Relation rel);
static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index,
@@ -168,11 +170,11 @@ RelationBuildPartitionDesc(Relation rel)
MemoryContext oldcxt;
int ndatums = 0;
+ int default_index = -1;
/* List partitioning specific */
PartitionListValue **all_values = NULL;
int null_index = -1;
- int default_index = -1;
/* Range partitioning specific */
PartitionRangeBound **rbounds = NULL;
@@ -347,6 +349,9 @@ RelationBuildPartitionDesc(Relation rel)
if (spec->strategy != PARTITION_STRATEGY_RANGE)
elog(ERROR, "invalid strategy in partition bound spec");
+ if (spec->is_default)
+ default_index = i;
+
lower = make_one_range_bound(key, i, spec->lowerdatums,
true);
upper = make_one_range_bound(key, i, spec->upperdatums,
@@ -387,6 +392,19 @@ RelationBuildPartitionDesc(Relation rel)
break;
}
+ if (cur->kind[j] == PARTITION_RANGE_DATUM_DEFAULT)
+ {
+ /*
+ * Both the upper and lower bound of default partition
+ * are same, and the upper bound is used to set the
+ * mapping later, hence only the upper bound is
+ * considered distinct.
+ */
+ if (!cur->lower)
+ is_distinct = true;
+ break;
+ }
+
/*
* If the bounds are both MINVALUE or MAXVALUE, stop now
* and treat them as equal, since any values after this
@@ -582,6 +600,8 @@ RelationBuildPartitionDesc(Relation rel)
}
}
boundinfo->indexes[i] = -1;
+ if (default_index != -1)
+ boundinfo->default_index = mapping[default_index];
break;
}
@@ -767,6 +787,7 @@ check_new_partition_bound(char *relname, Relation parent,
*upper;
Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
+
lower = make_one_range_bound(key, -1, spec->lowerdatums, true);
upper = make_one_range_bound(key, -1, spec->upperdatums, false);
@@ -880,10 +901,10 @@ check_default_allows_bound(Relation parent, Relation default_rel,
List *all_parts;
ListCell *lc;
- /* Currently default partition is supported only for LIST partition. */
- Assert(new_spec->strategy == PARTITION_STRATEGY_LIST);
- new_part_constraints = get_qual_for_list(parent, new_spec);
+ new_part_constraints = (new_spec->strategy == PARTITION_STRATEGY_LIST)
+ ? get_qual_for_list(parent, new_spec)
+ : get_qual_for_range(parent, new_spec, false);
def_part_constraints =
get_default_part_validation_constraint(new_part_constraints);
@@ -1083,7 +1104,7 @@ get_qual_from_partbound(Relation rel, Relation parent,
case PARTITION_STRATEGY_RANGE:
Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
- my_qual = get_qual_for_range(key, spec);
+ my_qual = get_qual_for_range(parent, spec, false);
break;
default:
@@ -1706,6 +1727,53 @@ get_range_key_properties(PartitionKey key, int keynum,
*upper_val = NULL;
}
+ /*
+ * get_range_nulltest
+ *
+ * A non-default range partition table does not currently allow partition
+ * keys to be null, so emit an IS NOT NULL expression for each key column.
+ */
+static List *
+get_range_nulltest(PartitionKey key)
+{
+ List *result = NIL;
+ NullTest *nulltest;
+ ListCell *partexprs_item;
+ int i;
+
+ partexprs_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ Expr *keyCol;
+
+ if (key->partattrs[i] != 0)
+ {
+ keyCol = (Expr *) makeVar(1,
+ key->partattrs[i],
+ key->parttypid[i],
+ key->parttypmod[i],
+ key->parttypcoll[i],
+ 0);
+ }
+ else
+ {
+ if (partexprs_item == NULL)
+ elog(ERROR, "wrong number of partition key expressions");
+ keyCol = copyObject(lfirst(partexprs_item));
+ partexprs_item = lnext(partexprs_item);
+ }
+
+ nulltest = makeNode(NullTest);
+ nulltest->arg = keyCol;
+ nulltest->nulltesttype = IS_NOT_NULL;
+ nulltest->argisrow = false;
+ nulltest->location = -1;
+ result = lappend(result, nulltest);
+ }
+
+ return result;
+}
+
/*
* get_qual_for_range
*
@@ -1744,11 +1812,15 @@ get_range_key_properties(PartitionKey key, int keynum,
* In most common cases with only one partition column, say a, the following
* expression tree will be generated: a IS NOT NULL AND a >= al AND a < au
*
+ * For default partition, it returns the negation of the constraints of all
+ * the other partitions.
+ *
* If we end up with an empty result list, we return a single-member list
* containing a constant TRUE, because callers expect a non-empty list.
*/
static List *
-get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
+get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
+ bool for_default)
{
List *result = NIL;
ListCell *cell1,
@@ -1759,10 +1831,10 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
j;
PartitionRangeDatum *ldatum,
*udatum;
+ PartitionKey key = RelationGetPartitionKey(parent);
Expr *keyCol;
Const *lower_val,
*upper_val;
- NullTest *nulltest;
List *lower_or_arms,
*upper_or_arms;
int num_or_arms,
@@ -1772,44 +1844,70 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
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;
-
- /*
- * A range-partitioned table does not currently allow partition keys to be
- * null, so emit an IS NOT NULL expression for each key column.
- */
- partexprs_item = list_head(key->partexprs);
- for (i = 0; i < key->partnatts; i++)
+ if (spec->is_default)
{
- Expr *keyCol;
+ List *or_expr_args = NIL;
+ PartitionDesc pdesc = RelationGetPartitionDesc(parent);
+ Oid *inhoids = pdesc->oids;
+ int nparts = pdesc->nparts,
+ i;
- if (key->partattrs[i] != 0)
+ for (i = 0; i < nparts; i++)
{
- keyCol = (Expr *) makeVar(1,
- key->partattrs[i],
- key->parttypid[i],
- key->parttypmod[i],
- key->parttypcoll[i],
- 0);
+ Oid inhrelid = inhoids[i];
+ HeapTuple tuple;
+ Datum datum;
+ bool isnull;
+ PartitionBoundSpec *bspec;
+
+ tuple = SearchSysCache1(RELOID, inhrelid);
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", inhrelid);
+
+ datum = SysCacheGetAttr(RELOID, tuple,
+ Anum_pg_class_relpartbound,
+ &isnull);
+
+ Assert(!isnull);
+ bspec = (PartitionBoundSpec *) stringToNode(TextDatumGetCString(datum));
+
+ if (!bspec->is_default)
+ {
+ List *part_qual = get_qual_for_range(parent, bspec, true);
+
+ /*
+ * AND the constraints of the partition and add to
+ * or_expr_args
+ */
+ or_expr_args = lappend(or_expr_args, list_length(part_qual) > 1
+ ? makeBoolExpr(AND_EXPR, part_qual, -1)
+ : linitial(part_qual));
+ }
+ ReleaseSysCache(tuple);
}
- else
+
+ if (or_expr_args != NIL)
{
- if (partexprs_item == NULL)
- elog(ERROR, "wrong number of partition key expressions");
- keyCol = copyObject(lfirst(partexprs_item));
- partexprs_item = lnext(partexprs_item);
+ /* OR all the non-default partition constraints; then negate it */
+ result = lappend(result,
+ list_length(or_expr_args) > 1
+ ? makeBoolExpr(OR_EXPR, or_expr_args, -1)
+ : linitial(or_expr_args));
+ result = list_make1(makeBoolExpr(NOT_EXPR, result, -1));
}
+ else /* default is the only partition */
+ result = list_make1(makeBoolConst(true, false));
- nulltest = makeNode(NullTest);
- nulltest->arg = keyCol;
- nulltest->nulltesttype = IS_NOT_NULL;
- nulltest->argisrow = false;
- nulltest->location = -1;
- result = lappend(result, nulltest);
+ return result;
}
+ lower_or_start_datum = list_head(spec->lowerdatums);
+ upper_or_start_datum = list_head(spec->upperdatums);
+ num_or_arms = key->partnatts;
+
+ if (!for_default)
+ result = get_range_nulltest(key);
+
/*
* Iterate over the key columns and check if the corresponding lower and
* upper datums are equal using the btree equality operator for the
@@ -2033,7 +2131,9 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
/* As noted above, caller expects the list to be non-empty. */
if (result == NIL)
- result = list_make1(makeBoolConst(true, false));
+ result = for_default
+ ? get_range_nulltest(key)
+ : list_make1(makeBoolConst(true, false));
return result;
}
@@ -2201,8 +2301,7 @@ get_partition_for_tuple(PartitionDispatch *pd,
Datum values[PARTITION_MAX_KEYS];
bool isnull[PARTITION_MAX_KEYS];
int cur_offset,
- cur_index;
- int i,
+ cur_index = -1,
result;
ExprContext *ecxt = GetPerTupleExprContext(estate);
TupleTableSlot *ecxt_scantuple_old = ecxt->ecxt_scantuple;
@@ -2246,24 +2345,6 @@ get_partition_for_tuple(PartitionDispatch *pd,
ecxt->ecxt_scantuple = slot;
FormPartitionKeyDatum(parent, slot, estate, values, isnull);
- if (key->strategy == PARTITION_STRATEGY_RANGE)
- {
- /*
- * Since we cannot route tuples with NULL partition keys through a
- * range-partitioned table, simply return that no partition exists
- */
- for (i = 0; i < key->partnatts; i++)
- {
- if (isnull[i])
- {
- *failed_at = parent;
- *failed_slot = slot;
- result = -1;
- goto error_exit;
- }
- }
- }
-
/*
* If this is a NULL, route it to the null-accepting partition.
* Otherwise, route by searching the array of partition bounds.
@@ -2368,12 +2449,22 @@ make_one_range_bound(PartitionKey key, int index, List *datums, bool lower)
bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
bound->index = index;
- bound->datums = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+ bound->datums = datums ? (Datum *) palloc0(key->partnatts * sizeof(Datum))
+ : NULL;
bound->kind = (PartitionRangeDatumKind *) palloc0(key->partnatts *
sizeof(PartitionRangeDatumKind));
bound->lower = lower;
i = 0;
+
+ /* datums are NULL for default */
+ if (datums == NULL)
+ {
+ for (i = 0; i < key->partnatts; i++)
+ bound->kind[i] = PARTITION_RANGE_DATUM_DEFAULT;
+ return bound;
+ }
+
foreach(lc, datums)
{
PartitionRangeDatum *datum = castNode(PartitionRangeDatum, lfirst(lc));
@@ -2434,10 +2525,10 @@ partition_rbound_cmp(PartitionKey key,
for (i = 0; i < key->partnatts; i++)
{
/*
- * First, handle cases where the column is unbounded, which should not
- * invoke the comparison procedure, and should not consider any later
- * columns. Note that the PartitionRangeDatumKind enum elements
- * compare the same way as the values they represent.
+ * First, handle cases where the column is unbounded or default, which
+ * should not invoke the comparison procedure, and should not consider
+ * any later columns. Note that the PartitionRangeDatumKind enum
+ * elements compare the same way as the values they represent.
*/
if (kind1[i] < kind2[i])
return -1;
@@ -2488,7 +2579,7 @@ partition_rbound_datum_cmp(PartitionKey key,
for (i = 0; i < key->partnatts; i++)
{
- if (rb_kind[i] == PARTITION_RANGE_DATUM_MINVALUE)
+ if (rb_kind[i] == PARTITION_RANGE_DATUM_DEFAULT || rb_kind[i] == PARTITION_RANGE_DATUM_MINVALUE)
return -1;
else if (rb_kind[i] == PARTITION_RANGE_DATUM_MAXVALUE)
return 1;
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 6830b64..6157613 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3307,11 +3307,6 @@ transformPartitionBound(ParseState *pstate, Relation parent,
if (spec->is_default)
{
- if (strategy != PARTITION_STRATEGY_LIST)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("default partition is supported only for a list partitioned table")));
-
/*
* In case of the default partition, parser had no way to identify the
* partition strategy. Assign the parent strategy to the default
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index e066159..67a5c62 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8700,7 +8700,6 @@ get_rule_expr(Node *node, deparse_context *context,
if (spec->is_default)
{
- Assert(strategy == PARTITION_STRATEGY_LIST);
appendStringInfoString(buf, "DEFAULT");
break;
}
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 6e05b79..20d1fc8 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -816,9 +816,10 @@ typedef struct PartitionBoundSpec
*/
typedef enum PartitionRangeDatumKind
{
- PARTITION_RANGE_DATUM_MINVALUE = -1, /* less than any other value */
- PARTITION_RANGE_DATUM_VALUE = 0, /* a specific (bounded) value */
- PARTITION_RANGE_DATUM_MAXVALUE = 1 /* greater than any other value */
+ PARTITION_RANGE_DATUM_DEFAULT = 0, /* default partition */
+ PARTITION_RANGE_DATUM_MINVALUE, /* less than any other value */
+ PARTITION_RANGE_DATUM_VALUE, /* a specific (bounded) value */
+ PARTITION_RANGE_DATUM_MAXVALUE /* greater than any other value */
} PartitionRangeDatumKind;
typedef struct PartitionRangeDatum
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 9a5f6bf..6855d71 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3356,6 +3356,19 @@ CREATE TABLE part2 (
);
ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
INFO: partition constraint for table "part2" is implied by existing constraints
+-- Create default partition
+CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
+-- Only one default partition is allowed, hence, following should give error
+CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS);
+ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT;
+ERROR: partition "partr_def2" conflicts with existing default partition "partr_def1"
+-- Overlapping partitions cannot be attached, hence, following should give error
+INSERT INTO partr_def1 VALUES (2, 10);
+CREATE TABLE part3 (LIKE range_parted);
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (2, 10) TO (2, 20);
+ERROR: updated partition constraint for default partition would be violated by some row
+-- Attaching partitions should be successful when there are no overlapping rows
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (3, 10) TO (3, 20);
-- check that leaf partitions are scanned when attaching a partitioned
-- table
CREATE TABLE part_5 (
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 9615c07..55914e4 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -517,9 +517,6 @@ ERROR: TO must specify exactly one value per partitioning column
-- cannot specify null values in range bounds
CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (maxvalue);
ERROR: cannot specify NULL in range bound
--- range partition cannot have default partition
-CREATE TABLE fail_part PARTITION OF range_parted DEFAULT;
-ERROR: default partition is supported only for a list partitioned table
-- check if compatible with the specified parent
-- cannot create as partition of a non-partitioned table
CREATE TABLE unparted (
@@ -594,6 +591,16 @@ CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30);
ERROR: partition "fail_part" would overlap partition "part2"
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50);
ERROR: partition "fail_part" would overlap partition "part2"
+-- Create a default partition for range partitioned table
+CREATE TABLE range2_default PARTITION OF range_parted2 DEFAULT;
+-- More than one default partition is not allowed, so this should give error
+CREATE TABLE fail_default_part PARTITION OF range_parted2 DEFAULT;
+ERROR: partition "fail_default_part" conflicts with existing default partition "range2_default"
+-- Check if the range for default partitions overlap
+INSERT INTO range_parted2 VALUES (85);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90);
+ERROR: updated partition constraint for default partition "range2_default" would be violated by some row
+CREATE TABLE part4 PARTITION OF range_parted2 FOR VALUES FROM (90) TO (100);
-- now check for multi-column range partition key
CREATE TABLE range_parted3 (
a int,
@@ -607,6 +614,7 @@ CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10)
CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, maxvalue);
CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
ERROR: partition "fail_part" would overlap partition "part12"
+CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
-- cannot create a partition that says column b is allowed to range
-- from -infinity to +infinity, while there exist partitions that have
-- more specific ranges
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 3319f6d..cc28bdc 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -271,6 +271,18 @@ insert into range_parted values ('b', 10);
insert into range_parted values ('a');
ERROR: no partition of relation "range_parted" found for row
DETAIL: Partition key of the failing row contains (a, (b + 0)) = (a, null).
+-- Check default partition
+create table part_def partition of range_parted default;
+-- fail
+insert into part_def values ('b', 10);
+ERROR: new row for relation "part_def" violates partition constraint
+DETAIL: Failing row contains (b, 10).
+-- ok
+insert into part_def values ('c', 10);
+insert into range_parted values (null, null);
+insert into range_parted values ('a', null);
+insert into range_parted values (null, 19);
+insert into range_parted values ('b', 20);
select tableoid::regclass, * from range_parted;
tableoid | a | b
----------+---+----
@@ -280,7 +292,12 @@ select tableoid::regclass, * from range_parted;
part3 | b | 1
part4 | b | 10
part4 | b | 10
-(6 rows)
+ part_def | c | 10
+ part_def | |
+ part_def | a |
+ part_def | | 19
+ part_def | b | 20
+(11 rows)
-- ok
insert into list_parted values (null, 1);
@@ -467,6 +484,36 @@ insert into mlparted5 (a, b, c) values (1, 40, 'a');
ERROR: new row for relation "mlparted5a" violates partition constraint
DETAIL: Failing row contains (b, 1, 40).
drop table mlparted5;
+alter table mlparted drop constraint check_b;
+-- Check multi-level default partition
+create table mlparted_def partition of mlparted default partition by range(a);
+create table mlparted_def1 partition of mlparted_def for values from (40) to (50);
+create table mlparted_def2 partition of mlparted_def for values from (50) to (60);
+insert into mlparted values (40, 100);
+insert into mlparted_def1 values (42, 100);
+insert into mlparted_def2 values (54, 50);
+-- fail
+insert into mlparted values (70, 100);
+ERROR: no partition of relation "mlparted_def" found for row
+DETAIL: Partition key of the failing row contains (a) = (70).
+insert into mlparted_def1 values (52, 50);
+ERROR: new row for relation "mlparted_def1" violates partition constraint
+DETAIL: Failing row contains (52, 50, null).
+insert into mlparted_def2 values (34, 50);
+ERROR: new row for relation "mlparted_def2" violates partition constraint
+DETAIL: Failing row contains (34, 50, null).
+-- ok
+create table mlparted_defd partition of mlparted_def default;
+insert into mlparted values (70, 100);
+select tableoid::regclass, * from mlparted_def;
+ tableoid | a | b | c
+---------------+----+-----+---
+ mlparted_def1 | 40 | 100 |
+ mlparted_def1 | 42 | 100 |
+ mlparted_def2 | 54 | 50 |
+ mlparted_defd | 70 | 100 |
+(4 rows)
+
-- check that message shown after failure to find a partition shows the
-- appropriate key description (or none) in various situations
create table key_desc (a int, b int) partition by list ((a+0));
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 6750152..e996640 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -77,6 +77,10 @@ mlparted12|f
mlparted2|f
mlparted3|f
mlparted4|f
+mlparted_def|f
+mlparted_def1|f
+mlparted_def2|f
+mlparted_defd|f
money_data|f
num_data|f
num_exp_add|t
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index 9912ef2..cfb6aa8 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -218,6 +218,15 @@ ERROR: new row for relation "part_b_10_b_20" violates partition constraint
DETAIL: Failing row contains (b, 9).
-- ok
update range_parted set b = b + 1 where b = 10;
+-- Creating default partition for range
+create table part_def partition of range_parted default;
+insert into range_parted values ('c', 9);
+-- ok
+update part_def set a = 'd' where a = 'c';
+-- fail
+update part_def set a = 'a' where a = 'd';
+ERROR: new row for relation "part_def" violates partition constraint
+DETAIL: Failing row contains (a, 9).
create table list_parted (
a text,
b int
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 935b24b..d4ca7f1 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2181,6 +2181,21 @@ CREATE TABLE part2 (
);
ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
+-- Create default partition
+CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
+
+-- Only one default partition is allowed, hence, following should give error
+CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS);
+ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT;
+
+-- Overlapping partitions cannot be attached, hence, following should give error
+INSERT INTO partr_def1 VALUES (2, 10);
+CREATE TABLE part3 (LIKE range_parted);
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (2, 10) TO (2, 20);
+
+-- Attaching partitions should be successful when there are no overlapping rows
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (3, 10) TO (3, 20);
+
-- check that leaf partitions are scanned when attaching a partitioned
-- table
CREATE TABLE part_5 (
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 1eefa0e..e4fde85 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -486,8 +486,6 @@ CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a') TO ('z',
-- cannot specify null values in range bounds
CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (maxvalue);
--- range partition cannot have default partition
-CREATE TABLE fail_part PARTITION OF range_parted DEFAULT;
-- check if compatible with the specified parent
@@ -554,6 +552,17 @@ CREATE TABLE part3 PARTITION OF range_parted2 FOR VALUES FROM (30) TO (40);
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30);
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50);
+-- Create a default partition for range partitioned table
+CREATE TABLE range2_default PARTITION OF range_parted2 DEFAULT;
+
+-- More than one default partition is not allowed, so this should give error
+CREATE TABLE fail_default_part PARTITION OF range_parted2 DEFAULT;
+
+-- Check if the range for default partitions overlap
+INSERT INTO range_parted2 VALUES (85);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90);
+CREATE TABLE part4 PARTITION OF range_parted2 FOR VALUES FROM (90) TO (100);
+
-- now check for multi-column range partition key
CREATE TABLE range_parted3 (
a int,
@@ -567,6 +576,7 @@ CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES FROM (1, minvalue) TO
CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10);
CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, maxvalue);
CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
+CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
-- cannot create a partition that says column b is allowed to range
-- from -infinity to +infinity, while there exist partitions that have
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 7bf1778..6f5689f 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -168,8 +168,19 @@ insert into range_parted values ('b', 1);
insert into range_parted values ('b', 10);
-- fail (partition key (b+0) is null)
insert into range_parted values ('a');
-select tableoid::regclass, * from range_parted;
+-- Check default partition
+create table part_def partition of range_parted default;
+-- fail
+insert into part_def values ('b', 10);
+-- ok
+insert into part_def values ('c', 10);
+insert into range_parted values (null, null);
+insert into range_parted values ('a', null);
+insert into range_parted values (null, 19);
+insert into range_parted values ('b', 20);
+
+select tableoid::regclass, * from range_parted;
-- ok
insert into list_parted values (null, 1);
insert into list_parted (a) values ('aA');
@@ -299,6 +310,24 @@ create function mlparted5abrtrig_func() returns trigger as $$ begin new.c = 'b';
create trigger mlparted5abrtrig before insert on mlparted5a for each row execute procedure mlparted5abrtrig_func();
insert into mlparted5 (a, b, c) values (1, 40, 'a');
drop table mlparted5;
+alter table mlparted drop constraint check_b;
+
+-- Check multi-level default partition
+create table mlparted_def partition of mlparted default partition by range(a);
+create table mlparted_def1 partition of mlparted_def for values from (40) to (50);
+create table mlparted_def2 partition of mlparted_def for values from (50) to (60);
+insert into mlparted values (40, 100);
+insert into mlparted_def1 values (42, 100);
+insert into mlparted_def2 values (54, 50);
+-- fail
+insert into mlparted values (70, 100);
+insert into mlparted_def1 values (52, 50);
+insert into mlparted_def2 values (34, 50);
+-- ok
+create table mlparted_defd partition of mlparted_def default;
+insert into mlparted values (70, 100);
+
+select tableoid::regclass, * from mlparted_def;
-- check that message shown after failure to find a partition shows the
-- appropriate key description (or none) in various situations
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 44fb0dc..1ce7a94 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -125,6 +125,14 @@ update range_parted set b = b - 1 where b = 10;
-- ok
update range_parted set b = b + 1 where b = 10;
+-- Creating default partition for range
+create table part_def partition of range_parted default;
+insert into range_parted values ('c', 9);
+-- ok
+update part_def set a = 'd' where a = 'c';
+-- fail
+update part_def set a = 'a' where a = 'd';
+
create table list_parted (
a text,
b int
On Mon, Jul 31, 2017 at 8:28 AM, Beena Emerson <memissemerson@gmail.com> wrote:
Thanks for informing.
PFA the updated patch.
I have changed the numbering of enum PartitionRangeDatumKind since I
have to include DEFAULT as well. If you have better ideas, let me
know.
Why do we need to introduce PARTITION_RANGE_DATUM_DEFAULT at all? It
seems to me that the handling of default range partitions ought to be
similar to the way a null-accepting list partition is handled -
namely, it wouldn't show up in the "datums" or "kind" array at all,
instead just showing up in PartitionBoundInfoData's default_index
field.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Aug 4, 2017 at 7:48 PM, Robert Haas <robertmhaas@gmail.com> wrote:
On Mon, Jul 31, 2017 at 8:28 AM, Beena Emerson <memissemerson@gmail.com> wrote:
Why do we need to introduce PARTITION_RANGE_DATUM_DEFAULT at all? It
seems to me that the handling of default range partitions ought to be
similar to the way a null-accepting list partition is handled -
namely, it wouldn't show up in the "datums" or "kind" array at all,
instead just showing up in PartitionBoundInfoData's default_index
field.
I have updated the patch to make it similar to the way default/null is
handled in list partition, removing the PARTITION_RANGE_DATUM_DEFAULT.
This is to be applied over v24 patches shared by Jeevan [1]/messages/by-id/CAOgcT0OVwDu+beChWb5R5s6rfKLCiWcZT5617hqu7T3GdA1hAw@mail.gmail.com which
applies on commit id 5ff3d73813ebcc3ff80be77c30b458d728951036.
The RelationBuildPartitionDesc has been modified a lot, especially the
way all_bounds, ndatums and rbounds are set.
[1]: /messages/by-id/CAOgcT0OVwDu+beChWb5R5s6rfKLCiWcZT5617hqu7T3GdA1hAw@mail.gmail.com
--
Beena Emerson
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Attachments:
default_range_partition_v9.patchapplication/octet-stream; name=default_range_partition_v9.patchDownload
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index e0b9dec..9b1d8e7 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -127,7 +127,9 @@ static void get_range_key_properties(PartitionKey key, int keynum,
Expr **keyCol,
Const **lower_val, Const **upper_val);
static List *get_qual_for_list(Relation parent, PartitionBoundSpec *spec);
-static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
+ bool for_default);
+static List *get_range_nulltest(PartitionKey key);
static List *generate_partition_qual(Relation rel);
static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index,
@@ -168,11 +170,11 @@ RelationBuildPartitionDesc(Relation rel)
MemoryContext oldcxt;
int ndatums = 0;
+ int default_index = -1;
/* List partitioning specific */
PartitionListValue **all_values = NULL;
int null_index = -1;
- int default_index = -1;
/* Range partitioning specific */
PartitionRangeBound **rbounds = NULL;
@@ -322,21 +324,18 @@ RelationBuildPartitionDesc(Relation rel)
}
else if (key->strategy == PARTITION_STRATEGY_RANGE)
{
- int j,
- k;
+ int k;
PartitionRangeBound **all_bounds,
*prev;
- bool *distinct_indexes;
all_bounds = (PartitionRangeBound **) palloc0(2 * nparts *
sizeof(PartitionRangeBound *));
- distinct_indexes = (bool *) palloc(2 * nparts * sizeof(bool));
/*
* Create a unified list of range bounds across all the
* partitions.
*/
- i = j = 0;
+ i = ndatums = 0;
foreach(cell, boundspecs)
{
PartitionBoundSpec *spec = castNode(PartitionBoundSpec,
@@ -347,30 +346,41 @@ RelationBuildPartitionDesc(Relation rel)
if (spec->strategy != PARTITION_STRATEGY_RANGE)
elog(ERROR, "invalid strategy in partition bound spec");
+ /*
+ * Note the index of the partition bound spec for the default
+ * partition. There's no datum to add to the allbounds array
+ * for this partition.
+ */
+ if (spec->is_default)
+ {
+ default_index = i++;
+ continue;
+ }
+
lower = make_one_range_bound(key, i, spec->lowerdatums,
true);
upper = make_one_range_bound(key, i, spec->upperdatums,
false);
- all_bounds[j] = lower;
- all_bounds[j + 1] = upper;
- j += 2;
+ all_bounds[ndatums++] = lower;
+ all_bounds[ndatums++] = upper;
i++;
}
- Assert(j == 2 * nparts);
+
+ Assert(ndatums == nparts * 2 ||
+ (default_index != 1 && ndatums == (nparts - 1) * 2));
/* Sort all the bounds in ascending order */
- qsort_arg(all_bounds, 2 * nparts,
+ qsort_arg(all_bounds, ndatums,
sizeof(PartitionRangeBound *),
qsort_partition_rbound_cmp,
(void *) key);
- /*
- * Count the number of distinct bounds to allocate an array of
- * that size.
- */
- ndatums = 0;
+ /* Pick out distinct datums and store it in a new array. */
+ rbounds = (PartitionRangeBound **) palloc(ndatums *
+ sizeof(PartitionRangeBound *));
+ k = 0;
prev = NULL;
- for (i = 0; i < 2 * nparts; i++)
+ for (i = 0; i < ndatums; i++)
{
PartitionRangeBound *cur = all_bounds[i];
bool is_distinct = false;
@@ -407,34 +417,17 @@ RelationBuildPartitionDesc(Relation rel)
}
/*
- * Count the current bound if it is distinct from the previous
- * one. Also, store if the index i contains a distinct bound
- * that we'd like put in the relcache array.
+ * If distinct, save into array from where they will be copied
+ * into the relcache.
*/
if (is_distinct)
- {
- distinct_indexes[i] = true;
- ndatums++;
- }
- else
- distinct_indexes[i] = false;
+ rbounds[k++] = all_bounds[i];
prev = cur;
}
- /*
- * Finally save them in an array from where they will be copied
- * into the relcache.
- */
- rbounds = (PartitionRangeBound **) palloc(ndatums *
- sizeof(PartitionRangeBound *));
- k = 0;
- for (i = 0; i < 2 * nparts; i++)
- {
- if (distinct_indexes[i])
- rbounds[k++] = all_bounds[i];
- }
- Assert(k == ndatums);
+ /* Update ndatums to hold the count of distinct datums. */
+ ndatums = k;
}
else
elog(ERROR, "unexpected partition strategy: %d",
@@ -581,6 +574,14 @@ RelationBuildPartitionDesc(Relation rel)
boundinfo->indexes[i] = mapping[orig_index];
}
}
+
+ /* Assign mapped index for the default partition. */
+ if (default_index != -1)
+ {
+ Assert(default_index >= 0 && mapping[default_index] == -1);
+ mapping[default_index] = next_index++;
+ boundinfo->default_index = mapping[default_index];
+ }
boundinfo->indexes[i] = -1;
break;
}
@@ -787,8 +788,10 @@ check_new_partition_bound(char *relname, Relation parent,
int offset;
bool equal;
- Assert(boundinfo && boundinfo->ndatums > 0 &&
- boundinfo->strategy == PARTITION_STRATEGY_RANGE);
+ Assert(boundinfo &&
+ boundinfo->strategy == PARTITION_STRATEGY_RANGE &&
+ (boundinfo->ndatums > 0 ||
+ partition_bound_has_default(boundinfo)));
/*
* Test whether the new lower bound (which is treated
@@ -880,10 +883,10 @@ check_default_allows_bound(Relation parent, Relation default_rel,
List *all_parts;
ListCell *lc;
- /* Currently default partition is supported only for LIST partition. */
- Assert(new_spec->strategy == PARTITION_STRATEGY_LIST);
- new_part_constraints = get_qual_for_list(parent, new_spec);
+ new_part_constraints = (new_spec->strategy == PARTITION_STRATEGY_LIST)
+ ? get_qual_for_list(parent, new_spec)
+ : get_qual_for_range(parent, new_spec, false);
def_part_constraints =
get_default_part_validation_constraint(new_part_constraints);
@@ -1083,7 +1086,7 @@ get_qual_from_partbound(Relation rel, Relation parent,
case PARTITION_STRATEGY_RANGE:
Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
- my_qual = get_qual_for_range(key, spec);
+ my_qual = get_qual_for_range(parent, spec, false);
break;
default:
@@ -1706,6 +1709,53 @@ get_range_key_properties(PartitionKey key, int keynum,
*upper_val = NULL;
}
+ /*
+ * get_range_nulltest
+ *
+ * A non-default range partition table does not currently allow partition
+ * keys to be null, so emit an IS NOT NULL expression for each key column.
+ */
+static List *
+get_range_nulltest(PartitionKey key)
+{
+ List *result = NIL;
+ NullTest *nulltest;
+ ListCell *partexprs_item;
+ int i;
+
+ partexprs_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ Expr *keyCol;
+
+ if (key->partattrs[i] != 0)
+ {
+ keyCol = (Expr *) makeVar(1,
+ key->partattrs[i],
+ key->parttypid[i],
+ key->parttypmod[i],
+ key->parttypcoll[i],
+ 0);
+ }
+ else
+ {
+ if (partexprs_item == NULL)
+ elog(ERROR, "wrong number of partition key expressions");
+ keyCol = copyObject(lfirst(partexprs_item));
+ partexprs_item = lnext(partexprs_item);
+ }
+
+ nulltest = makeNode(NullTest);
+ nulltest->arg = keyCol;
+ nulltest->nulltesttype = IS_NOT_NULL;
+ nulltest->argisrow = false;
+ nulltest->location = -1;
+ result = lappend(result, nulltest);
+ }
+
+ return result;
+}
+
/*
* get_qual_for_range
*
@@ -1744,11 +1794,15 @@ get_range_key_properties(PartitionKey key, int keynum,
* In most common cases with only one partition column, say a, the following
* expression tree will be generated: a IS NOT NULL AND a >= al AND a < au
*
+ * For default partition, it returns the negation of the constraints of all
+ * the other partitions.
+ *
* If we end up with an empty result list, we return a single-member list
* containing a constant TRUE, because callers expect a non-empty list.
*/
static List *
-get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
+get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
+ bool for_default)
{
List *result = NIL;
ListCell *cell1,
@@ -1759,10 +1813,10 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
j;
PartitionRangeDatum *ldatum,
*udatum;
+ PartitionKey key = RelationGetPartitionKey(parent);
Expr *keyCol;
Const *lower_val,
*upper_val;
- NullTest *nulltest;
List *lower_or_arms,
*upper_or_arms;
int num_or_arms,
@@ -1772,44 +1826,70 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
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;
-
- /*
- * A range-partitioned table does not currently allow partition keys to be
- * null, so emit an IS NOT NULL expression for each key column.
- */
- partexprs_item = list_head(key->partexprs);
- for (i = 0; i < key->partnatts; i++)
+ if (spec->is_default)
{
- Expr *keyCol;
+ List *or_expr_args = NIL;
+ PartitionDesc pdesc = RelationGetPartitionDesc(parent);
+ Oid *inhoids = pdesc->oids;
+ int nparts = pdesc->nparts,
+ i;
- if (key->partattrs[i] != 0)
+ for (i = 0; i < nparts; i++)
{
- keyCol = (Expr *) makeVar(1,
- key->partattrs[i],
- key->parttypid[i],
- key->parttypmod[i],
- key->parttypcoll[i],
- 0);
+ Oid inhrelid = inhoids[i];
+ HeapTuple tuple;
+ Datum datum;
+ bool isnull;
+ PartitionBoundSpec *bspec;
+
+ tuple = SearchSysCache1(RELOID, inhrelid);
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", inhrelid);
+
+ datum = SysCacheGetAttr(RELOID, tuple,
+ Anum_pg_class_relpartbound,
+ &isnull);
+
+ Assert(!isnull);
+ bspec = (PartitionBoundSpec *) stringToNode(TextDatumGetCString(datum));
+
+ if (!bspec->is_default)
+ {
+ List *part_qual = get_qual_for_range(parent, bspec, true);
+
+ /*
+ * AND the constraints of the partition and add to
+ * or_expr_args
+ */
+ or_expr_args = lappend(or_expr_args, list_length(part_qual) > 1
+ ? makeBoolExpr(AND_EXPR, part_qual, -1)
+ : linitial(part_qual));
+ }
+ ReleaseSysCache(tuple);
}
- else
+
+ if (or_expr_args != NIL)
{
- if (partexprs_item == NULL)
- elog(ERROR, "wrong number of partition key expressions");
- keyCol = copyObject(lfirst(partexprs_item));
- partexprs_item = lnext(partexprs_item);
+ /* OR all the non-default partition constraints; then negate it */
+ result = lappend(result,
+ list_length(or_expr_args) > 1
+ ? makeBoolExpr(OR_EXPR, or_expr_args, -1)
+ : linitial(or_expr_args));
+ result = list_make1(makeBoolExpr(NOT_EXPR, result, -1));
}
+ else /* default is the only partition */
+ result = list_make1(makeBoolConst(true, false));
- nulltest = makeNode(NullTest);
- nulltest->arg = keyCol;
- nulltest->nulltesttype = IS_NOT_NULL;
- nulltest->argisrow = false;
- nulltest->location = -1;
- result = lappend(result, nulltest);
+ return result;
}
+ lower_or_start_datum = list_head(spec->lowerdatums);
+ upper_or_start_datum = list_head(spec->upperdatums);
+ num_or_arms = key->partnatts;
+
+ if (!for_default)
+ result = get_range_nulltest(key);
+
/*
* Iterate over the key columns and check if the corresponding lower and
* upper datums are equal using the btree equality operator for the
@@ -2033,7 +2113,9 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
/* As noted above, caller expects the list to be non-empty. */
if (result == NIL)
- result = list_make1(makeBoolConst(true, false));
+ result = for_default
+ ? get_range_nulltest(key)
+ : list_make1(makeBoolConst(true, false));
return result;
}
@@ -2201,8 +2283,7 @@ get_partition_for_tuple(PartitionDispatch *pd,
Datum values[PARTITION_MAX_KEYS];
bool isnull[PARTITION_MAX_KEYS];
int cur_offset,
- cur_index;
- int i,
+ cur_index = -1,
result;
ExprContext *ecxt = GetPerTupleExprContext(estate);
TupleTableSlot *ecxt_scantuple_old = ecxt->ecxt_scantuple;
@@ -2246,24 +2327,6 @@ get_partition_for_tuple(PartitionDispatch *pd,
ecxt->ecxt_scantuple = slot;
FormPartitionKeyDatum(parent, slot, estate, values, isnull);
- if (key->strategy == PARTITION_STRATEGY_RANGE)
- {
- /*
- * Since we cannot route tuples with NULL partition keys through a
- * range-partitioned table, simply return that no partition exists
- */
- for (i = 0; i < key->partnatts; i++)
- {
- if (isnull[i])
- {
- *failed_at = parent;
- *failed_slot = slot;
- result = -1;
- goto error_exit;
- }
- }
- }
-
/*
* If this is a NULL, route it to the null-accepting partition.
* Otherwise, route by searching the array of partition bounds.
@@ -2366,6 +2429,8 @@ make_one_range_bound(PartitionKey key, int index, List *datums, bool lower)
ListCell *lc;
int i;
+ Assert(datums != NULL);
+
bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
bound->index = index;
bound->datums = (Datum *) palloc0(key->partnatts * sizeof(Datum));
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 6830b64..6157613 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3307,11 +3307,6 @@ transformPartitionBound(ParseState *pstate, Relation parent,
if (spec->is_default)
{
- if (strategy != PARTITION_STRATEGY_LIST)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("default partition is supported only for a list partitioned table")));
-
/*
* In case of the default partition, parser had no way to identify the
* partition strategy. Assign the parent strategy to the default
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index e066159..67a5c62 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8700,7 +8700,6 @@ get_rule_expr(Node *node, deparse_context *context,
if (spec->is_default)
{
- Assert(strategy == PARTITION_STRATEGY_LIST);
appendStringInfoString(buf, "DEFAULT");
break;
}
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 9a5f6bf..6855d71 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3356,6 +3356,19 @@ CREATE TABLE part2 (
);
ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
INFO: partition constraint for table "part2" is implied by existing constraints
+-- Create default partition
+CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
+-- Only one default partition is allowed, hence, following should give error
+CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS);
+ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT;
+ERROR: partition "partr_def2" conflicts with existing default partition "partr_def1"
+-- Overlapping partitions cannot be attached, hence, following should give error
+INSERT INTO partr_def1 VALUES (2, 10);
+CREATE TABLE part3 (LIKE range_parted);
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (2, 10) TO (2, 20);
+ERROR: updated partition constraint for default partition would be violated by some row
+-- Attaching partitions should be successful when there are no overlapping rows
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (3, 10) TO (3, 20);
-- check that leaf partitions are scanned when attaching a partitioned
-- table
CREATE TABLE part_5 (
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 9615c07..55914e4 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -517,9 +517,6 @@ ERROR: TO must specify exactly one value per partitioning column
-- cannot specify null values in range bounds
CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (maxvalue);
ERROR: cannot specify NULL in range bound
--- range partition cannot have default partition
-CREATE TABLE fail_part PARTITION OF range_parted DEFAULT;
-ERROR: default partition is supported only for a list partitioned table
-- check if compatible with the specified parent
-- cannot create as partition of a non-partitioned table
CREATE TABLE unparted (
@@ -594,6 +591,16 @@ CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30);
ERROR: partition "fail_part" would overlap partition "part2"
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50);
ERROR: partition "fail_part" would overlap partition "part2"
+-- Create a default partition for range partitioned table
+CREATE TABLE range2_default PARTITION OF range_parted2 DEFAULT;
+-- More than one default partition is not allowed, so this should give error
+CREATE TABLE fail_default_part PARTITION OF range_parted2 DEFAULT;
+ERROR: partition "fail_default_part" conflicts with existing default partition "range2_default"
+-- Check if the range for default partitions overlap
+INSERT INTO range_parted2 VALUES (85);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90);
+ERROR: updated partition constraint for default partition "range2_default" would be violated by some row
+CREATE TABLE part4 PARTITION OF range_parted2 FOR VALUES FROM (90) TO (100);
-- now check for multi-column range partition key
CREATE TABLE range_parted3 (
a int,
@@ -607,6 +614,7 @@ CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10)
CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, maxvalue);
CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
ERROR: partition "fail_part" would overlap partition "part12"
+CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
-- cannot create a partition that says column b is allowed to range
-- from -infinity to +infinity, while there exist partitions that have
-- more specific ranges
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 3319f6d..cc28bdc 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -271,6 +271,18 @@ insert into range_parted values ('b', 10);
insert into range_parted values ('a');
ERROR: no partition of relation "range_parted" found for row
DETAIL: Partition key of the failing row contains (a, (b + 0)) = (a, null).
+-- Check default partition
+create table part_def partition of range_parted default;
+-- fail
+insert into part_def values ('b', 10);
+ERROR: new row for relation "part_def" violates partition constraint
+DETAIL: Failing row contains (b, 10).
+-- ok
+insert into part_def values ('c', 10);
+insert into range_parted values (null, null);
+insert into range_parted values ('a', null);
+insert into range_parted values (null, 19);
+insert into range_parted values ('b', 20);
select tableoid::regclass, * from range_parted;
tableoid | a | b
----------+---+----
@@ -280,7 +292,12 @@ select tableoid::regclass, * from range_parted;
part3 | b | 1
part4 | b | 10
part4 | b | 10
-(6 rows)
+ part_def | c | 10
+ part_def | |
+ part_def | a |
+ part_def | | 19
+ part_def | b | 20
+(11 rows)
-- ok
insert into list_parted values (null, 1);
@@ -467,6 +484,36 @@ insert into mlparted5 (a, b, c) values (1, 40, 'a');
ERROR: new row for relation "mlparted5a" violates partition constraint
DETAIL: Failing row contains (b, 1, 40).
drop table mlparted5;
+alter table mlparted drop constraint check_b;
+-- Check multi-level default partition
+create table mlparted_def partition of mlparted default partition by range(a);
+create table mlparted_def1 partition of mlparted_def for values from (40) to (50);
+create table mlparted_def2 partition of mlparted_def for values from (50) to (60);
+insert into mlparted values (40, 100);
+insert into mlparted_def1 values (42, 100);
+insert into mlparted_def2 values (54, 50);
+-- fail
+insert into mlparted values (70, 100);
+ERROR: no partition of relation "mlparted_def" found for row
+DETAIL: Partition key of the failing row contains (a) = (70).
+insert into mlparted_def1 values (52, 50);
+ERROR: new row for relation "mlparted_def1" violates partition constraint
+DETAIL: Failing row contains (52, 50, null).
+insert into mlparted_def2 values (34, 50);
+ERROR: new row for relation "mlparted_def2" violates partition constraint
+DETAIL: Failing row contains (34, 50, null).
+-- ok
+create table mlparted_defd partition of mlparted_def default;
+insert into mlparted values (70, 100);
+select tableoid::regclass, * from mlparted_def;
+ tableoid | a | b | c
+---------------+----+-----+---
+ mlparted_def1 | 40 | 100 |
+ mlparted_def1 | 42 | 100 |
+ mlparted_def2 | 54 | 50 |
+ mlparted_defd | 70 | 100 |
+(4 rows)
+
-- check that message shown after failure to find a partition shows the
-- appropriate key description (or none) in various situations
create table key_desc (a int, b int) partition by list ((a+0));
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 6750152..e996640 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -77,6 +77,10 @@ mlparted12|f
mlparted2|f
mlparted3|f
mlparted4|f
+mlparted_def|f
+mlparted_def1|f
+mlparted_def2|f
+mlparted_defd|f
money_data|f
num_data|f
num_exp_add|t
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index 9912ef2..cfb6aa8 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -218,6 +218,15 @@ ERROR: new row for relation "part_b_10_b_20" violates partition constraint
DETAIL: Failing row contains (b, 9).
-- ok
update range_parted set b = b + 1 where b = 10;
+-- Creating default partition for range
+create table part_def partition of range_parted default;
+insert into range_parted values ('c', 9);
+-- ok
+update part_def set a = 'd' where a = 'c';
+-- fail
+update part_def set a = 'a' where a = 'd';
+ERROR: new row for relation "part_def" violates partition constraint
+DETAIL: Failing row contains (a, 9).
create table list_parted (
a text,
b int
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 935b24b..d4ca7f1 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2181,6 +2181,21 @@ CREATE TABLE part2 (
);
ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
+-- Create default partition
+CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
+
+-- Only one default partition is allowed, hence, following should give error
+CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS);
+ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT;
+
+-- Overlapping partitions cannot be attached, hence, following should give error
+INSERT INTO partr_def1 VALUES (2, 10);
+CREATE TABLE part3 (LIKE range_parted);
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (2, 10) TO (2, 20);
+
+-- Attaching partitions should be successful when there are no overlapping rows
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (3, 10) TO (3, 20);
+
-- check that leaf partitions are scanned when attaching a partitioned
-- table
CREATE TABLE part_5 (
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 1eefa0e..e4fde85 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -486,8 +486,6 @@ CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a') TO ('z',
-- cannot specify null values in range bounds
CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (maxvalue);
--- range partition cannot have default partition
-CREATE TABLE fail_part PARTITION OF range_parted DEFAULT;
-- check if compatible with the specified parent
@@ -554,6 +552,17 @@ CREATE TABLE part3 PARTITION OF range_parted2 FOR VALUES FROM (30) TO (40);
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30);
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50);
+-- Create a default partition for range partitioned table
+CREATE TABLE range2_default PARTITION OF range_parted2 DEFAULT;
+
+-- More than one default partition is not allowed, so this should give error
+CREATE TABLE fail_default_part PARTITION OF range_parted2 DEFAULT;
+
+-- Check if the range for default partitions overlap
+INSERT INTO range_parted2 VALUES (85);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90);
+CREATE TABLE part4 PARTITION OF range_parted2 FOR VALUES FROM (90) TO (100);
+
-- now check for multi-column range partition key
CREATE TABLE range_parted3 (
a int,
@@ -567,6 +576,7 @@ CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES FROM (1, minvalue) TO
CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10);
CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, maxvalue);
CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
+CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
-- cannot create a partition that says column b is allowed to range
-- from -infinity to +infinity, while there exist partitions that have
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 7bf1778..6f5689f 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -168,8 +168,19 @@ insert into range_parted values ('b', 1);
insert into range_parted values ('b', 10);
-- fail (partition key (b+0) is null)
insert into range_parted values ('a');
-select tableoid::regclass, * from range_parted;
+-- Check default partition
+create table part_def partition of range_parted default;
+-- fail
+insert into part_def values ('b', 10);
+-- ok
+insert into part_def values ('c', 10);
+insert into range_parted values (null, null);
+insert into range_parted values ('a', null);
+insert into range_parted values (null, 19);
+insert into range_parted values ('b', 20);
+
+select tableoid::regclass, * from range_parted;
-- ok
insert into list_parted values (null, 1);
insert into list_parted (a) values ('aA');
@@ -299,6 +310,24 @@ create function mlparted5abrtrig_func() returns trigger as $$ begin new.c = 'b';
create trigger mlparted5abrtrig before insert on mlparted5a for each row execute procedure mlparted5abrtrig_func();
insert into mlparted5 (a, b, c) values (1, 40, 'a');
drop table mlparted5;
+alter table mlparted drop constraint check_b;
+
+-- Check multi-level default partition
+create table mlparted_def partition of mlparted default partition by range(a);
+create table mlparted_def1 partition of mlparted_def for values from (40) to (50);
+create table mlparted_def2 partition of mlparted_def for values from (50) to (60);
+insert into mlparted values (40, 100);
+insert into mlparted_def1 values (42, 100);
+insert into mlparted_def2 values (54, 50);
+-- fail
+insert into mlparted values (70, 100);
+insert into mlparted_def1 values (52, 50);
+insert into mlparted_def2 values (34, 50);
+-- ok
+create table mlparted_defd partition of mlparted_def default;
+insert into mlparted values (70, 100);
+
+select tableoid::regclass, * from mlparted_def;
-- check that message shown after failure to find a partition shows the
-- appropriate key description (or none) in various situations
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 44fb0dc..1ce7a94 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -125,6 +125,14 @@ update range_parted set b = b - 1 where b = 10;
-- ok
update range_parted set b = b + 1 where b = 10;
+-- Creating default partition for range
+create table part_def partition of range_parted default;
+insert into range_parted values ('c', 9);
+-- ok
+update part_def set a = 'd' where a = 'c';
+-- fail
+update part_def set a = 'a' where a = 'd';
+
create table list_parted (
a text,
b int
On Wed, Aug 9, 2017 at 8:26 AM, Beena Emerson <memissemerson@gmail.com>
wrote:
I have updated the patch to make it similar to the way default/null is
handled in list partition, removing the PARTITION_RANGE_DATUM_DEFAULT.
This is to be applied over v24 patches shared by Jeevan [1] which
applies on commit id 5ff3d73813ebcc3ff80be77c30b458d728951036.The RelationBuildPartitionDesc has been modified a lot, especially the
way all_bounds, ndatums and rbounds are set.[1] /messages/by-id/CAOgcT0OVwDu%25
2BbeChWb5R5s6rfKLCiWcZT5617hqu7T3GdA1hAw%40mail.gmail.com
Hi Beena,
I have applied Jeevan's v24 patches and then your v9 patch over commit
5ff3d73813ebcc3ff80be77c30b458d728951036.
and while testing I got a server crash. below is sql to reproduce it.
postgres=# CREATE TABLE rp (a int, b int) PARTITION by range (a);
CREATE TABLE
postgres=# CREATE TABLE rp_p1 PARTITION OF rp DEFAULT partition by range(a);
CREATE TABLE
postgres=# CREATE TABLE rp_p11 PARTITION OF rp_p1 FOR VALUES FROM (1) TO
(15);
CREATE TABLE
postgres=# CREATE TABLE rp_p12 PARTITION OF rp_p1 DEFAULT;
CREATE TABLE
postgres=# insert into rp select i,i from generate_series(1,15) i;
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.
Thanks & Regards,
Rajkumar Raghuwanshi
QMG, EnterpriseDB Corporation
Hello Rajkumar,
On Wed, Aug 9, 2017 at 12:37 PM, Rajkumar Raghuwanshi
<rajkumar.raghuwanshi@enterprisedb.com> wrote:
Hi Beena,
I have applied Jeevan's v24 patches and then your v9 patch over commit
5ff3d73813ebcc3ff80be77c30b458d728951036.
and while testing I got a server crash. below is sql to reproduce it.postgres=# CREATE TABLE rp (a int, b int) PARTITION by range (a);
CREATE TABLE
postgres=# CREATE TABLE rp_p1 PARTITION OF rp DEFAULT partition by range(a);
CREATE TABLE
postgres=# CREATE TABLE rp_p11 PARTITION OF rp_p1 FOR VALUES FROM (1) TO
(15);
CREATE TABLE
postgres=# CREATE TABLE rp_p12 PARTITION OF rp_p1 DEFAULT;
CREATE TABLE
postgres=# insert into rp select i,i from generate_series(1,15) i;
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.
Thank you for testing. It seems I made a mistake in the assert
condition. I have corrected it in this patch.
Thank you,
Beena Emerson
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Attachments:
default_range_partition_v10.patchapplication/octet-stream; name=default_range_partition_v10.patchDownload
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index e0b9dec..75b03d2 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -127,7 +127,9 @@ static void get_range_key_properties(PartitionKey key, int keynum,
Expr **keyCol,
Const **lower_val, Const **upper_val);
static List *get_qual_for_list(Relation parent, PartitionBoundSpec *spec);
-static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
+ bool for_default);
+static List *get_range_nulltest(PartitionKey key);
static List *generate_partition_qual(Relation rel);
static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index,
@@ -168,11 +170,11 @@ RelationBuildPartitionDesc(Relation rel)
MemoryContext oldcxt;
int ndatums = 0;
+ int default_index = -1;
/* List partitioning specific */
PartitionListValue **all_values = NULL;
int null_index = -1;
- int default_index = -1;
/* Range partitioning specific */
PartitionRangeBound **rbounds = NULL;
@@ -322,21 +324,18 @@ RelationBuildPartitionDesc(Relation rel)
}
else if (key->strategy == PARTITION_STRATEGY_RANGE)
{
- int j,
- k;
+ int k;
PartitionRangeBound **all_bounds,
*prev;
- bool *distinct_indexes;
all_bounds = (PartitionRangeBound **) palloc0(2 * nparts *
sizeof(PartitionRangeBound *));
- distinct_indexes = (bool *) palloc(2 * nparts * sizeof(bool));
/*
* Create a unified list of range bounds across all the
* partitions.
*/
- i = j = 0;
+ i = ndatums = 0;
foreach(cell, boundspecs)
{
PartitionBoundSpec *spec = castNode(PartitionBoundSpec,
@@ -347,30 +346,41 @@ RelationBuildPartitionDesc(Relation rel)
if (spec->strategy != PARTITION_STRATEGY_RANGE)
elog(ERROR, "invalid strategy in partition bound spec");
+ /*
+ * Note the index of the partition bound spec for the default
+ * partition. There's no datum to add to the allbounds array
+ * for this partition.
+ */
+ if (spec->is_default)
+ {
+ default_index = i++;
+ continue;
+ }
+
lower = make_one_range_bound(key, i, spec->lowerdatums,
true);
upper = make_one_range_bound(key, i, spec->upperdatums,
false);
- all_bounds[j] = lower;
- all_bounds[j + 1] = upper;
- j += 2;
+ all_bounds[ndatums++] = lower;
+ all_bounds[ndatums++] = upper;
i++;
}
- Assert(j == 2 * nparts);
+
+ Assert(ndatums == nparts * 2 ||
+ (default_index != -1 && ndatums == (nparts - 1) * 2));
/* Sort all the bounds in ascending order */
- qsort_arg(all_bounds, 2 * nparts,
+ qsort_arg(all_bounds, ndatums,
sizeof(PartitionRangeBound *),
qsort_partition_rbound_cmp,
(void *) key);
- /*
- * Count the number of distinct bounds to allocate an array of
- * that size.
- */
- ndatums = 0;
+ /* Pick out distinct datums and store it in a new array. */
+ rbounds = (PartitionRangeBound **) palloc(ndatums *
+ sizeof(PartitionRangeBound *));
+ k = 0;
prev = NULL;
- for (i = 0; i < 2 * nparts; i++)
+ for (i = 0; i < ndatums; i++)
{
PartitionRangeBound *cur = all_bounds[i];
bool is_distinct = false;
@@ -407,34 +417,17 @@ RelationBuildPartitionDesc(Relation rel)
}
/*
- * Count the current bound if it is distinct from the previous
- * one. Also, store if the index i contains a distinct bound
- * that we'd like put in the relcache array.
+ * If distinct, save into array from where they will be copied
+ * into the relcache.
*/
if (is_distinct)
- {
- distinct_indexes[i] = true;
- ndatums++;
- }
- else
- distinct_indexes[i] = false;
+ rbounds[k++] = all_bounds[i];
prev = cur;
}
- /*
- * Finally save them in an array from where they will be copied
- * into the relcache.
- */
- rbounds = (PartitionRangeBound **) palloc(ndatums *
- sizeof(PartitionRangeBound *));
- k = 0;
- for (i = 0; i < 2 * nparts; i++)
- {
- if (distinct_indexes[i])
- rbounds[k++] = all_bounds[i];
- }
- Assert(k == ndatums);
+ /* Update ndatums to hold the count of distinct datums. */
+ ndatums = k;
}
else
elog(ERROR, "unexpected partition strategy: %d",
@@ -581,6 +574,14 @@ RelationBuildPartitionDesc(Relation rel)
boundinfo->indexes[i] = mapping[orig_index];
}
}
+
+ /* Assign mapped index for the default partition. */
+ if (default_index != -1)
+ {
+ Assert(default_index >= 0 && mapping[default_index] == -1);
+ mapping[default_index] = next_index++;
+ boundinfo->default_index = mapping[default_index];
+ }
boundinfo->indexes[i] = -1;
break;
}
@@ -787,8 +788,10 @@ check_new_partition_bound(char *relname, Relation parent,
int offset;
bool equal;
- Assert(boundinfo && boundinfo->ndatums > 0 &&
- boundinfo->strategy == PARTITION_STRATEGY_RANGE);
+ Assert(boundinfo &&
+ boundinfo->strategy == PARTITION_STRATEGY_RANGE &&
+ (boundinfo->ndatums > 0 ||
+ partition_bound_has_default(boundinfo)));
/*
* Test whether the new lower bound (which is treated
@@ -880,10 +883,10 @@ check_default_allows_bound(Relation parent, Relation default_rel,
List *all_parts;
ListCell *lc;
- /* Currently default partition is supported only for LIST partition. */
- Assert(new_spec->strategy == PARTITION_STRATEGY_LIST);
- new_part_constraints = get_qual_for_list(parent, new_spec);
+ new_part_constraints = (new_spec->strategy == PARTITION_STRATEGY_LIST)
+ ? get_qual_for_list(parent, new_spec)
+ : get_qual_for_range(parent, new_spec, false);
def_part_constraints =
get_default_part_validation_constraint(new_part_constraints);
@@ -1083,7 +1086,7 @@ get_qual_from_partbound(Relation rel, Relation parent,
case PARTITION_STRATEGY_RANGE:
Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
- my_qual = get_qual_for_range(key, spec);
+ my_qual = get_qual_for_range(parent, spec, false);
break;
default:
@@ -1706,6 +1709,53 @@ get_range_key_properties(PartitionKey key, int keynum,
*upper_val = NULL;
}
+ /*
+ * get_range_nulltest
+ *
+ * A non-default range partition table does not currently allow partition
+ * keys to be null, so emit an IS NOT NULL expression for each key column.
+ */
+static List *
+get_range_nulltest(PartitionKey key)
+{
+ List *result = NIL;
+ NullTest *nulltest;
+ ListCell *partexprs_item;
+ int i;
+
+ partexprs_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ Expr *keyCol;
+
+ if (key->partattrs[i] != 0)
+ {
+ keyCol = (Expr *) makeVar(1,
+ key->partattrs[i],
+ key->parttypid[i],
+ key->parttypmod[i],
+ key->parttypcoll[i],
+ 0);
+ }
+ else
+ {
+ if (partexprs_item == NULL)
+ elog(ERROR, "wrong number of partition key expressions");
+ keyCol = copyObject(lfirst(partexprs_item));
+ partexprs_item = lnext(partexprs_item);
+ }
+
+ nulltest = makeNode(NullTest);
+ nulltest->arg = keyCol;
+ nulltest->nulltesttype = IS_NOT_NULL;
+ nulltest->argisrow = false;
+ nulltest->location = -1;
+ result = lappend(result, nulltest);
+ }
+
+ return result;
+}
+
/*
* get_qual_for_range
*
@@ -1744,11 +1794,15 @@ get_range_key_properties(PartitionKey key, int keynum,
* In most common cases with only one partition column, say a, the following
* expression tree will be generated: a IS NOT NULL AND a >= al AND a < au
*
+ * For default partition, it returns the negation of the constraints of all
+ * the other partitions.
+ *
* If we end up with an empty result list, we return a single-member list
* containing a constant TRUE, because callers expect a non-empty list.
*/
static List *
-get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
+get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
+ bool for_default)
{
List *result = NIL;
ListCell *cell1,
@@ -1759,10 +1813,10 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
j;
PartitionRangeDatum *ldatum,
*udatum;
+ PartitionKey key = RelationGetPartitionKey(parent);
Expr *keyCol;
Const *lower_val,
*upper_val;
- NullTest *nulltest;
List *lower_or_arms,
*upper_or_arms;
int num_or_arms,
@@ -1772,44 +1826,70 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
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;
-
- /*
- * A range-partitioned table does not currently allow partition keys to be
- * null, so emit an IS NOT NULL expression for each key column.
- */
- partexprs_item = list_head(key->partexprs);
- for (i = 0; i < key->partnatts; i++)
+ if (spec->is_default)
{
- Expr *keyCol;
+ List *or_expr_args = NIL;
+ PartitionDesc pdesc = RelationGetPartitionDesc(parent);
+ Oid *inhoids = pdesc->oids;
+ int nparts = pdesc->nparts,
+ i;
- if (key->partattrs[i] != 0)
+ for (i = 0; i < nparts; i++)
{
- keyCol = (Expr *) makeVar(1,
- key->partattrs[i],
- key->parttypid[i],
- key->parttypmod[i],
- key->parttypcoll[i],
- 0);
+ Oid inhrelid = inhoids[i];
+ HeapTuple tuple;
+ Datum datum;
+ bool isnull;
+ PartitionBoundSpec *bspec;
+
+ tuple = SearchSysCache1(RELOID, inhrelid);
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", inhrelid);
+
+ datum = SysCacheGetAttr(RELOID, tuple,
+ Anum_pg_class_relpartbound,
+ &isnull);
+
+ Assert(!isnull);
+ bspec = (PartitionBoundSpec *) stringToNode(TextDatumGetCString(datum));
+
+ if (!bspec->is_default)
+ {
+ List *part_qual = get_qual_for_range(parent, bspec, true);
+
+ /*
+ * AND the constraints of the partition and add to
+ * or_expr_args
+ */
+ or_expr_args = lappend(or_expr_args, list_length(part_qual) > 1
+ ? makeBoolExpr(AND_EXPR, part_qual, -1)
+ : linitial(part_qual));
+ }
+ ReleaseSysCache(tuple);
}
- else
+
+ if (or_expr_args != NIL)
{
- if (partexprs_item == NULL)
- elog(ERROR, "wrong number of partition key expressions");
- keyCol = copyObject(lfirst(partexprs_item));
- partexprs_item = lnext(partexprs_item);
+ /* OR all the non-default partition constraints; then negate it */
+ result = lappend(result,
+ list_length(or_expr_args) > 1
+ ? makeBoolExpr(OR_EXPR, or_expr_args, -1)
+ : linitial(or_expr_args));
+ result = list_make1(makeBoolExpr(NOT_EXPR, result, -1));
}
+ else /* default is the only partition */
+ result = list_make1(makeBoolConst(true, false));
- nulltest = makeNode(NullTest);
- nulltest->arg = keyCol;
- nulltest->nulltesttype = IS_NOT_NULL;
- nulltest->argisrow = false;
- nulltest->location = -1;
- result = lappend(result, nulltest);
+ return result;
}
+ lower_or_start_datum = list_head(spec->lowerdatums);
+ upper_or_start_datum = list_head(spec->upperdatums);
+ num_or_arms = key->partnatts;
+
+ if (!for_default)
+ result = get_range_nulltest(key);
+
/*
* Iterate over the key columns and check if the corresponding lower and
* upper datums are equal using the btree equality operator for the
@@ -2033,7 +2113,9 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
/* As noted above, caller expects the list to be non-empty. */
if (result == NIL)
- result = list_make1(makeBoolConst(true, false));
+ result = for_default
+ ? get_range_nulltest(key)
+ : list_make1(makeBoolConst(true, false));
return result;
}
@@ -2201,8 +2283,7 @@ get_partition_for_tuple(PartitionDispatch *pd,
Datum values[PARTITION_MAX_KEYS];
bool isnull[PARTITION_MAX_KEYS];
int cur_offset,
- cur_index;
- int i,
+ cur_index = -1,
result;
ExprContext *ecxt = GetPerTupleExprContext(estate);
TupleTableSlot *ecxt_scantuple_old = ecxt->ecxt_scantuple;
@@ -2246,24 +2327,6 @@ get_partition_for_tuple(PartitionDispatch *pd,
ecxt->ecxt_scantuple = slot;
FormPartitionKeyDatum(parent, slot, estate, values, isnull);
- if (key->strategy == PARTITION_STRATEGY_RANGE)
- {
- /*
- * Since we cannot route tuples with NULL partition keys through a
- * range-partitioned table, simply return that no partition exists
- */
- for (i = 0; i < key->partnatts; i++)
- {
- if (isnull[i])
- {
- *failed_at = parent;
- *failed_slot = slot;
- result = -1;
- goto error_exit;
- }
- }
- }
-
/*
* If this is a NULL, route it to the null-accepting partition.
* Otherwise, route by searching the array of partition bounds.
@@ -2366,6 +2429,8 @@ make_one_range_bound(PartitionKey key, int index, List *datums, bool lower)
ListCell *lc;
int i;
+ Assert(datums != NULL);
+
bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
bound->index = index;
bound->datums = (Datum *) palloc0(key->partnatts * sizeof(Datum));
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 6830b64..6157613 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3307,11 +3307,6 @@ transformPartitionBound(ParseState *pstate, Relation parent,
if (spec->is_default)
{
- if (strategy != PARTITION_STRATEGY_LIST)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("default partition is supported only for a list partitioned table")));
-
/*
* In case of the default partition, parser had no way to identify the
* partition strategy. Assign the parent strategy to the default
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index e066159..67a5c62 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8700,7 +8700,6 @@ get_rule_expr(Node *node, deparse_context *context,
if (spec->is_default)
{
- Assert(strategy == PARTITION_STRATEGY_LIST);
appendStringInfoString(buf, "DEFAULT");
break;
}
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 9a5f6bf..6855d71 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3356,6 +3356,19 @@ CREATE TABLE part2 (
);
ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
INFO: partition constraint for table "part2" is implied by existing constraints
+-- Create default partition
+CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
+-- Only one default partition is allowed, hence, following should give error
+CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS);
+ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT;
+ERROR: partition "partr_def2" conflicts with existing default partition "partr_def1"
+-- Overlapping partitions cannot be attached, hence, following should give error
+INSERT INTO partr_def1 VALUES (2, 10);
+CREATE TABLE part3 (LIKE range_parted);
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (2, 10) TO (2, 20);
+ERROR: updated partition constraint for default partition would be violated by some row
+-- Attaching partitions should be successful when there are no overlapping rows
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (3, 10) TO (3, 20);
-- check that leaf partitions are scanned when attaching a partitioned
-- table
CREATE TABLE part_5 (
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 9615c07..55914e4 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -517,9 +517,6 @@ ERROR: TO must specify exactly one value per partitioning column
-- cannot specify null values in range bounds
CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (maxvalue);
ERROR: cannot specify NULL in range bound
--- range partition cannot have default partition
-CREATE TABLE fail_part PARTITION OF range_parted DEFAULT;
-ERROR: default partition is supported only for a list partitioned table
-- check if compatible with the specified parent
-- cannot create as partition of a non-partitioned table
CREATE TABLE unparted (
@@ -594,6 +591,16 @@ CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30);
ERROR: partition "fail_part" would overlap partition "part2"
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50);
ERROR: partition "fail_part" would overlap partition "part2"
+-- Create a default partition for range partitioned table
+CREATE TABLE range2_default PARTITION OF range_parted2 DEFAULT;
+-- More than one default partition is not allowed, so this should give error
+CREATE TABLE fail_default_part PARTITION OF range_parted2 DEFAULT;
+ERROR: partition "fail_default_part" conflicts with existing default partition "range2_default"
+-- Check if the range for default partitions overlap
+INSERT INTO range_parted2 VALUES (85);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90);
+ERROR: updated partition constraint for default partition "range2_default" would be violated by some row
+CREATE TABLE part4 PARTITION OF range_parted2 FOR VALUES FROM (90) TO (100);
-- now check for multi-column range partition key
CREATE TABLE range_parted3 (
a int,
@@ -607,6 +614,7 @@ CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10)
CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, maxvalue);
CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
ERROR: partition "fail_part" would overlap partition "part12"
+CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
-- cannot create a partition that says column b is allowed to range
-- from -infinity to +infinity, while there exist partitions that have
-- more specific ranges
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 3319f6d..cc28bdc 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -271,6 +271,18 @@ insert into range_parted values ('b', 10);
insert into range_parted values ('a');
ERROR: no partition of relation "range_parted" found for row
DETAIL: Partition key of the failing row contains (a, (b + 0)) = (a, null).
+-- Check default partition
+create table part_def partition of range_parted default;
+-- fail
+insert into part_def values ('b', 10);
+ERROR: new row for relation "part_def" violates partition constraint
+DETAIL: Failing row contains (b, 10).
+-- ok
+insert into part_def values ('c', 10);
+insert into range_parted values (null, null);
+insert into range_parted values ('a', null);
+insert into range_parted values (null, 19);
+insert into range_parted values ('b', 20);
select tableoid::regclass, * from range_parted;
tableoid | a | b
----------+---+----
@@ -280,7 +292,12 @@ select tableoid::regclass, * from range_parted;
part3 | b | 1
part4 | b | 10
part4 | b | 10
-(6 rows)
+ part_def | c | 10
+ part_def | |
+ part_def | a |
+ part_def | | 19
+ part_def | b | 20
+(11 rows)
-- ok
insert into list_parted values (null, 1);
@@ -467,6 +484,36 @@ insert into mlparted5 (a, b, c) values (1, 40, 'a');
ERROR: new row for relation "mlparted5a" violates partition constraint
DETAIL: Failing row contains (b, 1, 40).
drop table mlparted5;
+alter table mlparted drop constraint check_b;
+-- Check multi-level default partition
+create table mlparted_def partition of mlparted default partition by range(a);
+create table mlparted_def1 partition of mlparted_def for values from (40) to (50);
+create table mlparted_def2 partition of mlparted_def for values from (50) to (60);
+insert into mlparted values (40, 100);
+insert into mlparted_def1 values (42, 100);
+insert into mlparted_def2 values (54, 50);
+-- fail
+insert into mlparted values (70, 100);
+ERROR: no partition of relation "mlparted_def" found for row
+DETAIL: Partition key of the failing row contains (a) = (70).
+insert into mlparted_def1 values (52, 50);
+ERROR: new row for relation "mlparted_def1" violates partition constraint
+DETAIL: Failing row contains (52, 50, null).
+insert into mlparted_def2 values (34, 50);
+ERROR: new row for relation "mlparted_def2" violates partition constraint
+DETAIL: Failing row contains (34, 50, null).
+-- ok
+create table mlparted_defd partition of mlparted_def default;
+insert into mlparted values (70, 100);
+select tableoid::regclass, * from mlparted_def;
+ tableoid | a | b | c
+---------------+----+-----+---
+ mlparted_def1 | 40 | 100 |
+ mlparted_def1 | 42 | 100 |
+ mlparted_def2 | 54 | 50 |
+ mlparted_defd | 70 | 100 |
+(4 rows)
+
-- check that message shown after failure to find a partition shows the
-- appropriate key description (or none) in various situations
create table key_desc (a int, b int) partition by list ((a+0));
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 6750152..e996640 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -77,6 +77,10 @@ mlparted12|f
mlparted2|f
mlparted3|f
mlparted4|f
+mlparted_def|f
+mlparted_def1|f
+mlparted_def2|f
+mlparted_defd|f
money_data|f
num_data|f
num_exp_add|t
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index 9912ef2..cfb6aa8 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -218,6 +218,15 @@ ERROR: new row for relation "part_b_10_b_20" violates partition constraint
DETAIL: Failing row contains (b, 9).
-- ok
update range_parted set b = b + 1 where b = 10;
+-- Creating default partition for range
+create table part_def partition of range_parted default;
+insert into range_parted values ('c', 9);
+-- ok
+update part_def set a = 'd' where a = 'c';
+-- fail
+update part_def set a = 'a' where a = 'd';
+ERROR: new row for relation "part_def" violates partition constraint
+DETAIL: Failing row contains (a, 9).
create table list_parted (
a text,
b int
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 935b24b..d4ca7f1 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2181,6 +2181,21 @@ CREATE TABLE part2 (
);
ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
+-- Create default partition
+CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
+
+-- Only one default partition is allowed, hence, following should give error
+CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS);
+ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT;
+
+-- Overlapping partitions cannot be attached, hence, following should give error
+INSERT INTO partr_def1 VALUES (2, 10);
+CREATE TABLE part3 (LIKE range_parted);
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (2, 10) TO (2, 20);
+
+-- Attaching partitions should be successful when there are no overlapping rows
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (3, 10) TO (3, 20);
+
-- check that leaf partitions are scanned when attaching a partitioned
-- table
CREATE TABLE part_5 (
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 1eefa0e..e4fde85 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -486,8 +486,6 @@ CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a') TO ('z',
-- cannot specify null values in range bounds
CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (maxvalue);
--- range partition cannot have default partition
-CREATE TABLE fail_part PARTITION OF range_parted DEFAULT;
-- check if compatible with the specified parent
@@ -554,6 +552,17 @@ CREATE TABLE part3 PARTITION OF range_parted2 FOR VALUES FROM (30) TO (40);
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30);
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50);
+-- Create a default partition for range partitioned table
+CREATE TABLE range2_default PARTITION OF range_parted2 DEFAULT;
+
+-- More than one default partition is not allowed, so this should give error
+CREATE TABLE fail_default_part PARTITION OF range_parted2 DEFAULT;
+
+-- Check if the range for default partitions overlap
+INSERT INTO range_parted2 VALUES (85);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90);
+CREATE TABLE part4 PARTITION OF range_parted2 FOR VALUES FROM (90) TO (100);
+
-- now check for multi-column range partition key
CREATE TABLE range_parted3 (
a int,
@@ -567,6 +576,7 @@ CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES FROM (1, minvalue) TO
CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10);
CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, maxvalue);
CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
+CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
-- cannot create a partition that says column b is allowed to range
-- from -infinity to +infinity, while there exist partitions that have
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 7bf1778..6f5689f 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -168,8 +168,19 @@ insert into range_parted values ('b', 1);
insert into range_parted values ('b', 10);
-- fail (partition key (b+0) is null)
insert into range_parted values ('a');
-select tableoid::regclass, * from range_parted;
+-- Check default partition
+create table part_def partition of range_parted default;
+-- fail
+insert into part_def values ('b', 10);
+-- ok
+insert into part_def values ('c', 10);
+insert into range_parted values (null, null);
+insert into range_parted values ('a', null);
+insert into range_parted values (null, 19);
+insert into range_parted values ('b', 20);
+
+select tableoid::regclass, * from range_parted;
-- ok
insert into list_parted values (null, 1);
insert into list_parted (a) values ('aA');
@@ -299,6 +310,24 @@ create function mlparted5abrtrig_func() returns trigger as $$ begin new.c = 'b';
create trigger mlparted5abrtrig before insert on mlparted5a for each row execute procedure mlparted5abrtrig_func();
insert into mlparted5 (a, b, c) values (1, 40, 'a');
drop table mlparted5;
+alter table mlparted drop constraint check_b;
+
+-- Check multi-level default partition
+create table mlparted_def partition of mlparted default partition by range(a);
+create table mlparted_def1 partition of mlparted_def for values from (40) to (50);
+create table mlparted_def2 partition of mlparted_def for values from (50) to (60);
+insert into mlparted values (40, 100);
+insert into mlparted_def1 values (42, 100);
+insert into mlparted_def2 values (54, 50);
+-- fail
+insert into mlparted values (70, 100);
+insert into mlparted_def1 values (52, 50);
+insert into mlparted_def2 values (34, 50);
+-- ok
+create table mlparted_defd partition of mlparted_def default;
+insert into mlparted values (70, 100);
+
+select tableoid::regclass, * from mlparted_def;
-- check that message shown after failure to find a partition shows the
-- appropriate key description (or none) in various situations
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 44fb0dc..1ce7a94 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -125,6 +125,14 @@ update range_parted set b = b - 1 where b = 10;
-- ok
update range_parted set b = b + 1 where b = 10;
+-- Creating default partition for range
+create table part_def partition of range_parted default;
+insert into range_parted values ('c', 9);
+-- ok
+update part_def set a = 'd' where a = 'c';
+-- fail
+update part_def set a = 'a' where a = 'd';
+
create table list_parted (
a text,
b int
On Wed, Aug 9, 2017 at 1:54 PM, Beena Emerson <memissemerson@gmail.com>
wrote:
Hello Rajkumar,
On Wed, Aug 9, 2017 at 12:37 PM, Rajkumar Raghuwanshi
<rajkumar.raghuwanshi@enterprisedb.com> wrote:Hi Beena,
I have applied Jeevan's v24 patches and then your v9 patch over commit
5ff3d73813ebcc3ff80be77c30b458d728951036.
and while testing I got a server crash. below is sql to reproduce it.postgres=# CREATE TABLE rp (a int, b int) PARTITION by range (a);
CREATE TABLE
postgres=# CREATE TABLE rp_p1 PARTITION OF rp DEFAULT partition byrange(a);
CREATE TABLE
postgres=# CREATE TABLE rp_p11 PARTITION OF rp_p1 FOR VALUES FROM (1) TO
(15);
CREATE TABLE
postgres=# CREATE TABLE rp_p12 PARTITION OF rp_p1 DEFAULT;
CREATE TABLE
postgres=# insert into rp select i,i from generate_series(1,15) i;
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.Thank you for testing. It seems I made a mistake in the assert
condition. I have corrected it in this patch.Thanks Beena, I have tested new patch, crash got fixed now, got two
observations, please check if these are expected?
--difference in the description of default partition in case of list vs
range
create table lp (a int) partition by list(a);
create table lp_d partition of lp DEFAULT;
postgres=# \d+ lp_d
Table "public.lp_d"
Column | Type | Collation | Nullable | Default | Storage | Stats target
| Description
--------+---------+-----------+----------+---------+---------+--------------+-------------
a | integer | | | | plain |
|
Partition of: lp DEFAULT
*Partition constraint:*
create table rp (a int) partition by range(a);
create table rp_d partition of rp DEFAULT;
postgres=# \d+ rp_d
Table "public.rp_d"
Column | Type | Collation | Nullable | Default | Storage | Stats target
| Description
--------+---------+-----------+----------+---------+---------+--------------+-------------
a | integer | | | | plain |
|
Partition of: rp DEFAULT
*Partition constraint: true*
--getting warning WARNING: skipped scanning foreign table...which is a
partition of default partition...
--when adding new partition after adding default as foreign partition
CREATE EXTENSION postgres_fdw;
CREATE SERVER def_server FOREIGN DATA WRAPPER postgres_fdw OPTIONS (dbname
'postgres', port '5432', use_remote_estimate 'true');
CREATE USER MAPPING FOR PUBLIC SERVER def_server;
postgres=# CREATE TABLE frp (a int, b int) PARTITION BY RANGE(a);
CREATE TABLE
postgres=# CREATE TABLE frp_d PARTITION OF frp DEFAULT partition by
RANGE(b);
CREATE TABLE
postgres=# CREATE TABLE ffrp_d_d (like frp);
CREATE TABLE
postgres=# CREATE FOREIGN TABLE ftfrp_d_d PARTITION OF frp_d DEFAULT SERVER
def_server OPTIONS (TABLE_NAME 'ffrp_d_d');
CREATE FOREIGN TABLE
postgres=# CREATE TABLE frp_p2 PARTITION OF frp FOR VALUES FROM (10) TO
(12);
WARNING: skipped scanning foreign table "ftfrp_d_d" which is a partition
of default partition "frp_d"
CREATE TABLE
Thanks & Regards,
Rajkumar Raghuwanshi
QMG, EnterpriseDB Corporation
On Wed, Aug 9, 2017 at 8:18 AM, Rajkumar Raghuwanshi
<rajkumar.raghuwanshi@enterprisedb.com> wrote:
--difference in the description of default partition in case of list vs
rangecreate table lp (a int) partition by list(a);
create table lp_d partition of lp DEFAULT;
postgres=# \d+ lp_d
Table "public.lp_d"
Column | Type | Collation | Nullable | Default | Storage | Stats target
| Description
--------+---------+-----------+----------+---------+---------+--------------+-------------
a | integer | | | | plain |
|
Partition of: lp DEFAULT
Partition constraint:create table rp (a int) partition by range(a);
create table rp_d partition of rp DEFAULT;
postgres=# \d+ rp_d
Table "public.rp_d"
Column | Type | Collation | Nullable | Default | Storage | Stats target
| Description
--------+---------+-----------+----------+---------+---------+--------------+-------------
a | integer | | | | plain |
|
Partition of: rp DEFAULT
Partition constraint: true
This looks like a problem with the default list partitioning patch. I
think "true" is what we expect to see here in both cases.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hi Robert, Beena,
On Wed, Aug 9, 2017 at 5:53 PM, Robert Haas <robertmhaas@gmail.com> wrote:
On Wed, Aug 9, 2017 at 8:18 AM, Rajkumar Raghuwanshi
<rajkumar.raghuwanshi@enterprisedb.com> wrote:--difference in the description of default partition in case of list vs
rangecreate table lp (a int) partition by list(a);
create table lp_d partition of lp DEFAULT;
postgres=# \d+ lp_d
Table "public.lp_d"
Column | Type | Collation | Nullable | Default | Storage | Statstarget
| Description
--------+---------+-----------+----------+---------+---------+--------------+-------------
a | integer | | | | plain |
|
Partition of: lp DEFAULT
Partition constraint:create table rp (a int) partition by range(a);
create table rp_d partition of rp DEFAULT;
postgres=# \d+ rp_d
Table "public.rp_d"
Column | Type | Collation | Nullable | Default | Storage | Statstarget
| Description
--------+---------+-----------+----------+---------+---------+--------------+-------------
a | integer | | | | plain |
|
Partition of: rp DEFAULT
Partition constraint: trueThis looks like a problem with the default list partitioning patch. I
think "true" is what we expect to see here in both cases.
In case of list partition if there is only default partition, then there is
no
specific value set that can be excluded from default partition, so in
such a case DEFAULT partition for a list partitioned table simply will
not have any constraint on it, and hence while the describe output is
printed it does not have any constraints to be printed.
Whereas, in case of default partition for a range partitioned table, in
get_qual_for_range() it creates true boolean expression if default is the
only partition. Here is the relevent code from Beena's patch:
+ else /* default is the only partition */
+ result = list_make1(makeBoolConst(true, false));
I think, it is unnecessary to create a constraint that is going to be always
true. Instead, if we have no constraints we can avoid some extra cpu
cycles which would otherwise be wasted in evaluating an expression
that is going to be always true.
Having said this, I see a point that in such a case we should have
some neat meaningful content to be printed for "Partition constraint: ".
I think we can address this when we construct describe output string.
Some ideas that come to my mind are:
Partition constraint: NIL
Partition constraint: no constraints
No partition constraint.
Partition constraint: true
Please let me know your thoughts.
Regards,
Jeevan Ladhe
On Thu, Aug 10, 2017 at 6:56 AM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:
This looks like a problem with the default list partitioning patch. I
think "true" is what we expect to see here in both cases.In case of list partition if there is only default partition, then there is
no
specific value set that can be excluded from default partition, so in
such a case DEFAULT partition for a list partitioned table simply will
not have any constraint on it, and hence while the describe output is
printed it does not have any constraints to be printed.Whereas, in case of default partition for a range partitioned table, in
get_qual_for_range() it creates true boolean expression if default is the
only partition. Here is the relevent code from Beena's patch:+ else /* default is the only partition */ + result = list_make1(makeBoolConst(true, false));I think, it is unnecessary to create a constraint that is going to be always
true. Instead, if we have no constraints we can avoid some extra cpu
cycles which would otherwise be wasted in evaluating an expression
that is going to be always true.
That's a reasonable argument. I'm not sure whether having no
partition constraint at all is likely to be a source of bugs, but
certainly, not needing to check it is appealing.
Having said this, I see a point that in such a case we should have
some neat meaningful content to be printed for "Partition constraint: ".
I think we can address this when we construct describe output string.Some ideas that come to my mind are:
Partition constraint: NIL
Partition constraint: no constraints
No partition constraint.
Partition constraint: truePlease let me know your thoughts.
I like "No partition constraint." of those options.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hello,
PFA the updated patch which returns NULL instead of true when the
default partition has no constraints and also have modified the output
as discussed above.
This applies over v24 patch [1]/messages/by-id/CAOgcT0OVwDu+beChWb5R5s6rfKLCiWcZT5617hqu7T3GdA1hAw@mail.gmail.com -- of default list partition. I will
rebase over the next version when it is updated.
[1]: /messages/by-id/CAOgcT0OVwDu+beChWb5R5s6rfKLCiWcZT5617hqu7T3GdA1hAw@mail.gmail.com --
--
Beena Emerson
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Attachments:
default_range_partition_v11.patchapplication/octet-stream; name=default_range_partition_v11.patchDownload
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index e0b9dec..13ab823 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -127,7 +127,9 @@ static void get_range_key_properties(PartitionKey key, int keynum,
Expr **keyCol,
Const **lower_val, Const **upper_val);
static List *get_qual_for_list(Relation parent, PartitionBoundSpec *spec);
-static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
+ bool for_default);
+static List *get_range_nulltest(PartitionKey key);
static List *generate_partition_qual(Relation rel);
static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index,
@@ -168,11 +170,11 @@ RelationBuildPartitionDesc(Relation rel)
MemoryContext oldcxt;
int ndatums = 0;
+ int default_index = -1;
/* List partitioning specific */
PartitionListValue **all_values = NULL;
int null_index = -1;
- int default_index = -1;
/* Range partitioning specific */
PartitionRangeBound **rbounds = NULL;
@@ -322,21 +324,18 @@ RelationBuildPartitionDesc(Relation rel)
}
else if (key->strategy == PARTITION_STRATEGY_RANGE)
{
- int j,
- k;
+ int k;
PartitionRangeBound **all_bounds,
*prev;
- bool *distinct_indexes;
all_bounds = (PartitionRangeBound **) palloc0(2 * nparts *
sizeof(PartitionRangeBound *));
- distinct_indexes = (bool *) palloc(2 * nparts * sizeof(bool));
/*
* Create a unified list of range bounds across all the
* partitions.
*/
- i = j = 0;
+ i = ndatums = 0;
foreach(cell, boundspecs)
{
PartitionBoundSpec *spec = castNode(PartitionBoundSpec,
@@ -347,30 +346,41 @@ RelationBuildPartitionDesc(Relation rel)
if (spec->strategy != PARTITION_STRATEGY_RANGE)
elog(ERROR, "invalid strategy in partition bound spec");
+ /*
+ * Note the index of the partition bound spec for the default
+ * partition. There's no datum to add to the allbounds array
+ * for this partition.
+ */
+ if (spec->is_default)
+ {
+ default_index = i++;
+ continue;
+ }
+
lower = make_one_range_bound(key, i, spec->lowerdatums,
true);
upper = make_one_range_bound(key, i, spec->upperdatums,
false);
- all_bounds[j] = lower;
- all_bounds[j + 1] = upper;
- j += 2;
+ all_bounds[ndatums++] = lower;
+ all_bounds[ndatums++] = upper;
i++;
}
- Assert(j == 2 * nparts);
+
+ Assert(ndatums == nparts * 2 ||
+ (default_index != -1 && ndatums == (nparts - 1) * 2));
/* Sort all the bounds in ascending order */
- qsort_arg(all_bounds, 2 * nparts,
+ qsort_arg(all_bounds, ndatums,
sizeof(PartitionRangeBound *),
qsort_partition_rbound_cmp,
(void *) key);
- /*
- * Count the number of distinct bounds to allocate an array of
- * that size.
- */
- ndatums = 0;
+ /* Pick out distinct datums and store it in a new array. */
+ rbounds = (PartitionRangeBound **) palloc(ndatums *
+ sizeof(PartitionRangeBound *));
+ k = 0;
prev = NULL;
- for (i = 0; i < 2 * nparts; i++)
+ for (i = 0; i < ndatums; i++)
{
PartitionRangeBound *cur = all_bounds[i];
bool is_distinct = false;
@@ -407,34 +417,17 @@ RelationBuildPartitionDesc(Relation rel)
}
/*
- * Count the current bound if it is distinct from the previous
- * one. Also, store if the index i contains a distinct bound
- * that we'd like put in the relcache array.
+ * If distinct, save into array from where they will be copied
+ * into the relcache.
*/
if (is_distinct)
- {
- distinct_indexes[i] = true;
- ndatums++;
- }
- else
- distinct_indexes[i] = false;
+ rbounds[k++] = all_bounds[i];
prev = cur;
}
- /*
- * Finally save them in an array from where they will be copied
- * into the relcache.
- */
- rbounds = (PartitionRangeBound **) palloc(ndatums *
- sizeof(PartitionRangeBound *));
- k = 0;
- for (i = 0; i < 2 * nparts; i++)
- {
- if (distinct_indexes[i])
- rbounds[k++] = all_bounds[i];
- }
- Assert(k == ndatums);
+ /* Update ndatums to hold the count of distinct datums. */
+ ndatums = k;
}
else
elog(ERROR, "unexpected partition strategy: %d",
@@ -581,6 +574,14 @@ RelationBuildPartitionDesc(Relation rel)
boundinfo->indexes[i] = mapping[orig_index];
}
}
+
+ /* Assign mapped index for the default partition. */
+ if (default_index != -1)
+ {
+ Assert(default_index >= 0 && mapping[default_index] == -1);
+ mapping[default_index] = next_index++;
+ boundinfo->default_index = mapping[default_index];
+ }
boundinfo->indexes[i] = -1;
break;
}
@@ -787,8 +788,10 @@ check_new_partition_bound(char *relname, Relation parent,
int offset;
bool equal;
- Assert(boundinfo && boundinfo->ndatums > 0 &&
- boundinfo->strategy == PARTITION_STRATEGY_RANGE);
+ Assert(boundinfo &&
+ boundinfo->strategy == PARTITION_STRATEGY_RANGE &&
+ (boundinfo->ndatums > 0 ||
+ partition_bound_has_default(boundinfo)));
/*
* Test whether the new lower bound (which is treated
@@ -880,10 +883,10 @@ check_default_allows_bound(Relation parent, Relation default_rel,
List *all_parts;
ListCell *lc;
- /* Currently default partition is supported only for LIST partition. */
- Assert(new_spec->strategy == PARTITION_STRATEGY_LIST);
- new_part_constraints = get_qual_for_list(parent, new_spec);
+ new_part_constraints = (new_spec->strategy == PARTITION_STRATEGY_LIST)
+ ? get_qual_for_list(parent, new_spec)
+ : get_qual_for_range(parent, new_spec, false);
def_part_constraints =
get_default_part_validation_constraint(new_part_constraints);
@@ -1083,7 +1086,7 @@ get_qual_from_partbound(Relation rel, Relation parent,
case PARTITION_STRATEGY_RANGE:
Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
- my_qual = get_qual_for_range(key, spec);
+ my_qual = get_qual_for_range(parent, spec, false);
break;
default:
@@ -1706,6 +1709,53 @@ get_range_key_properties(PartitionKey key, int keynum,
*upper_val = NULL;
}
+ /*
+ * get_range_nulltest
+ *
+ * A non-default range partition table does not currently allow partition
+ * keys to be null, so emit an IS NOT NULL expression for each key column.
+ */
+static List *
+get_range_nulltest(PartitionKey key)
+{
+ List *result = NIL;
+ NullTest *nulltest;
+ ListCell *partexprs_item;
+ int i;
+
+ partexprs_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ Expr *keyCol;
+
+ if (key->partattrs[i] != 0)
+ {
+ keyCol = (Expr *) makeVar(1,
+ key->partattrs[i],
+ key->parttypid[i],
+ key->parttypmod[i],
+ key->parttypcoll[i],
+ 0);
+ }
+ else
+ {
+ if (partexprs_item == NULL)
+ elog(ERROR, "wrong number of partition key expressions");
+ keyCol = copyObject(lfirst(partexprs_item));
+ partexprs_item = lnext(partexprs_item);
+ }
+
+ nulltest = makeNode(NullTest);
+ nulltest->arg = keyCol;
+ nulltest->nulltesttype = IS_NOT_NULL;
+ nulltest->argisrow = false;
+ nulltest->location = -1;
+ result = lappend(result, nulltest);
+ }
+
+ return result;
+}
+
/*
* get_qual_for_range
*
@@ -1744,11 +1794,15 @@ get_range_key_properties(PartitionKey key, int keynum,
* In most common cases with only one partition column, say a, the following
* expression tree will be generated: a IS NOT NULL AND a >= al AND a < au
*
+ * For default partition, it returns the negation of the constraints of all
+ * the other partitions.
+ *
* If we end up with an empty result list, we return a single-member list
* containing a constant TRUE, because callers expect a non-empty list.
*/
static List *
-get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
+get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
+ bool for_default)
{
List *result = NIL;
ListCell *cell1,
@@ -1759,10 +1813,10 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
j;
PartitionRangeDatum *ldatum,
*udatum;
+ PartitionKey key = RelationGetPartitionKey(parent);
Expr *keyCol;
Const *lower_val,
*upper_val;
- NullTest *nulltest;
List *lower_or_arms,
*upper_or_arms;
int num_or_arms,
@@ -1772,44 +1826,68 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
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;
-
- /*
- * A range-partitioned table does not currently allow partition keys to be
- * null, so emit an IS NOT NULL expression for each key column.
- */
- partexprs_item = list_head(key->partexprs);
- for (i = 0; i < key->partnatts; i++)
+ if (spec->is_default)
{
- Expr *keyCol;
+ List *or_expr_args = NIL;
+ PartitionDesc pdesc = RelationGetPartitionDesc(parent);
+ Oid *inhoids = pdesc->oids;
+ int nparts = pdesc->nparts,
+ i;
- if (key->partattrs[i] != 0)
+ for (i = 0; i < nparts; i++)
{
- keyCol = (Expr *) makeVar(1,
- key->partattrs[i],
- key->parttypid[i],
- key->parttypmod[i],
- key->parttypcoll[i],
- 0);
+ Oid inhrelid = inhoids[i];
+ HeapTuple tuple;
+ Datum datum;
+ bool isnull;
+ PartitionBoundSpec *bspec;
+
+ tuple = SearchSysCache1(RELOID, inhrelid);
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", inhrelid);
+
+ datum = SysCacheGetAttr(RELOID, tuple,
+ Anum_pg_class_relpartbound,
+ &isnull);
+
+ Assert(!isnull);
+ bspec = (PartitionBoundSpec *) stringToNode(TextDatumGetCString(datum));
+
+ if (!bspec->is_default)
+ {
+ List *part_qual = get_qual_for_range(parent, bspec, true);
+
+ /*
+ * AND the constraints of the partition and add to
+ * or_expr_args
+ */
+ or_expr_args = lappend(or_expr_args, list_length(part_qual) > 1
+ ? makeBoolExpr(AND_EXPR, part_qual, -1)
+ : linitial(part_qual));
+ }
+ ReleaseSysCache(tuple);
}
- else
+
+ if (or_expr_args != NIL)
{
- if (partexprs_item == NULL)
- elog(ERROR, "wrong number of partition key expressions");
- keyCol = copyObject(lfirst(partexprs_item));
- partexprs_item = lnext(partexprs_item);
+ /* OR all the non-default partition constraints; then negate it */
+ result = lappend(result,
+ list_length(or_expr_args) > 1
+ ? makeBoolExpr(OR_EXPR, or_expr_args, -1)
+ : linitial(or_expr_args));
+ result = list_make1(makeBoolExpr(NOT_EXPR, result, -1));
}
- nulltest = makeNode(NullTest);
- nulltest->arg = keyCol;
- nulltest->nulltesttype = IS_NOT_NULL;
- nulltest->argisrow = false;
- nulltest->location = -1;
- result = lappend(result, nulltest);
+ return result;
}
+ lower_or_start_datum = list_head(spec->lowerdatums);
+ upper_or_start_datum = list_head(spec->upperdatums);
+ num_or_arms = key->partnatts;
+
+ if (!for_default)
+ result = get_range_nulltest(key);
+
/*
* Iterate over the key columns and check if the corresponding lower and
* upper datums are equal using the btree equality operator for the
@@ -2033,7 +2111,9 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
/* As noted above, caller expects the list to be non-empty. */
if (result == NIL)
- result = list_make1(makeBoolConst(true, false));
+ result = for_default
+ ? get_range_nulltest(key)
+ : list_make1(makeBoolConst(true, false));
return result;
}
@@ -2201,8 +2281,7 @@ get_partition_for_tuple(PartitionDispatch *pd,
Datum values[PARTITION_MAX_KEYS];
bool isnull[PARTITION_MAX_KEYS];
int cur_offset,
- cur_index;
- int i,
+ cur_index = -1,
result;
ExprContext *ecxt = GetPerTupleExprContext(estate);
TupleTableSlot *ecxt_scantuple_old = ecxt->ecxt_scantuple;
@@ -2246,24 +2325,6 @@ get_partition_for_tuple(PartitionDispatch *pd,
ecxt->ecxt_scantuple = slot;
FormPartitionKeyDatum(parent, slot, estate, values, isnull);
- if (key->strategy == PARTITION_STRATEGY_RANGE)
- {
- /*
- * Since we cannot route tuples with NULL partition keys through a
- * range-partitioned table, simply return that no partition exists
- */
- for (i = 0; i < key->partnatts; i++)
- {
- if (isnull[i])
- {
- *failed_at = parent;
- *failed_slot = slot;
- result = -1;
- goto error_exit;
- }
- }
- }
-
/*
* If this is a NULL, route it to the null-accepting partition.
* Otherwise, route by searching the array of partition bounds.
@@ -2366,6 +2427,8 @@ make_one_range_bound(PartitionKey key, int index, List *datums, bool lower)
ListCell *lc;
int i;
+ Assert(datums != NULL);
+
bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
bound->index = index;
bound->datums = (Datum *) palloc0(key->partnatts * sizeof(Datum));
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 6830b64..6157613 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3307,11 +3307,6 @@ transformPartitionBound(ParseState *pstate, Relation parent,
if (spec->is_default)
{
- if (strategy != PARTITION_STRATEGY_LIST)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("default partition is supported only for a list partitioned table")));
-
/*
* In case of the default partition, parser had no way to identify the
* partition strategy. Assign the parent strategy to the default
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index e066159..67a5c62 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8700,7 +8700,6 @@ get_rule_expr(Node *node, deparse_context *context,
if (spec->is_default)
{
- Assert(strategy == PARTITION_STRATEGY_LIST);
appendStringInfoString(buf, "DEFAULT");
break;
}
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 798e710..24e34ef 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1900,8 +1900,10 @@ describeOneTableDetails(const char *schemaname,
if (partconstraintdef)
{
- printfPQExpBuffer(&tmpbuf, _("Partition constraint: %s"),
- partconstraintdef);
+ (strcmp(partconstraintdef,"") == 0) ?
+ printfPQExpBuffer(&tmpbuf, _("No partition constraint.")) :
+ printfPQExpBuffer(&tmpbuf, _("Partition constraint: %s"),
+ partconstraintdef);
printTableAddFooter(&cont, tmpbuf.data);
}
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 9a5f6bf..6855d71 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3356,6 +3356,19 @@ CREATE TABLE part2 (
);
ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
INFO: partition constraint for table "part2" is implied by existing constraints
+-- Create default partition
+CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
+-- Only one default partition is allowed, hence, following should give error
+CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS);
+ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT;
+ERROR: partition "partr_def2" conflicts with existing default partition "partr_def1"
+-- Overlapping partitions cannot be attached, hence, following should give error
+INSERT INTO partr_def1 VALUES (2, 10);
+CREATE TABLE part3 (LIKE range_parted);
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (2, 10) TO (2, 20);
+ERROR: updated partition constraint for default partition would be violated by some row
+-- Attaching partitions should be successful when there are no overlapping rows
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (3, 10) TO (3, 20);
-- check that leaf partitions are scanned when attaching a partitioned
-- table
CREATE TABLE part_5 (
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 9615c07..55914e4 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -517,9 +517,6 @@ ERROR: TO must specify exactly one value per partitioning column
-- cannot specify null values in range bounds
CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (maxvalue);
ERROR: cannot specify NULL in range bound
--- range partition cannot have default partition
-CREATE TABLE fail_part PARTITION OF range_parted DEFAULT;
-ERROR: default partition is supported only for a list partitioned table
-- check if compatible with the specified parent
-- cannot create as partition of a non-partitioned table
CREATE TABLE unparted (
@@ -594,6 +591,16 @@ CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30);
ERROR: partition "fail_part" would overlap partition "part2"
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50);
ERROR: partition "fail_part" would overlap partition "part2"
+-- Create a default partition for range partitioned table
+CREATE TABLE range2_default PARTITION OF range_parted2 DEFAULT;
+-- More than one default partition is not allowed, so this should give error
+CREATE TABLE fail_default_part PARTITION OF range_parted2 DEFAULT;
+ERROR: partition "fail_default_part" conflicts with existing default partition "range2_default"
+-- Check if the range for default partitions overlap
+INSERT INTO range_parted2 VALUES (85);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90);
+ERROR: updated partition constraint for default partition "range2_default" would be violated by some row
+CREATE TABLE part4 PARTITION OF range_parted2 FOR VALUES FROM (90) TO (100);
-- now check for multi-column range partition key
CREATE TABLE range_parted3 (
a int,
@@ -607,6 +614,7 @@ CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10)
CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, maxvalue);
CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
ERROR: partition "fail_part" would overlap partition "part12"
+CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
-- cannot create a partition that says column b is allowed to range
-- from -infinity to +infinity, while there exist partitions that have
-- more specific ranges
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 3319f6d..cc28bdc 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -271,6 +271,18 @@ insert into range_parted values ('b', 10);
insert into range_parted values ('a');
ERROR: no partition of relation "range_parted" found for row
DETAIL: Partition key of the failing row contains (a, (b + 0)) = (a, null).
+-- Check default partition
+create table part_def partition of range_parted default;
+-- fail
+insert into part_def values ('b', 10);
+ERROR: new row for relation "part_def" violates partition constraint
+DETAIL: Failing row contains (b, 10).
+-- ok
+insert into part_def values ('c', 10);
+insert into range_parted values (null, null);
+insert into range_parted values ('a', null);
+insert into range_parted values (null, 19);
+insert into range_parted values ('b', 20);
select tableoid::regclass, * from range_parted;
tableoid | a | b
----------+---+----
@@ -280,7 +292,12 @@ select tableoid::regclass, * from range_parted;
part3 | b | 1
part4 | b | 10
part4 | b | 10
-(6 rows)
+ part_def | c | 10
+ part_def | |
+ part_def | a |
+ part_def | | 19
+ part_def | b | 20
+(11 rows)
-- ok
insert into list_parted values (null, 1);
@@ -467,6 +484,36 @@ insert into mlparted5 (a, b, c) values (1, 40, 'a');
ERROR: new row for relation "mlparted5a" violates partition constraint
DETAIL: Failing row contains (b, 1, 40).
drop table mlparted5;
+alter table mlparted drop constraint check_b;
+-- Check multi-level default partition
+create table mlparted_def partition of mlparted default partition by range(a);
+create table mlparted_def1 partition of mlparted_def for values from (40) to (50);
+create table mlparted_def2 partition of mlparted_def for values from (50) to (60);
+insert into mlparted values (40, 100);
+insert into mlparted_def1 values (42, 100);
+insert into mlparted_def2 values (54, 50);
+-- fail
+insert into mlparted values (70, 100);
+ERROR: no partition of relation "mlparted_def" found for row
+DETAIL: Partition key of the failing row contains (a) = (70).
+insert into mlparted_def1 values (52, 50);
+ERROR: new row for relation "mlparted_def1" violates partition constraint
+DETAIL: Failing row contains (52, 50, null).
+insert into mlparted_def2 values (34, 50);
+ERROR: new row for relation "mlparted_def2" violates partition constraint
+DETAIL: Failing row contains (34, 50, null).
+-- ok
+create table mlparted_defd partition of mlparted_def default;
+insert into mlparted values (70, 100);
+select tableoid::regclass, * from mlparted_def;
+ tableoid | a | b | c
+---------------+----+-----+---
+ mlparted_def1 | 40 | 100 |
+ mlparted_def1 | 42 | 100 |
+ mlparted_def2 | 54 | 50 |
+ mlparted_defd | 70 | 100 |
+(4 rows)
+
-- check that message shown after failure to find a partition shows the
-- appropriate key description (or none) in various situations
create table key_desc (a int, b int) partition by list ((a+0));
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 6750152..e996640 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -77,6 +77,10 @@ mlparted12|f
mlparted2|f
mlparted3|f
mlparted4|f
+mlparted_def|f
+mlparted_def1|f
+mlparted_def2|f
+mlparted_defd|f
money_data|f
num_data|f
num_exp_add|t
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index 9912ef2..cfb6aa8 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -218,6 +218,15 @@ ERROR: new row for relation "part_b_10_b_20" violates partition constraint
DETAIL: Failing row contains (b, 9).
-- ok
update range_parted set b = b + 1 where b = 10;
+-- Creating default partition for range
+create table part_def partition of range_parted default;
+insert into range_parted values ('c', 9);
+-- ok
+update part_def set a = 'd' where a = 'c';
+-- fail
+update part_def set a = 'a' where a = 'd';
+ERROR: new row for relation "part_def" violates partition constraint
+DETAIL: Failing row contains (a, 9).
create table list_parted (
a text,
b int
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 935b24b..d4ca7f1 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2181,6 +2181,21 @@ CREATE TABLE part2 (
);
ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
+-- Create default partition
+CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
+
+-- Only one default partition is allowed, hence, following should give error
+CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS);
+ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT;
+
+-- Overlapping partitions cannot be attached, hence, following should give error
+INSERT INTO partr_def1 VALUES (2, 10);
+CREATE TABLE part3 (LIKE range_parted);
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (2, 10) TO (2, 20);
+
+-- Attaching partitions should be successful when there are no overlapping rows
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (3, 10) TO (3, 20);
+
-- check that leaf partitions are scanned when attaching a partitioned
-- table
CREATE TABLE part_5 (
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 1eefa0e..e4fde85 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -486,8 +486,6 @@ CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a') TO ('z',
-- cannot specify null values in range bounds
CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (maxvalue);
--- range partition cannot have default partition
-CREATE TABLE fail_part PARTITION OF range_parted DEFAULT;
-- check if compatible with the specified parent
@@ -554,6 +552,17 @@ CREATE TABLE part3 PARTITION OF range_parted2 FOR VALUES FROM (30) TO (40);
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30);
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50);
+-- Create a default partition for range partitioned table
+CREATE TABLE range2_default PARTITION OF range_parted2 DEFAULT;
+
+-- More than one default partition is not allowed, so this should give error
+CREATE TABLE fail_default_part PARTITION OF range_parted2 DEFAULT;
+
+-- Check if the range for default partitions overlap
+INSERT INTO range_parted2 VALUES (85);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90);
+CREATE TABLE part4 PARTITION OF range_parted2 FOR VALUES FROM (90) TO (100);
+
-- now check for multi-column range partition key
CREATE TABLE range_parted3 (
a int,
@@ -567,6 +576,7 @@ CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES FROM (1, minvalue) TO
CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10);
CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, maxvalue);
CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
+CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
-- cannot create a partition that says column b is allowed to range
-- from -infinity to +infinity, while there exist partitions that have
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 7bf1778..6f5689f 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -168,8 +168,19 @@ insert into range_parted values ('b', 1);
insert into range_parted values ('b', 10);
-- fail (partition key (b+0) is null)
insert into range_parted values ('a');
-select tableoid::regclass, * from range_parted;
+-- Check default partition
+create table part_def partition of range_parted default;
+-- fail
+insert into part_def values ('b', 10);
+-- ok
+insert into part_def values ('c', 10);
+insert into range_parted values (null, null);
+insert into range_parted values ('a', null);
+insert into range_parted values (null, 19);
+insert into range_parted values ('b', 20);
+
+select tableoid::regclass, * from range_parted;
-- ok
insert into list_parted values (null, 1);
insert into list_parted (a) values ('aA');
@@ -299,6 +310,24 @@ create function mlparted5abrtrig_func() returns trigger as $$ begin new.c = 'b';
create trigger mlparted5abrtrig before insert on mlparted5a for each row execute procedure mlparted5abrtrig_func();
insert into mlparted5 (a, b, c) values (1, 40, 'a');
drop table mlparted5;
+alter table mlparted drop constraint check_b;
+
+-- Check multi-level default partition
+create table mlparted_def partition of mlparted default partition by range(a);
+create table mlparted_def1 partition of mlparted_def for values from (40) to (50);
+create table mlparted_def2 partition of mlparted_def for values from (50) to (60);
+insert into mlparted values (40, 100);
+insert into mlparted_def1 values (42, 100);
+insert into mlparted_def2 values (54, 50);
+-- fail
+insert into mlparted values (70, 100);
+insert into mlparted_def1 values (52, 50);
+insert into mlparted_def2 values (34, 50);
+-- ok
+create table mlparted_defd partition of mlparted_def default;
+insert into mlparted values (70, 100);
+
+select tableoid::regclass, * from mlparted_def;
-- check that message shown after failure to find a partition shows the
-- appropriate key description (or none) in various situations
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 44fb0dc..1ce7a94 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -125,6 +125,14 @@ update range_parted set b = b - 1 where b = 10;
-- ok
update range_parted set b = b + 1 where b = 10;
+-- Creating default partition for range
+create table part_def partition of range_parted default;
+insert into range_parted values ('c', 9);
+-- ok
+update part_def set a = 'd' where a = 'c';
+-- fail
+update part_def set a = 'a' where a = 'd';
+
create table list_parted (
a text,
b int
PFA the patch rebased over v25 patches of default list partition [1]/messages/by-id/CAOgcT0NwqnavYtu-QM-DAZ6N=wTiqKgy83WwtO2x94LSLZ1-Sw@mail.gmail.com
[1]: /messages/by-id/CAOgcT0NwqnavYtu-QM-DAZ6N=wTiqKgy83WwtO2x94LSLZ1-Sw@mail.gmail.com
--
Beena Emerson
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Attachments:
default_range_partition_v11_rebase.patchapplication/octet-stream; name=default_range_partition_v11_rebase.patchDownload
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 6e6955a..9fde66b 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -127,7 +127,9 @@ static void get_range_key_properties(PartitionKey key, int keynum,
Expr **keyCol,
Const **lower_val, Const **upper_val);
static List *get_qual_for_list(Relation parent, PartitionBoundSpec *spec);
-static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
+ bool for_default);
+static List *get_range_nulltest(PartitionKey key);
static List *generate_partition_qual(Relation rel);
static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index,
@@ -168,11 +170,11 @@ RelationBuildPartitionDesc(Relation rel)
MemoryContext oldcxt;
int ndatums = 0;
+ int default_index = -1;
/* List partitioning specific */
PartitionListValue **all_values = NULL;
int null_index = -1;
- int default_index = -1;
/* Range partitioning specific */
PartitionRangeBound **rbounds = NULL;
@@ -335,21 +337,18 @@ RelationBuildPartitionDesc(Relation rel)
}
else if (key->strategy == PARTITION_STRATEGY_RANGE)
{
- int j,
- k;
+ int k;
PartitionRangeBound **all_bounds,
*prev;
- bool *distinct_indexes;
all_bounds = (PartitionRangeBound **) palloc0(2 * nparts *
sizeof(PartitionRangeBound *));
- distinct_indexes = (bool *) palloc(2 * nparts * sizeof(bool));
/*
* Create a unified list of range bounds across all the
* partitions.
*/
- i = j = 0;
+ i = ndatums = 0;
foreach(cell, boundspecs)
{
PartitionBoundSpec *spec = castNode(PartitionBoundSpec,
@@ -360,30 +359,41 @@ RelationBuildPartitionDesc(Relation rel)
if (spec->strategy != PARTITION_STRATEGY_RANGE)
elog(ERROR, "invalid strategy in partition bound spec");
+ /*
+ * Note the index of the partition bound spec for the default
+ * partition. There's no datum to add to the allbounds array
+ * for this partition.
+ */
+ if (spec->is_default)
+ {
+ default_index = i++;
+ continue;
+ }
+
lower = make_one_range_bound(key, i, spec->lowerdatums,
true);
upper = make_one_range_bound(key, i, spec->upperdatums,
false);
- all_bounds[j] = lower;
- all_bounds[j + 1] = upper;
- j += 2;
+ all_bounds[ndatums++] = lower;
+ all_bounds[ndatums++] = upper;
i++;
}
- Assert(j == 2 * nparts);
+
+ Assert(ndatums == nparts * 2 ||
+ (default_index != -1 && ndatums == (nparts - 1) * 2));
/* Sort all the bounds in ascending order */
- qsort_arg(all_bounds, 2 * nparts,
+ qsort_arg(all_bounds, ndatums,
sizeof(PartitionRangeBound *),
qsort_partition_rbound_cmp,
(void *) key);
- /*
- * Count the number of distinct bounds to allocate an array of
- * that size.
- */
- ndatums = 0;
+ /* Pick out distinct datums and store it in a new array. */
+ rbounds = (PartitionRangeBound **) palloc(ndatums *
+ sizeof(PartitionRangeBound *));
+ k = 0;
prev = NULL;
- for (i = 0; i < 2 * nparts; i++)
+ for (i = 0; i < ndatums; i++)
{
PartitionRangeBound *cur = all_bounds[i];
bool is_distinct = false;
@@ -420,34 +430,17 @@ RelationBuildPartitionDesc(Relation rel)
}
/*
- * Count the current bound if it is distinct from the previous
- * one. Also, store if the index i contains a distinct bound
- * that we'd like put in the relcache array.
+ * If distinct, save into array from where they will be copied
+ * into the relcache.
*/
if (is_distinct)
- {
- distinct_indexes[i] = true;
- ndatums++;
- }
- else
- distinct_indexes[i] = false;
+ rbounds[k++] = all_bounds[i];
prev = cur;
}
- /*
- * Finally save them in an array from where they will be copied
- * into the relcache.
- */
- rbounds = (PartitionRangeBound **) palloc(ndatums *
- sizeof(PartitionRangeBound *));
- k = 0;
- for (i = 0; i < 2 * nparts; i++)
- {
- if (distinct_indexes[i])
- rbounds[k++] = all_bounds[i];
- }
- Assert(k == ndatums);
+ /* Update ndatums to hold the count of distinct datums. */
+ ndatums = k;
}
else
elog(ERROR, "unexpected partition strategy: %d",
@@ -595,6 +588,14 @@ RelationBuildPartitionDesc(Relation rel)
boundinfo->indexes[i] = mapping[orig_index];
}
}
+
+ /* Assign mapped index for the default partition. */
+ if (default_index != -1)
+ {
+ Assert(default_index >= 0 && mapping[default_index] == -1);
+ mapping[default_index] = next_index++;
+ boundinfo->default_index = mapping[default_index];
+ }
boundinfo->indexes[i] = -1;
break;
}
@@ -803,8 +804,10 @@ check_new_partition_bound(char *relname, Relation parent,
int offset;
bool equal;
- Assert(boundinfo && boundinfo->ndatums > 0 &&
- boundinfo->strategy == PARTITION_STRATEGY_RANGE);
+ Assert(boundinfo &&
+ boundinfo->strategy == PARTITION_STRATEGY_RANGE &&
+ (boundinfo->ndatums > 0 ||
+ partition_bound_has_default(boundinfo)));
/*
* Test whether the new lower bound (which is treated
@@ -896,10 +899,10 @@ check_default_allows_bound(Relation parent, Relation default_rel,
List *all_parts;
ListCell *lc;
- /* Currently default partition is supported only for LIST partition. */
- Assert(new_spec->strategy == PARTITION_STRATEGY_LIST);
- new_part_constraints = get_qual_for_list(parent, new_spec);
+ new_part_constraints = (new_spec->strategy == PARTITION_STRATEGY_LIST)
+ ? get_qual_for_list(parent, new_spec)
+ : get_qual_for_range(parent, new_spec, false);
def_part_constraints =
get_default_part_validation_constraint(new_part_constraints);
@@ -1102,7 +1105,7 @@ get_qual_from_partbound(Relation rel, Relation parent,
case PARTITION_STRATEGY_RANGE:
Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
- my_qual = get_qual_for_range(key, spec);
+ my_qual = get_qual_for_range(parent, spec, false);
break;
default:
@@ -1730,6 +1733,53 @@ get_range_key_properties(PartitionKey key, int keynum,
*upper_val = NULL;
}
+ /*
+ * get_range_nulltest
+ *
+ * A non-default range partition table does not currently allow partition
+ * keys to be null, so emit an IS NOT NULL expression for each key column.
+ */
+static List *
+get_range_nulltest(PartitionKey key)
+{
+ List *result = NIL;
+ NullTest *nulltest;
+ ListCell *partexprs_item;
+ int i;
+
+ partexprs_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ Expr *keyCol;
+
+ if (key->partattrs[i] != 0)
+ {
+ keyCol = (Expr *) makeVar(1,
+ key->partattrs[i],
+ key->parttypid[i],
+ key->parttypmod[i],
+ key->parttypcoll[i],
+ 0);
+ }
+ else
+ {
+ if (partexprs_item == NULL)
+ elog(ERROR, "wrong number of partition key expressions");
+ keyCol = copyObject(lfirst(partexprs_item));
+ partexprs_item = lnext(partexprs_item);
+ }
+
+ nulltest = makeNode(NullTest);
+ nulltest->arg = keyCol;
+ nulltest->nulltesttype = IS_NOT_NULL;
+ nulltest->argisrow = false;
+ nulltest->location = -1;
+ result = lappend(result, nulltest);
+ }
+
+ return result;
+}
+
/*
* get_qual_for_range
*
@@ -1768,11 +1818,14 @@ get_range_key_properties(PartitionKey key, int keynum,
* In most common cases with only one partition column, say a, the following
* expression tree will be generated: a IS NOT NULL AND a >= al AND a < au
*
- * If we end up with an empty result list, we return a single-member list
- * containing a constant TRUE, because callers expect a non-empty list.
+ * For default partition, it returns the negation of the constraints of all
+ * the other partitions.
+ *
+ * If we end up with an empty result list, we return NULL.
*/
static List *
-get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
+get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
+ bool for_default)
{
List *result = NIL;
ListCell *cell1,
@@ -1783,10 +1836,10 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
j;
PartitionRangeDatum *ldatum,
*udatum;
+ PartitionKey key = RelationGetPartitionKey(parent);
Expr *keyCol;
Const *lower_val,
*upper_val;
- NullTest *nulltest;
List *lower_or_arms,
*upper_or_arms;
int num_or_arms,
@@ -1796,44 +1849,68 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
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;
-
- /*
- * A range-partitioned table does not currently allow partition keys to be
- * null, so emit an IS NOT NULL expression for each key column.
- */
- partexprs_item = list_head(key->partexprs);
- for (i = 0; i < key->partnatts; i++)
+ if (spec->is_default)
{
- Expr *keyCol;
+ List *or_expr_args = NIL;
+ PartitionDesc pdesc = RelationGetPartitionDesc(parent);
+ Oid *inhoids = pdesc->oids;
+ int nparts = pdesc->nparts,
+ i;
- if (key->partattrs[i] != 0)
+ for (i = 0; i < nparts; i++)
{
- keyCol = (Expr *) makeVar(1,
- key->partattrs[i],
- key->parttypid[i],
- key->parttypmod[i],
- key->parttypcoll[i],
- 0);
+ Oid inhrelid = inhoids[i];
+ HeapTuple tuple;
+ Datum datum;
+ bool isnull;
+ PartitionBoundSpec *bspec;
+
+ tuple = SearchSysCache1(RELOID, inhrelid);
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", inhrelid);
+
+ datum = SysCacheGetAttr(RELOID, tuple,
+ Anum_pg_class_relpartbound,
+ &isnull);
+
+ Assert(!isnull);
+ bspec = (PartitionBoundSpec *) stringToNode(TextDatumGetCString(datum));
+
+ if (!bspec->is_default)
+ {
+ List *part_qual = get_qual_for_range(parent, bspec, true);
+
+ /*
+ * AND the constraints of the partition and add to
+ * or_expr_args
+ */
+ or_expr_args = lappend(or_expr_args, list_length(part_qual) > 1
+ ? makeBoolExpr(AND_EXPR, part_qual, -1)
+ : linitial(part_qual));
+ }
+ ReleaseSysCache(tuple);
}
- else
+
+ if (or_expr_args != NIL)
{
- if (partexprs_item == NULL)
- elog(ERROR, "wrong number of partition key expressions");
- keyCol = copyObject(lfirst(partexprs_item));
- partexprs_item = lnext(partexprs_item);
+ /* OR all the non-default partition constraints; then negate it */
+ result = lappend(result,
+ list_length(or_expr_args) > 1
+ ? makeBoolExpr(OR_EXPR, or_expr_args, -1)
+ : linitial(or_expr_args));
+ result = list_make1(makeBoolExpr(NOT_EXPR, result, -1));
}
- nulltest = makeNode(NullTest);
- nulltest->arg = keyCol;
- nulltest->nulltesttype = IS_NOT_NULL;
- nulltest->argisrow = false;
- nulltest->location = -1;
- result = lappend(result, nulltest);
+ return result;
}
+ lower_or_start_datum = list_head(spec->lowerdatums);
+ upper_or_start_datum = list_head(spec->upperdatums);
+ num_or_arms = key->partnatts;
+
+ if (!for_default)
+ result = get_range_nulltest(key);
+
/*
* Iterate over the key columns and check if the corresponding lower and
* upper datums are equal using the btree equality operator for the
@@ -2057,7 +2134,9 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
/* As noted above, caller expects the list to be non-empty. */
if (result == NIL)
- result = list_make1(makeBoolConst(true, false));
+ result = for_default
+ ? get_range_nulltest(key)
+ : list_make1(makeBoolConst(true, false));
return result;
}
@@ -2230,8 +2309,7 @@ get_partition_for_tuple(PartitionDispatch *pd,
Datum values[PARTITION_MAX_KEYS];
bool isnull[PARTITION_MAX_KEYS];
int cur_offset,
- cur_index;
- int i,
+ cur_index = -1,
result;
ExprContext *ecxt = GetPerTupleExprContext(estate);
TupleTableSlot *ecxt_scantuple_old = ecxt->ecxt_scantuple;
@@ -2275,24 +2353,6 @@ get_partition_for_tuple(PartitionDispatch *pd,
ecxt->ecxt_scantuple = slot;
FormPartitionKeyDatum(parent, slot, estate, values, isnull);
- if (key->strategy == PARTITION_STRATEGY_RANGE)
- {
- /*
- * Since we cannot route tuples with NULL partition keys through a
- * range-partitioned table, simply return that no partition exists
- */
- for (i = 0; i < key->partnatts; i++)
- {
- if (isnull[i])
- {
- *failed_at = parent;
- *failed_slot = slot;
- result = -1;
- goto error_exit;
- }
- }
- }
-
/*
* If this is a null partition key, route it to the null-accepting
* partition. Otherwise, route by searching the array of partition
@@ -2396,6 +2456,8 @@ make_one_range_bound(PartitionKey key, int index, List *datums, bool lower)
ListCell *lc;
int i;
+ Assert(datums != NULL);
+
bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
bound->index = index;
bound->datums = (Datum *) palloc0(key->partnatts * sizeof(Datum));
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 2816c9f..4e52f65 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3308,11 +3308,6 @@ transformPartitionBound(ParseState *pstate, Relation parent,
if (spec->is_default)
{
- if (strategy != PARTITION_STRATEGY_LIST)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("default partition is supported only for a list partitioned table")));
-
/*
* In case of the default partition, parser had no way to identify the
* partition strategy. Assign the parent's strategy to the default
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index fc4854ba..6f24f3b 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8700,7 +8700,6 @@ get_rule_expr(Node *node, deparse_context *context,
if (spec->is_default)
{
- Assert(strategy == PARTITION_STRATEGY_LIST);
appendStringInfoString(buf, "DEFAULT");
break;
}
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index bef8d55..cc62edd 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3359,6 +3359,19 @@ CREATE TABLE part2 (
);
ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
INFO: partition constraint for table "part2" is implied by existing constraints
+-- Create default partition
+CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
+-- Only one default partition is allowed, hence, following should give error
+CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS);
+ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT;
+ERROR: partition "partr_def2" conflicts with existing default partition "partr_def1"
+-- Overlapping partitions cannot be attached, hence, following should give error
+INSERT INTO partr_def1 VALUES (2, 10);
+CREATE TABLE part3 (LIKE range_parted);
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (2, 10) TO (2, 20);
+ERROR: updated partition constraint for default partition would be violated by some row
+-- Attaching partitions should be successful when there are no overlapping rows
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (3, 10) TO (3, 20);
-- check that leaf partitions are scanned when attaching a partitioned
-- table
CREATE TABLE part_5 (
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index bf0acad..58c755b 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -594,6 +594,16 @@ CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30);
ERROR: partition "fail_part" would overlap partition "part2"
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50);
ERROR: partition "fail_part" would overlap partition "part2"
+-- Create a default partition for range partitioned table
+CREATE TABLE range2_default PARTITION OF range_parted2 DEFAULT;
+-- More than one default partition is not allowed, so this should give error
+CREATE TABLE fail_default_part PARTITION OF range_parted2 DEFAULT;
+ERROR: partition "fail_default_part" conflicts with existing default partition "range2_default"
+-- Check if the range for default partitions overlap
+INSERT INTO range_parted2 VALUES (85);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90);
+ERROR: updated partition constraint for default partition "range2_default" would be violated by some row
+CREATE TABLE part4 PARTITION OF range_parted2 FOR VALUES FROM (90) TO (100);
-- now check for multi-column range partition key
CREATE TABLE range_parted3 (
a int,
@@ -607,6 +617,7 @@ CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10)
CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, maxvalue);
CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
ERROR: partition "fail_part" would overlap partition "part12"
+CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
-- cannot create a partition that says column b is allowed to range
-- from -infinity to +infinity, while there exist partitions that have
-- more specific ranges
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 5ca6e5e..2022584 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -295,6 +295,18 @@ insert into range_parted values ('b', 10);
insert into range_parted values ('a');
ERROR: no partition of relation "range_parted" found for row
DETAIL: Partition key of the failing row contains (a, (b + 0)) = (a, null).
+-- Check default partition
+create table part_def partition of range_parted default;
+-- fail
+insert into part_def values ('b', 10);
+ERROR: new row for relation "part_def" violates partition constraint
+DETAIL: Failing row contains (b, 10).
+-- ok
+insert into part_def values ('c', 10);
+insert into range_parted values (null, null);
+insert into range_parted values ('a', null);
+insert into range_parted values (null, 19);
+insert into range_parted values ('b', 20);
select tableoid::regclass, * from range_parted;
tableoid | a | b
----------+---+----
@@ -304,7 +316,12 @@ select tableoid::regclass, * from range_parted;
part3 | b | 1
part4 | b | 10
part4 | b | 10
-(6 rows)
+ part_def | c | 10
+ part_def | |
+ part_def | a |
+ part_def | | 19
+ part_def | b | 20
+(11 rows)
-- ok
insert into list_parted values (null, 1);
@@ -493,6 +510,36 @@ insert into mlparted5 (a, b, c) values (1, 40, 'a');
ERROR: new row for relation "mlparted5a" violates partition constraint
DETAIL: Failing row contains (b, 1, 40).
drop table mlparted5;
+alter table mlparted drop constraint check_b;
+-- Check multi-level default partition
+create table mlparted_def partition of mlparted default partition by range(a);
+create table mlparted_def1 partition of mlparted_def for values from (40) to (50);
+create table mlparted_def2 partition of mlparted_def for values from (50) to (60);
+insert into mlparted values (40, 100);
+insert into mlparted_def1 values (42, 100);
+insert into mlparted_def2 values (54, 50);
+-- fail
+insert into mlparted values (70, 100);
+ERROR: no partition of relation "mlparted_def" found for row
+DETAIL: Partition key of the failing row contains (a) = (70).
+insert into mlparted_def1 values (52, 50);
+ERROR: new row for relation "mlparted_def1" violates partition constraint
+DETAIL: Failing row contains (52, 50, null).
+insert into mlparted_def2 values (34, 50);
+ERROR: new row for relation "mlparted_def2" violates partition constraint
+DETAIL: Failing row contains (34, 50, null).
+-- ok
+create table mlparted_defd partition of mlparted_def default;
+insert into mlparted values (70, 100);
+select tableoid::regclass, * from mlparted_def;
+ tableoid | a | b | c
+---------------+----+-----+---
+ mlparted_def1 | 40 | 100 |
+ mlparted_def1 | 42 | 100 |
+ mlparted_def2 | 54 | 50 |
+ mlparted_defd | 70 | 100 |
+(4 rows)
+
-- check that message shown after failure to find a partition shows the
-- appropriate key description (or none) in various situations
create table key_desc (a int, b int) partition by list ((a+0));
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 6750152..e996640 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -77,6 +77,10 @@ mlparted12|f
mlparted2|f
mlparted3|f
mlparted4|f
+mlparted_def|f
+mlparted_def1|f
+mlparted_def2|f
+mlparted_defd|f
money_data|f
num_data|f
num_exp_add|t
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index 9912ef2..cfb6aa8 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -218,6 +218,15 @@ ERROR: new row for relation "part_b_10_b_20" violates partition constraint
DETAIL: Failing row contains (b, 9).
-- ok
update range_parted set b = b + 1 where b = 10;
+-- Creating default partition for range
+create table part_def partition of range_parted default;
+insert into range_parted values ('c', 9);
+-- ok
+update part_def set a = 'd' where a = 'c';
+-- fail
+update part_def set a = 'a' where a = 'd';
+ERROR: new row for relation "part_def" violates partition constraint
+DETAIL: Failing row contains (a, 9).
create table list_parted (
a text,
b int
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 92a93eb..ccb2f26 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2184,6 +2184,21 @@ CREATE TABLE part2 (
);
ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
+-- Create default partition
+CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
+
+-- Only one default partition is allowed, hence, following should give error
+CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS);
+ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT;
+
+-- Overlapping partitions cannot be attached, hence, following should give error
+INSERT INTO partr_def1 VALUES (2, 10);
+CREATE TABLE part3 (LIKE range_parted);
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (2, 10) TO (2, 20);
+
+-- Attaching partitions should be successful when there are no overlapping rows
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (3, 10) TO (3, 20);
+
-- check that leaf partitions are scanned when attaching a partitioned
-- table
CREATE TABLE part_5 (
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 07d653a..eeab5d9 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -554,6 +554,17 @@ CREATE TABLE part3 PARTITION OF range_parted2 FOR VALUES FROM (30) TO (40);
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30);
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50);
+-- Create a default partition for range partitioned table
+CREATE TABLE range2_default PARTITION OF range_parted2 DEFAULT;
+
+-- More than one default partition is not allowed, so this should give error
+CREATE TABLE fail_default_part PARTITION OF range_parted2 DEFAULT;
+
+-- Check if the range for default partitions overlap
+INSERT INTO range_parted2 VALUES (85);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90);
+CREATE TABLE part4 PARTITION OF range_parted2 FOR VALUES FROM (90) TO (100);
+
-- now check for multi-column range partition key
CREATE TABLE range_parted3 (
a int,
@@ -567,6 +578,7 @@ CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES FROM (1, minvalue) TO
CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10);
CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, maxvalue);
CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
+CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
-- cannot create a partition that says column b is allowed to range
-- from -infinity to +infinity, while there exist partitions that have
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 7bd0619..adb31d4 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -180,8 +180,19 @@ insert into range_parted values ('b', 1);
insert into range_parted values ('b', 10);
-- fail (partition key (b+0) is null)
insert into range_parted values ('a');
-select tableoid::regclass, * from range_parted;
+-- Check default partition
+create table part_def partition of range_parted default;
+-- fail
+insert into part_def values ('b', 10);
+-- ok
+insert into part_def values ('c', 10);
+insert into range_parted values (null, null);
+insert into range_parted values ('a', null);
+insert into range_parted values (null, 19);
+insert into range_parted values ('b', 20);
+
+select tableoid::regclass, * from range_parted;
-- ok
insert into list_parted values (null, 1);
insert into list_parted (a) values ('aA');
@@ -311,6 +322,24 @@ create function mlparted5abrtrig_func() returns trigger as $$ begin new.c = 'b';
create trigger mlparted5abrtrig before insert on mlparted5a for each row execute procedure mlparted5abrtrig_func();
insert into mlparted5 (a, b, c) values (1, 40, 'a');
drop table mlparted5;
+alter table mlparted drop constraint check_b;
+
+-- Check multi-level default partition
+create table mlparted_def partition of mlparted default partition by range(a);
+create table mlparted_def1 partition of mlparted_def for values from (40) to (50);
+create table mlparted_def2 partition of mlparted_def for values from (50) to (60);
+insert into mlparted values (40, 100);
+insert into mlparted_def1 values (42, 100);
+insert into mlparted_def2 values (54, 50);
+-- fail
+insert into mlparted values (70, 100);
+insert into mlparted_def1 values (52, 50);
+insert into mlparted_def2 values (34, 50);
+-- ok
+create table mlparted_defd partition of mlparted_def default;
+insert into mlparted values (70, 100);
+
+select tableoid::regclass, * from mlparted_def;
-- check that message shown after failure to find a partition shows the
-- appropriate key description (or none) in various situations
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 44fb0dc..1ce7a94 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -125,6 +125,14 @@ update range_parted set b = b - 1 where b = 10;
-- ok
update range_parted set b = b + 1 where b = 10;
+-- Creating default partition for range
+create table part_def partition of range_parted default;
+insert into range_parted values ('c', 9);
+-- ok
+update part_def set a = 'd' where a = 'c';
+-- fail
+update part_def set a = 'a' where a = 'd';
+
create table list_parted (
a text,
b int
Hi Beena,
On Thu, Aug 17, 2017 at 4:59 PM, Beena Emerson <memissemerson@gmail.com>
wrote:
PFA the patch rebased over v25 patches of default list partition [1]
Thanks for rebasing.
Range partition review:
1.
There are lot of changes in RelationBuildPartitionDesc(). It was hard to
understand why these changes are needed for default partition. I did not
find
any explanation for the same, may be I am missing some discussion? Only
when I looked into it carefully I could understand that these changes are
nothing but optimization for identifying the distinct bounds.
I think it is better to keep the patch for code optimisation/simplification
in a
separate patch. I have separated the patch(0001) in attached patches for
this
purpose.
2.
+ * For default partition, it returns the negation of the constraints of all
+ * the other partitions.
+ *
+ * If we end up with an empty result list, we return NULL.
We can rephrase the comment as below:
"For default partition, function returns the negation of the constraints of
all
the other partitions. If default is the only partition then returns NULL."
3.
@@ -2396,6 +2456,8 @@ make_one_range_bound(PartitionKey key, int index,
List *datums, bool lower)
ListCell *lc;
int i;
+ Assert(datums != NULL);
+
bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
bound->index = index;
bound->datums = (Datum *) palloc0(key->partnatts * sizeof(Datum));
I am not really convinced, why should we have above Assert.
4.
static List *
-get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
+get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
+ bool for_default)
{
The addition and the way flag ‘for_default’ is being used is very confusing.
At this moment I am not able to think about a alternate solution to the
way you have used this flag. But will try and see if I can come up with
an alternate approach.
I still need to look at the test, and need to do some manual testing. Will
update here with any findings.
Patches:
0001: Refactoring/simplification of code in RelationBuildPartitionDesc(),
0002: implementation of default range partition by Beena.
Regards,
Jeevan
Attachments:
0001-Refactor-RelationBuildPartitionDesc.patchapplication/octet-stream; name=0001-Refactor-RelationBuildPartitionDesc.patchDownload
From 761e7cde7e93da0030a879925e75c89ff1099e9c Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Tue, 22 Aug 2017 07:40:55 +0530
Subject: [PATCH 1/2] Refactor RelationBuildPartitionDesc().
This patch simplifies RelationBuildPartitionDesc() to optimize the
way we find out the disctinct bounds in case of range partitioned
table.
Beena Emerson, revised by Jeevan Ladhe.
---
src/backend/catalog/partition.c | 53 +++++++++++++----------------------------
1 file changed, 16 insertions(+), 37 deletions(-)
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 06444f4931..0695f86f88 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -335,21 +335,18 @@ RelationBuildPartitionDesc(Relation rel)
}
else if (key->strategy == PARTITION_STRATEGY_RANGE)
{
- int j,
- k;
+ int k;
PartitionRangeBound **all_bounds,
*prev;
- bool *distinct_indexes;
all_bounds = (PartitionRangeBound **) palloc0(2 * nparts *
sizeof(PartitionRangeBound *));
- distinct_indexes = (bool *) palloc(2 * nparts * sizeof(bool));
/*
* Create a unified list of range bounds across all the
* partitions.
*/
- i = j = 0;
+ i = ndatums = 0;
foreach(cell, boundspecs)
{
PartitionBoundSpec *spec = castNode(PartitionBoundSpec,
@@ -364,12 +361,12 @@ RelationBuildPartitionDesc(Relation rel)
true);
upper = make_one_range_bound(key, i, spec->upperdatums,
false);
- all_bounds[j] = lower;
- all_bounds[j + 1] = upper;
- j += 2;
+ all_bounds[ndatums++] = lower;
+ all_bounds[ndatums++] = upper;
i++;
}
- Assert(j == 2 * nparts);
+
+ Assert(ndatums == nparts * 2);
/* Sort all the bounds in ascending order */
qsort_arg(all_bounds, 2 * nparts,
@@ -377,13 +374,11 @@ RelationBuildPartitionDesc(Relation rel)
qsort_partition_rbound_cmp,
(void *) key);
- /*
- * Count the number of distinct bounds to allocate an array of
- * that size.
- */
- ndatums = 0;
+ rbounds = (PartitionRangeBound **) palloc(ndatums *
+ sizeof(PartitionRangeBound *));
+ k = 0;
prev = NULL;
- for (i = 0; i < 2 * nparts; i++)
+ for (i = 0; i < ndatums; i++)
{
PartitionRangeBound *cur = all_bounds[i];
bool is_distinct = false;
@@ -420,34 +415,18 @@ RelationBuildPartitionDesc(Relation rel)
}
/*
- * Count the current bound if it is distinct from the previous
- * one. Also, store if the index i contains a distinct bound
- * that we'd like put in the relcache array.
+ * Only if the bound is distinct save it into a temporary
+ * array i.e. rbounds which is later copied into boundinfo
+ * datums array.
*/
if (is_distinct)
- {
- distinct_indexes[i] = true;
- ndatums++;
- }
- else
- distinct_indexes[i] = false;
+ rbounds[k++] = all_bounds[i];
prev = cur;
}
- /*
- * Finally save them in an array from where they will be copied
- * into the relcache.
- */
- rbounds = (PartitionRangeBound **) palloc(ndatums *
- sizeof(PartitionRangeBound *));
- k = 0;
- for (i = 0; i < 2 * nparts; i++)
- {
- if (distinct_indexes[i])
- rbounds[k++] = all_bounds[i];
- }
- Assert(k == ndatums);
+ /* Update ndatums to hold the count of distinct datums. */
+ ndatums = k;
}
else
elog(ERROR, "unexpected partition strategy: %d",
--
2.13.3
0002-Add-support-for-default-partition-for-range.patchapplication/octet-stream; name=0002-Add-support-for-default-partition-for-range.patchDownload
From bfc2cf6f42cd7dee186c6c320ca916742720ecf8 Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Tue, 22 Aug 2017 07:42:24 +0530
Subject: [PATCH 2/2] Add support for default partition for range.
This patch implements the default partition for range
partitioned table. This patch needs to be appilied on
top of the other patches in default partition v25.
Beena Emerson.
---
src/backend/catalog/partition.c | 211 ++++++++++++++++++++---------
src/backend/parser/parse_utilcmd.c | 5 -
src/backend/utils/adt/ruleutils.c | 1 -
src/test/regress/expected/alter_table.out | 13 ++
src/test/regress/expected/create_table.out | 11 ++
src/test/regress/expected/insert.out | 49 ++++++-
src/test/regress/expected/sanity_check.out | 4 +
src/test/regress/expected/update.out | 9 ++
src/test/regress/sql/alter_table.sql | 15 ++
src/test/regress/sql/create_table.sql | 12 ++
src/test/regress/sql/insert.sql | 31 ++++-
src/test/regress/sql/update.sql | 8 ++
12 files changed, 297 insertions(+), 72 deletions(-)
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 0695f86f88..653c9c3961 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -127,7 +127,9 @@ static void get_range_key_properties(PartitionKey key, int keynum,
Expr **keyCol,
Const **lower_val, Const **upper_val);
static List *get_qual_for_list(Relation parent, PartitionBoundSpec *spec);
-static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
+ bool for_default);
+static List *get_range_nulltest(PartitionKey key);
static List *generate_partition_qual(Relation rel);
static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index,
@@ -168,11 +170,11 @@ RelationBuildPartitionDesc(Relation rel)
MemoryContext oldcxt;
int ndatums = 0;
+ int default_index = -1;
/* List partitioning specific */
PartitionListValue **all_values = NULL;
int null_index = -1;
- int default_index = -1;
/* Range partitioning specific */
PartitionRangeBound **rbounds = NULL;
@@ -357,6 +359,17 @@ RelationBuildPartitionDesc(Relation rel)
if (spec->strategy != PARTITION_STRATEGY_RANGE)
elog(ERROR, "invalid strategy in partition bound spec");
+ /*
+ * Note the index of the partition bound spec for the default
+ * partition. There's no datum to add to the allbounds array
+ * for this partition.
+ */
+ if (spec->is_default)
+ {
+ default_index = i++;
+ continue;
+ }
+
lower = make_one_range_bound(key, i, spec->lowerdatums,
true);
upper = make_one_range_bound(key, i, spec->upperdatums,
@@ -366,10 +379,11 @@ RelationBuildPartitionDesc(Relation rel)
i++;
}
- Assert(ndatums == nparts * 2);
+ Assert(ndatums == nparts * 2 ||
+ (default_index != -1 && ndatums == (nparts - 1) * 2));
/* Sort all the bounds in ascending order */
- qsort_arg(all_bounds, 2 * nparts,
+ qsort_arg(all_bounds, ndatums,
sizeof(PartitionRangeBound *),
qsort_partition_rbound_cmp,
(void *) key);
@@ -574,6 +588,14 @@ RelationBuildPartitionDesc(Relation rel)
boundinfo->indexes[i] = mapping[orig_index];
}
}
+
+ /* Assign mapped index for the default partition. */
+ if (default_index != -1)
+ {
+ Assert(default_index >= 0 && mapping[default_index] == -1);
+ mapping[default_index] = next_index++;
+ boundinfo->default_index = mapping[default_index];
+ }
boundinfo->indexes[i] = -1;
break;
}
@@ -782,8 +804,10 @@ check_new_partition_bound(char *relname, Relation parent,
int offset;
bool equal;
- Assert(boundinfo && boundinfo->ndatums > 0 &&
- boundinfo->strategy == PARTITION_STRATEGY_RANGE);
+ Assert(boundinfo &&
+ boundinfo->strategy == PARTITION_STRATEGY_RANGE &&
+ (boundinfo->ndatums > 0 ||
+ partition_bound_has_default(boundinfo)));
/*
* Test whether the new lower bound (which is treated
@@ -875,10 +899,10 @@ check_default_allows_bound(Relation parent, Relation default_rel,
List *all_parts;
ListCell *lc;
- /* Currently default partition is supported only for LIST partition. */
- Assert(new_spec->strategy == PARTITION_STRATEGY_LIST);
- new_part_constraints = get_qual_for_list(parent, new_spec);
+ new_part_constraints = (new_spec->strategy == PARTITION_STRATEGY_LIST)
+ ? get_qual_for_list(parent, new_spec)
+ : get_qual_for_range(parent, new_spec, false);
def_part_constraints =
get_default_part_validation_constraint(new_part_constraints);
@@ -1081,7 +1105,7 @@ get_qual_from_partbound(Relation rel, Relation parent,
case PARTITION_STRATEGY_RANGE:
Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
- my_qual = get_qual_for_range(key, spec);
+ my_qual = get_qual_for_range(parent, spec, false);
break;
default:
@@ -1712,6 +1736,53 @@ get_range_key_properties(PartitionKey key, int keynum,
*upper_val = NULL;
}
+ /*
+ * get_range_nulltest
+ *
+ * A non-default range partition table does not currently allow partition
+ * keys to be null, so emit an IS NOT NULL expression for each key column.
+ */
+static List *
+get_range_nulltest(PartitionKey key)
+{
+ List *result = NIL;
+ NullTest *nulltest;
+ ListCell *partexprs_item;
+ int i;
+
+ partexprs_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ Expr *keyCol;
+
+ if (key->partattrs[i] != 0)
+ {
+ keyCol = (Expr *) makeVar(1,
+ key->partattrs[i],
+ key->parttypid[i],
+ key->parttypmod[i],
+ key->parttypcoll[i],
+ 0);
+ }
+ else
+ {
+ if (partexprs_item == NULL)
+ elog(ERROR, "wrong number of partition key expressions");
+ keyCol = copyObject(lfirst(partexprs_item));
+ partexprs_item = lnext(partexprs_item);
+ }
+
+ nulltest = makeNode(NullTest);
+ nulltest->arg = keyCol;
+ nulltest->nulltesttype = IS_NOT_NULL;
+ nulltest->argisrow = false;
+ nulltest->location = -1;
+ result = lappend(result, nulltest);
+ }
+
+ return result;
+}
+
/*
* get_qual_for_range
*
@@ -1750,11 +1821,14 @@ get_range_key_properties(PartitionKey key, int keynum,
* In most common cases with only one partition column, say a, the following
* expression tree will be generated: a IS NOT NULL AND a >= al AND a < au
*
- * If we end up with an empty result list, we return a single-member list
- * containing a constant TRUE, because callers expect a non-empty list.
+ * For default partition, it returns the negation of the constraints of all
+ * the other partitions.
+ *
+ * If we end up with an empty result list, we return NULL.
*/
static List *
-get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
+get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
+ bool for_default)
{
List *result = NIL;
ListCell *cell1,
@@ -1765,10 +1839,10 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
j;
PartitionRangeDatum *ldatum,
*udatum;
+ PartitionKey key = RelationGetPartitionKey(parent);
Expr *keyCol;
Const *lower_val,
*upper_val;
- NullTest *nulltest;
List *lower_or_arms,
*upper_or_arms;
int num_or_arms,
@@ -1778,44 +1852,68 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
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;
-
- /*
- * A range-partitioned table does not currently allow partition keys to be
- * null, so emit an IS NOT NULL expression for each key column.
- */
- partexprs_item = list_head(key->partexprs);
- for (i = 0; i < key->partnatts; i++)
+ if (spec->is_default)
{
- Expr *keyCol;
+ List *or_expr_args = NIL;
+ PartitionDesc pdesc = RelationGetPartitionDesc(parent);
+ Oid *inhoids = pdesc->oids;
+ int nparts = pdesc->nparts,
+ i;
- if (key->partattrs[i] != 0)
+ for (i = 0; i < nparts; i++)
{
- keyCol = (Expr *) makeVar(1,
- key->partattrs[i],
- key->parttypid[i],
- key->parttypmod[i],
- key->parttypcoll[i],
- 0);
+ Oid inhrelid = inhoids[i];
+ HeapTuple tuple;
+ Datum datum;
+ bool isnull;
+ PartitionBoundSpec *bspec;
+
+ tuple = SearchSysCache1(RELOID, inhrelid);
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", inhrelid);
+
+ datum = SysCacheGetAttr(RELOID, tuple,
+ Anum_pg_class_relpartbound,
+ &isnull);
+
+ Assert(!isnull);
+ bspec = (PartitionBoundSpec *) stringToNode(TextDatumGetCString(datum));
+
+ if (!bspec->is_default)
+ {
+ List *part_qual = get_qual_for_range(parent, bspec, true);
+
+ /*
+ * AND the constraints of the partition and add to
+ * or_expr_args
+ */
+ or_expr_args = lappend(or_expr_args, list_length(part_qual) > 1
+ ? makeBoolExpr(AND_EXPR, part_qual, -1)
+ : linitial(part_qual));
+ }
+ ReleaseSysCache(tuple);
}
- else
+
+ if (or_expr_args != NIL)
{
- if (partexprs_item == NULL)
- elog(ERROR, "wrong number of partition key expressions");
- keyCol = copyObject(lfirst(partexprs_item));
- partexprs_item = lnext(partexprs_item);
+ /* OR all the non-default partition constraints; then negate it */
+ result = lappend(result,
+ list_length(or_expr_args) > 1
+ ? makeBoolExpr(OR_EXPR, or_expr_args, -1)
+ : linitial(or_expr_args));
+ result = list_make1(makeBoolExpr(NOT_EXPR, result, -1));
}
- nulltest = makeNode(NullTest);
- nulltest->arg = keyCol;
- nulltest->nulltesttype = IS_NOT_NULL;
- nulltest->argisrow = false;
- nulltest->location = -1;
- result = lappend(result, nulltest);
+ return result;
}
+ lower_or_start_datum = list_head(spec->lowerdatums);
+ upper_or_start_datum = list_head(spec->upperdatums);
+ num_or_arms = key->partnatts;
+
+ if (!for_default)
+ result = get_range_nulltest(key);
+
/*
* Iterate over the key columns and check if the corresponding lower and
* upper datums are equal using the btree equality operator for the
@@ -2039,7 +2137,9 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
/* As noted above, caller expects the list to be non-empty. */
if (result == NIL)
- result = list_make1(makeBoolConst(true, false));
+ result = for_default
+ ? get_range_nulltest(key)
+ : list_make1(makeBoolConst(true, false));
return result;
}
@@ -2212,8 +2312,7 @@ get_partition_for_tuple(PartitionDispatch *pd,
Datum values[PARTITION_MAX_KEYS];
bool isnull[PARTITION_MAX_KEYS];
int cur_offset,
- cur_index;
- int i,
+ cur_index = -1,
result;
ExprContext *ecxt = GetPerTupleExprContext(estate);
TupleTableSlot *ecxt_scantuple_old = ecxt->ecxt_scantuple;
@@ -2257,24 +2356,6 @@ get_partition_for_tuple(PartitionDispatch *pd,
ecxt->ecxt_scantuple = slot;
FormPartitionKeyDatum(parent, slot, estate, values, isnull);
- if (key->strategy == PARTITION_STRATEGY_RANGE)
- {
- /*
- * Since we cannot route tuples with NULL partition keys through a
- * range-partitioned table, simply return that no partition exists
- */
- for (i = 0; i < key->partnatts; i++)
- {
- if (isnull[i])
- {
- *failed_at = parent;
- *failed_slot = slot;
- result = -1;
- goto error_exit;
- }
- }
- }
-
/*
* If this is a null partition key, route it to the null-accepting
* partition. Otherwise, route by searching the array of partition
@@ -2378,6 +2459,8 @@ make_one_range_bound(PartitionKey key, int index, List *datums, bool lower)
ListCell *lc;
int i;
+ Assert(datums != NULL);
+
bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
bound->index = index;
bound->datums = (Datum *) palloc0(key->partnatts * sizeof(Datum));
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 22e7aa6e13..e28596403c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3311,11 +3311,6 @@ transformPartitionBound(ParseState *pstate, Relation parent,
if (spec->is_default)
{
- if (strategy != PARTITION_STRATEGY_LIST)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("default partition is supported only for a list partitioned table")));
-
/*
* In case of the default partition, parser had no way to identify the
* partition strategy. Assign the parent's strategy to the default
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 65df1a8717..7f7622ee0c 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8702,7 +8702,6 @@ get_rule_expr(Node *node, deparse_context *context,
if (spec->is_default)
{
- Assert(strategy == PARTITION_STRATEGY_LIST);
appendStringInfoString(buf, "DEFAULT");
break;
}
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index bef8d5531f..cc62edd69b 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3359,6 +3359,19 @@ CREATE TABLE part2 (
);
ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
INFO: partition constraint for table "part2" is implied by existing constraints
+-- Create default partition
+CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
+-- Only one default partition is allowed, hence, following should give error
+CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS);
+ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT;
+ERROR: partition "partr_def2" conflicts with existing default partition "partr_def1"
+-- Overlapping partitions cannot be attached, hence, following should give error
+INSERT INTO partr_def1 VALUES (2, 10);
+CREATE TABLE part3 (LIKE range_parted);
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (2, 10) TO (2, 20);
+ERROR: updated partition constraint for default partition would be violated by some row
+-- Attaching partitions should be successful when there are no overlapping rows
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (3, 10) TO (3, 20);
-- check that leaf partitions are scanned when attaching a partitioned
-- table
CREATE TABLE part_5 (
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index bf0acadc4b..58c755be50 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -594,6 +594,16 @@ CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30);
ERROR: partition "fail_part" would overlap partition "part2"
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50);
ERROR: partition "fail_part" would overlap partition "part2"
+-- Create a default partition for range partitioned table
+CREATE TABLE range2_default PARTITION OF range_parted2 DEFAULT;
+-- More than one default partition is not allowed, so this should give error
+CREATE TABLE fail_default_part PARTITION OF range_parted2 DEFAULT;
+ERROR: partition "fail_default_part" conflicts with existing default partition "range2_default"
+-- Check if the range for default partitions overlap
+INSERT INTO range_parted2 VALUES (85);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90);
+ERROR: updated partition constraint for default partition "range2_default" would be violated by some row
+CREATE TABLE part4 PARTITION OF range_parted2 FOR VALUES FROM (90) TO (100);
-- now check for multi-column range partition key
CREATE TABLE range_parted3 (
a int,
@@ -607,6 +617,7 @@ CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10)
CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, maxvalue);
CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
ERROR: partition "fail_part" would overlap partition "part12"
+CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
-- cannot create a partition that says column b is allowed to range
-- from -infinity to +infinity, while there exist partitions that have
-- more specific ranges
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 5ca6e5ea23..2022584537 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -295,6 +295,18 @@ insert into range_parted values ('b', 10);
insert into range_parted values ('a');
ERROR: no partition of relation "range_parted" found for row
DETAIL: Partition key of the failing row contains (a, (b + 0)) = (a, null).
+-- Check default partition
+create table part_def partition of range_parted default;
+-- fail
+insert into part_def values ('b', 10);
+ERROR: new row for relation "part_def" violates partition constraint
+DETAIL: Failing row contains (b, 10).
+-- ok
+insert into part_def values ('c', 10);
+insert into range_parted values (null, null);
+insert into range_parted values ('a', null);
+insert into range_parted values (null, 19);
+insert into range_parted values ('b', 20);
select tableoid::regclass, * from range_parted;
tableoid | a | b
----------+---+----
@@ -304,7 +316,12 @@ select tableoid::regclass, * from range_parted;
part3 | b | 1
part4 | b | 10
part4 | b | 10
-(6 rows)
+ part_def | c | 10
+ part_def | |
+ part_def | a |
+ part_def | | 19
+ part_def | b | 20
+(11 rows)
-- ok
insert into list_parted values (null, 1);
@@ -493,6 +510,36 @@ insert into mlparted5 (a, b, c) values (1, 40, 'a');
ERROR: new row for relation "mlparted5a" violates partition constraint
DETAIL: Failing row contains (b, 1, 40).
drop table mlparted5;
+alter table mlparted drop constraint check_b;
+-- Check multi-level default partition
+create table mlparted_def partition of mlparted default partition by range(a);
+create table mlparted_def1 partition of mlparted_def for values from (40) to (50);
+create table mlparted_def2 partition of mlparted_def for values from (50) to (60);
+insert into mlparted values (40, 100);
+insert into mlparted_def1 values (42, 100);
+insert into mlparted_def2 values (54, 50);
+-- fail
+insert into mlparted values (70, 100);
+ERROR: no partition of relation "mlparted_def" found for row
+DETAIL: Partition key of the failing row contains (a) = (70).
+insert into mlparted_def1 values (52, 50);
+ERROR: new row for relation "mlparted_def1" violates partition constraint
+DETAIL: Failing row contains (52, 50, null).
+insert into mlparted_def2 values (34, 50);
+ERROR: new row for relation "mlparted_def2" violates partition constraint
+DETAIL: Failing row contains (34, 50, null).
+-- ok
+create table mlparted_defd partition of mlparted_def default;
+insert into mlparted values (70, 100);
+select tableoid::regclass, * from mlparted_def;
+ tableoid | a | b | c
+---------------+----+-----+---
+ mlparted_def1 | 40 | 100 |
+ mlparted_def1 | 42 | 100 |
+ mlparted_def2 | 54 | 50 |
+ mlparted_defd | 70 | 100 |
+(4 rows)
+
-- check that message shown after failure to find a partition shows the
-- appropriate key description (or none) in various situations
create table key_desc (a int, b int) partition by list ((a+0));
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 6750152e0f..e996640593 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -77,6 +77,10 @@ mlparted12|f
mlparted2|f
mlparted3|f
mlparted4|f
+mlparted_def|f
+mlparted_def1|f
+mlparted_def2|f
+mlparted_defd|f
money_data|f
num_data|f
num_exp_add|t
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index 9912ef29a1..cfb6aa861e 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -218,6 +218,15 @@ ERROR: new row for relation "part_b_10_b_20" violates partition constraint
DETAIL: Failing row contains (b, 9).
-- ok
update range_parted set b = b + 1 where b = 10;
+-- Creating default partition for range
+create table part_def partition of range_parted default;
+insert into range_parted values ('c', 9);
+-- ok
+update part_def set a = 'd' where a = 'c';
+-- fail
+update part_def set a = 'a' where a = 'd';
+ERROR: new row for relation "part_def" violates partition constraint
+DETAIL: Failing row contains (a, 9).
create table list_parted (
a text,
b int
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 92a93ebd8e..ccb2f2678b 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2184,6 +2184,21 @@ CREATE TABLE part2 (
);
ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
+-- Create default partition
+CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
+
+-- Only one default partition is allowed, hence, following should give error
+CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS);
+ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT;
+
+-- Overlapping partitions cannot be attached, hence, following should give error
+INSERT INTO partr_def1 VALUES (2, 10);
+CREATE TABLE part3 (LIKE range_parted);
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (2, 10) TO (2, 20);
+
+-- Attaching partitions should be successful when there are no overlapping rows
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (3, 10) TO (3, 20);
+
-- check that leaf partitions are scanned when attaching a partitioned
-- table
CREATE TABLE part_5 (
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 07d653a087..eeab5d91ff 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -554,6 +554,17 @@ CREATE TABLE part3 PARTITION OF range_parted2 FOR VALUES FROM (30) TO (40);
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30);
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50);
+-- Create a default partition for range partitioned table
+CREATE TABLE range2_default PARTITION OF range_parted2 DEFAULT;
+
+-- More than one default partition is not allowed, so this should give error
+CREATE TABLE fail_default_part PARTITION OF range_parted2 DEFAULT;
+
+-- Check if the range for default partitions overlap
+INSERT INTO range_parted2 VALUES (85);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90);
+CREATE TABLE part4 PARTITION OF range_parted2 FOR VALUES FROM (90) TO (100);
+
-- now check for multi-column range partition key
CREATE TABLE range_parted3 (
a int,
@@ -567,6 +578,7 @@ CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES FROM (1, minvalue) TO
CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10);
CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, maxvalue);
CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
+CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
-- cannot create a partition that says column b is allowed to range
-- from -infinity to +infinity, while there exist partitions that have
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 7bd06198ce..adb31d4732 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -180,8 +180,19 @@ insert into range_parted values ('b', 1);
insert into range_parted values ('b', 10);
-- fail (partition key (b+0) is null)
insert into range_parted values ('a');
-select tableoid::regclass, * from range_parted;
+-- Check default partition
+create table part_def partition of range_parted default;
+-- fail
+insert into part_def values ('b', 10);
+-- ok
+insert into part_def values ('c', 10);
+insert into range_parted values (null, null);
+insert into range_parted values ('a', null);
+insert into range_parted values (null, 19);
+insert into range_parted values ('b', 20);
+
+select tableoid::regclass, * from range_parted;
-- ok
insert into list_parted values (null, 1);
insert into list_parted (a) values ('aA');
@@ -311,6 +322,24 @@ create function mlparted5abrtrig_func() returns trigger as $$ begin new.c = 'b';
create trigger mlparted5abrtrig before insert on mlparted5a for each row execute procedure mlparted5abrtrig_func();
insert into mlparted5 (a, b, c) values (1, 40, 'a');
drop table mlparted5;
+alter table mlparted drop constraint check_b;
+
+-- Check multi-level default partition
+create table mlparted_def partition of mlparted default partition by range(a);
+create table mlparted_def1 partition of mlparted_def for values from (40) to (50);
+create table mlparted_def2 partition of mlparted_def for values from (50) to (60);
+insert into mlparted values (40, 100);
+insert into mlparted_def1 values (42, 100);
+insert into mlparted_def2 values (54, 50);
+-- fail
+insert into mlparted values (70, 100);
+insert into mlparted_def1 values (52, 50);
+insert into mlparted_def2 values (34, 50);
+-- ok
+create table mlparted_defd partition of mlparted_def default;
+insert into mlparted values (70, 100);
+
+select tableoid::regclass, * from mlparted_def;
-- check that message shown after failure to find a partition shows the
-- appropriate key description (or none) in various situations
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 44fb0dc40a..1ce7a947e0 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -125,6 +125,14 @@ update range_parted set b = b - 1 where b = 10;
-- ok
update range_parted set b = b + 1 where b = 10;
+-- Creating default partition for range
+create table part_def partition of range_parted default;
+insert into range_parted values ('c', 9);
+-- ok
+update part_def set a = 'd' where a = 'c';
+-- fail
+update part_def set a = 'a' where a = 'd';
+
create table list_parted (
a text,
b int
--
2.13.3
Hi Jeevan,
On Tue, Aug 22, 2017 at 7:53 AM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:
Hi Beena,
On Thu, Aug 17, 2017 at 4:59 PM, Beena Emerson <memissemerson@gmail.com>
wrote:PFA the patch rebased over v25 patches of default list partition [1]
Thanks for rebasing.
Range partition review:
Thank you for your review.
3.
@@ -2396,6 +2456,8 @@ make_one_range_bound(PartitionKey key, int index, List
*datums, bool lower)
ListCell *lc;
int i;+ Assert(datums != NULL);
+
bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
bound->index = index;
bound->datums = (Datum *) palloc0(key->partnatts * sizeof(Datum));I am not really convinced, why should we have above Assert.
The function should never be called for default partition where datums = NULL.
4. static List * -get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec) +get_qual_for_range(Relation parent, PartitionBoundSpec *spec, + bool for_default) {The addition and the way flag ‘for_default’ is being used is very confusing.
At this moment I am not able to think about a alternate solution to the
way you have used this flag. But will try and see if I can come up with
an alternate approach.
Well, we need to skip the get_range_nulltest while collecting the qual
of other partition to make one for default. We could skip this flag
and remove the NullTest from the qual returned for each partition but
I am not sure if thats a good way to go about it.
--
Beena Emerson
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Aug 22, 2017 at 4:20 PM, Beena Emerson <memissemerson@gmail.com> wrote:
Hi Jeevan,
On Tue, Aug 22, 2017 at 7:53 AM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:4. static List * -get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec) +get_qual_for_range(Relation parent, PartitionBoundSpec *spec, + bool for_default) {The addition and the way flag ‘for_default’ is being used is very confusing.
At this moment I am not able to think about a alternate solution to the
way you have used this flag. But will try and see if I can come up with
an alternate approach.Well, we need to skip the get_range_nulltest while collecting the qual
of other partition to make one for default. We could skip this flag
and remove the NullTest from the qual returned for each partition but
I am not sure if thats a good way to go about it.
Please find attached a patch which removes the for_default flag from
the get_qual_for_range function. This patch is just to show an idea
and should be applied over my earlier patch. There could be a better
way to do this. Let me know your opinion.
--
Beena Emerson
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Attachments:
remove_default_flag.patchapplication/octet-stream; name=remove_default_flag.patchDownload
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 3232844..194258d 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -127,9 +127,8 @@ static void get_range_key_properties(PartitionKey key, int keynum,
Expr **keyCol,
Const **lower_val, Const **upper_val);
static List *get_qual_for_list(Relation parent, PartitionBoundSpec *spec);
-static List *get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
- bool for_default);
-static List *get_range_nulltest(PartitionKey key);
+static List *get_qual_for_range(Relation parent, PartitionBoundSpec *spec);
+static List *get_range_nulltest(PartitionKey key, bool for_default);
static List *generate_partition_qual(Relation rel);
static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index,
@@ -902,7 +901,7 @@ check_default_allows_bound(Relation parent, Relation default_rel,
new_part_constraints = (new_spec->strategy == PARTITION_STRATEGY_LIST)
? get_qual_for_list(parent, new_spec)
- : get_qual_for_range(parent, new_spec, false);
+ : get_qual_for_range(parent, new_spec);
def_part_constraints =
get_default_part_validation_constraint(new_part_constraints);
@@ -1105,7 +1104,7 @@ get_qual_from_partbound(Relation rel, Relation parent,
case PARTITION_STRATEGY_RANGE:
Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
- my_qual = get_qual_for_range(parent, spec, false);
+ my_qual = get_qual_for_range(parent, spec);
break;
default:
@@ -1740,7 +1739,7 @@ get_range_key_properties(PartitionKey key, int keynum,
* keys to be null, so emit an IS NOT NULL expression for each key column.
*/
static List *
-get_range_nulltest(PartitionKey key)
+get_range_nulltest(PartitionKey key, bool for_default)
{
List *result = NIL;
NullTest *nulltest;
@@ -1771,7 +1770,7 @@ get_range_nulltest(PartitionKey key)
nulltest = makeNode(NullTest);
nulltest->arg = keyCol;
- nulltest->nulltesttype = IS_NOT_NULL;
+ nulltest->nulltesttype = for_default? IS_NULL: IS_NOT_NULL;
nulltest->argisrow = false;
nulltest->location = -1;
result = lappend(result, nulltest);
@@ -1824,8 +1823,7 @@ get_range_nulltest(PartitionKey key)
* If we end up with an empty result list, we return NULL.
*/
static List *
-get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
- bool for_default)
+get_qual_for_range(Relation parent, PartitionBoundSpec *spec)
{
List *result = NIL;
ListCell *cell1,
@@ -1878,15 +1876,34 @@ get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
if (!bspec->is_default)
{
- List *part_qual = get_qual_for_range(parent, bspec, true);
+ List *part_qual = get_qual_for_range(parent, bspec);
+ List *list_tmp = list_copy(part_qual);
+ ListCell *lc;
+ int count = 0;
+
+ foreach (lc, list_tmp)
+ {
+ Node *n = (Node *) lfirst(lc);
+
+ if (IsA(n, NullTest))
+ {
+ list_delete(part_qual, n);
+ count++;
+ }
+ }
+
+ if (count == list_length(list_tmp))
+ part_qual = NIL;
/*
* AND the constraints of the partition and add to
* or_expr_args
*/
- or_expr_args = lappend(or_expr_args, list_length(part_qual) > 1
- ? makeBoolExpr(AND_EXPR, part_qual, -1)
- : linitial(part_qual));
+
+ if (part_qual != NIL)
+ or_expr_args = lappend(or_expr_args, list_length(part_qual) > 1
+ ? makeBoolExpr(AND_EXPR, part_qual, -1)
+ : linitial(part_qual));
}
ReleaseSysCache(tuple);
}
@@ -1900,6 +1917,8 @@ get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
: linitial(or_expr_args));
result = list_make1(makeBoolExpr(NOT_EXPR, result, -1));
}
+ else if (nparts != 1)
+ result = lappend(result,makeBoolExpr(OR_EXPR, get_range_nulltest(key, true), -1));
return result;
}
@@ -1908,8 +1928,7 @@ get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
upper_or_start_datum = list_head(spec->upperdatums);
num_or_arms = key->partnatts;
- if (!for_default)
- result = get_range_nulltest(key);
+ result = get_range_nulltest(key, false);
/*
* Iterate over the key columns and check if the corresponding lower and
@@ -2132,12 +2151,6 @@ get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
? makeBoolExpr(OR_EXPR, upper_or_arms, -1)
: linitial(upper_or_arms));
- /* As noted above, caller expects the list to be non-empty. */
- if (result == NIL)
- result = for_default
- ? get_range_nulltest(key)
- : list_make1(makeBoolConst(true, false));
-
return result;
}
Hi Beena,
On Tue, Aug 22, 2017 at 5:22 PM, Beena Emerson <memissemerson@gmail.com>
wrote:
On Tue, Aug 22, 2017 at 4:20 PM, Beena Emerson <memissemerson@gmail.com>
wrote:Hi Jeevan,
On Tue, Aug 22, 2017 at 7:53 AM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:4. static List * -get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec) +get_qual_for_range(Relation parent, PartitionBoundSpec *spec, + bool for_default) {The addition and the way flag ‘for_default’ is being used is very
confusing.
At this moment I am not able to think about a alternate solution to the
way you have used this flag. But will try and see if I can come up with
an alternate approach.Well, we need to skip the get_range_nulltest while collecting the qual
of other partition to make one for default. We could skip this flag
and remove the NullTest from the qual returned for each partition but
I am not sure if thats a good way to go about it.Please find attached a patch which removes the for_default flag from
the get_qual_for_range function. This patch is just to show an idea
and should be applied over my earlier patch. There could be a better
way to do this. Let me know your opinion.
+
+ foreach (lc, list_tmp)
+ {
+ Node *n = (Node *) lfirst(lc);
+
+ if (IsA(n, NullTest))
+ {
+ list_delete(part_qual, n);
+ count++;
+ }
+ }
+
I think its an unnecessary overhead to have the nulltest prepared first
and then search for it and remove it from the partition qual. This is very
ugly.
I think the original idea is still better compared to this. May be we can
rename
the 'for_default' flag to something like 'part_of_default_qual'.
Also, as discussed offline I will merge this with default list partitioning
patch.
Regards,
Jeevan Ladhe
Hello,
On Thu, Aug 24, 2017 at 3:08 PM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:
Hi Beena,
On Tue, Aug 22, 2017 at 5:22 PM, Beena Emerson <memissemerson@gmail.com>
wrote:On Tue, Aug 22, 2017 at 4:20 PM, Beena Emerson <memissemerson@gmail.com>
wrote:Hi Jeevan,
On Tue, Aug 22, 2017 at 7:53 AM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:4. static List * -get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec) +get_qual_for_range(Relation parent, PartitionBoundSpec *spec, + bool for_default) {The addition and the way flag ‘for_default’ is being used is very
confusing.
At this moment I am not able to think about a alternate solution to the
way you have used this flag. But will try and see if I can come up with
an alternate approach.Well, we need to skip the get_range_nulltest while collecting the qual
of other partition to make one for default. We could skip this flag
and remove the NullTest from the qual returned for each partition but
I am not sure if thats a good way to go about it.Please find attached a patch which removes the for_default flag from
the get_qual_for_range function. This patch is just to show an idea
and should be applied over my earlier patch. There could be a better
way to do this. Let me know your opinion.+ + foreach (lc, list_tmp) + { + Node *n = (Node *) lfirst(lc); + + if (IsA(n, NullTest)) + { + list_delete(part_qual, n); + count++; + } + } +I think its an unnecessary overhead to have the nulltest prepared first
and then search for it and remove it from the partition qual. This is very
ugly.
Yes, I felt the same but just put it out there to see if someone can
improve on this.
I think the original idea is still better compared to this. May be we can
rename
the 'for_default' flag to something like 'part_of_default_qual'.
Ya. I think that would work.
--
Beena Emerson
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hello all,
This is to inform all that this patch has been merged with default
list partition patch [1]/messages/by-id/CAOgcT0ONgwajdtkoq+AuYkdTPY9cLWWLjxt_k4SXue3eieAr+g@mail.gmail.com. There will be no further updates here. The
status of this will be updated on the commitfest according to progres
on that thread.
[1]: /messages/by-id/CAOgcT0ONgwajdtkoq+AuYkdTPY9cLWWLjxt_k4SXue3eieAr+g@mail.gmail.com
Thank you,
Beena Emerson
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers