default_range_partition_v4.patch
application/octet-stream
Filename: default_range_partition_v4.patch
Type: application/octet-stream
Part: 0
Message:
Re: Default Partition for Range
Patch
Format: unified
Series: patch v4
| File | + | − |
|---|---|---|
| src/backend/catalog/partition.c | 174 | 95 |
| src/backend/parser/gram.y | 0 | 1 |
| src/backend/parser/parse_utilcmd.c | 0 | 5 |
| src/backend/utils/adt/ruleutils.c | 6 | 0 |
| src/test/regress/expected/alter_table.out | 13 | 0 |
| src/test/regress/expected/create_table.out | 11 | 3 |
| src/test/regress/expected/insert.out | 41 | 1 |
| src/test/regress/expected/sanity_check.out | 4 | 0 |
| src/test/regress/expected/update.out | 9 | 0 |
| src/test/regress/sql/alter_table.sql | 15 | 0 |
| src/test/regress/sql/create_table.sql | 12 | 2 |
| src/test/regress/sql/insert.sql | 26 | 1 |
| src/test/regress/sql/update.sql | 8 | 0 |
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