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

