Adding support for Default partition in partitioning

Started by Rahila Syedalmost 9 years ago187 messages
#1Rahila Syed
rahilasyed90@gmail.com
1 attachment(s)

Hello,

Currently inserting the data into a partitioned table that does not fit into
any of its partitions is not allowed.

The attached patch provides a capability to add a default partition to a
list
partitioned table as follows.

postgres=# CREATE TABLE list_partitioned (
a int
) PARTITION BY LIST (a);
CREATE TABLE

postgres=# CREATE TABLE part_default PARTITION OF list_partitioned FOR
VALUES IN (DEFAULT);
CREATE TABLE

postgres=# CREATE TABLE part_1 PARTITION OF list_partitioned FOR VALUES IN
(4,5);
CREATE TABLE

postgres=# insert into list_partitioned values (9);
INSERT 0 1

postgres=# select * from part_default;
a
---
9
(1 row)

The attached patch is in a preliminary stage and has following ToDos:
1. Adding pg_dump support.
2. Documentation
3. Handling adding a new partition to a partitioned table
with default partition.
This will require moving tuples from existing default partition to
newly created partition if they satisfy its partition bound.
4. Handling of update of partition key in a default partition. As per
current design it should throw an error if the update requires the tuple to
be moved to any other partition. But this can changed by the following
proposal.

/messages/by-id/CAJ3gD9do9o2ccQ7j7+tSgiE1REY65XRiMb=
yJO3u3QhyP8EEPQ@mail.gmail.com

I am adding it to the current commitfest with the status Waiting on Author
as I will submit an updated patch with above ToDos.
Kindly give your suggestions.

Thank you,
Rahila Syed

Attachments:

default_list_partition_v1.patchapplication/x-download; name=default_list_partition_v1.patchDownload
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 4bcef58..2d96b24 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -90,6 +90,10 @@ typedef struct PartitionBoundInfoData
 								 * for range partitioned tables */
 	int			null_index;		/* Index of the null-accepting partition; -1
 								 * for range partitioned tables */
+	bool		has_def;		/* Is there a default partition? Currently false
+								 * for a range partitioned table */
+	bool		def_index;		/* Index of the default list partition. -1 for
+								 * range partitioned tables */
 } PartitionBoundInfoData;
 
 /*
@@ -173,6 +177,8 @@ RelationBuildPartitionDesc(Relation rel)
 	/* List partitioning specific */
 	PartitionListValue **all_values = NULL;
 	bool		found_null = false;
+	bool		found_def = false;
+	int			def_index = -1;
 	int			null_index = -1;
 
 	/* Range partitioning specific */
@@ -246,6 +252,8 @@ RelationBuildPartitionDesc(Relation rel)
 			i = 0;
 			found_null = false;
 			null_index = -1;
+			found_def = false;
+			def_index = -1;
 			foreach(cell, boundspecs)
 			{
 				ListCell   *c;
@@ -256,6 +264,15 @@ RelationBuildPartitionDesc(Relation rel)
 
 				foreach(c, spec->listdatums)
 				{
+					Node *value = lfirst(c);
+
+					if (IsA(value, DefElem))
+					{
+						found_def = true;
+						def_index = i;
+						continue;
+					}
+
 					Const	   *val = lfirst(c);
 					PartitionListValue *list_value = NULL;
 
@@ -466,6 +483,7 @@ RelationBuildPartitionDesc(Relation rel)
 			case PARTITION_STRATEGY_LIST:
 				{
 					boundinfo->has_null = found_null;
+					boundinfo->has_def = found_def;
 					boundinfo->indexes = (int *) palloc(ndatums * sizeof(int));
 
 					/*
@@ -503,6 +521,11 @@ RelationBuildPartitionDesc(Relation rel)
 						if (mapping[null_index] == -1)
 							mapping[null_index] = next_index++;
 					}
+					if (found_def)
+					{
+						if (mapping[def_index] == -1)
+							mapping[def_index] = next_index++;
+					}
 
 					/* All partition must now have a valid mapping */
 					Assert(next_index == nparts);
@@ -511,6 +534,11 @@ RelationBuildPartitionDesc(Relation rel)
 						boundinfo->null_index = mapping[null_index];
 					else
 						boundinfo->null_index = -1;
+
+					if (found_def)
+						boundinfo->def_index = mapping[def_index];
+					else
+						boundinfo->def_index = -1;
 					break;
 				}
 
@@ -1565,6 +1593,19 @@ generate_partition_qual(Relation rel)
 	bound = stringToNode(TextDatumGetCString(boundDatum));
 	ReleaseSysCache(tuple);
 
+	/* Return if it is a default list partition */
+	PartitionBoundSpec *spec = (PartitionBoundSpec *)bound;
+	ListCell *cell;
+	foreach(cell, spec->listdatums)
+	{
+		Node *value = lfirst(cell);
+		if (IsA(value, DefElem))
+		{
+			/* Keep the parent locked until commit */
+			heap_close(parent, NoLock);
+			return result;
+		}
+	}
 	my_qual = get_qual_from_partbound(rel, parent, bound);
 
 	/* Add the parent's quals to the list (if any) */
@@ -1773,8 +1814,17 @@ get_partition_for_tuple(PartitionDispatch *pd,
 		 */
 		if (cur_index < 0)
 		{
-			result = -1;
-			*failed_at = RelationGetRelid(parent->reldesc);
+			/*
+			 * If partitioned table has a default partition, return
+			 * its sequence number
+			 */
+			if (partdesc->boundinfo->has_def)
+				result = parent->indexes[partdesc->boundinfo->def_index];
+			else
+			{
+				result = -1;
+				*failed_at = RelationGetRelid(parent->reldesc);
+			}
 			break;
 		}
 		else if (parent->indexes[cur_index] >= 0)
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 07cc81e..504cf94 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2593,6 +2593,7 @@ partbound_datum:
 			Sconst			{ $$ = makeStringConst($1, @1); }
 			| NumericOnly	{ $$ = makeAConst($1, @1); }
 			| NULL_P		{ $$ = makeNullAConst(@1); }
+			| DEFAULT  { $$ = (Node *)makeDefElem("DEFAULT", NULL, @1); }
 		;
 
 partbound_datum_list:
@@ -2600,7 +2601,6 @@ partbound_datum_list:
 			| partbound_datum_list ',' partbound_datum
 												{ $$ = lappend($1, $3); }
 		;
-
 range_datum_list:
 			PartitionRangeDatum					{ $$ = list_make1($1); }
 			| range_datum_list ',' PartitionRangeDatum
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 46e5ae5..3610f22 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3098,47 +3098,50 @@ transformPartitionBound(ParseState *pstate, Relation parent, Node *bound)
 		result_spec->listdatums = NIL;
 		foreach(cell, spec->listdatums)
 		{
-			A_Const    *con = (A_Const *) lfirst(cell);
-			Node	   *value;
-			ListCell   *cell2;
-			bool		duplicate;
-
-			value = (Node *) make_const(pstate, &con->val, con->location);
-			value = coerce_to_target_type(pstate,
-										  value, exprType(value),
-										  get_partition_col_typid(key, 0),
-										  get_partition_col_typmod(key, 0),
-										  COERCION_ASSIGNMENT,
-										  COERCE_IMPLICIT_CAST,
-										  -1);
-
-			if (value == NULL)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
-							 format_type_be(get_partition_col_typid(key, 0)),
-								colname),
-						 parser_errposition(pstate,
-											exprLocation((Node *) con))));
-
-			/* Simplify the expression */
-			value = (Node *) expression_planner((Expr *) value);
-
-			/* Don't add to the result if the value is a duplicate */
-			duplicate = false;
-			foreach(cell2, result_spec->listdatums)
+			Node *value = lfirst(cell);
+			/* Perform the transformation only for non default partition */
+			if (!IsA(value, DefElem))
 			{
-				Const	   *value2 = (Const *) lfirst(cell2);
+				A_Const    *con = (A_Const *) lfirst(cell);
+				ListCell   *cell2;
+				bool		duplicate;
+
+				value = (Node *) make_const(pstate, &con->val, con->location);
+				value = coerce_to_target_type(pstate,
+											value, exprType(value),
+											get_partition_col_typid(key, 0),
+											get_partition_col_typmod(key, 0),
+											COERCION_ASSIGNMENT,
+											COERCE_IMPLICIT_CAST,
+											-1);
+
+				if (value == NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+								format_type_be(get_partition_col_typid(key, 0)),
+									colname),
+							parser_errposition(pstate,
+												exprLocation((Node *) con))));
 
-				if (equal(value, value2))
+				/* Simplify the expression */
+				value = (Node *) expression_planner((Expr *) value);
+
+				/* Don't add to the result if the value is a duplicate */
+				duplicate = false;
+				foreach(cell2, result_spec->listdatums)
 				{
-					duplicate = true;
-					break;
+					Const	   *value2 = (Const *) lfirst(cell2);
+
+					if (equal(value, value2))
+					{
+						duplicate = true;
+						break;
+					}
 				}
+				if (duplicate)
+					continue;
 			}
-			if (duplicate)
-				continue;
-
 			result_spec->listdatums = lappend(result_spec->listdatums,
 											  value);
 		}
#2Robert Haas
robertmhaas@gmail.com
In reply to: Rahila Syed (#1)
Re: Adding support for Default partition in partitioning

On Wed, Mar 1, 2017 at 6:29 AM, Rahila Syed <rahilasyed90@gmail.com> wrote:

3. Handling adding a new partition to a partitioned table
with default partition.
This will require moving tuples from existing default partition to
newly created partition if they satisfy its partition bound.

Considering that this patch was submitted at the last minute and isn't
even complete, I can't see this getting into v10. But that doesn't
mean we can't talk about it. I'm curious to hear other opinions on
whether we should have this feature. On the point mentioned above, I
don't think adding a partition should move tuples, necessarily; seems
like it would be good enough - maybe better - for it to fail if there
are any that would need to be moved.

--
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

#3David Fetter
david@fetter.org
In reply to: Robert Haas (#2)
Re: Adding support for Default partition in partitioning

On Fri, Mar 03, 2017 at 08:10:52AM +0530, Robert Haas wrote:

On Wed, Mar 1, 2017 at 6:29 AM, Rahila Syed <rahilasyed90@gmail.com> wrote:

3. Handling adding a new partition to a partitioned table
with default partition.
This will require moving tuples from existing default partition to
newly created partition if they satisfy its partition bound.

Considering that this patch was submitted at the last minute and isn't
even complete, I can't see this getting into v10. But that doesn't
mean we can't talk about it. I'm curious to hear other opinions on
whether we should have this feature. On the point mentioned above, I
don't think adding a partition should move tuples, necessarily; seems
like it would be good enough - maybe better - for it to fail if there
are any that would need to be moved.

I see this as a bug fix.

The current state of declarative partitions is such that you need way
too much foresight in order to use them. Missed adding a partition?
Writes fail and can't be made to succeed. This is not a failure mode
we should be forcing on people, especially as it's a massive
regression from the extant inheritance-based partitioning.

Best,
David.
--
David Fetter <david(at)fetter(dot)org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david(dot)fetter(at)gmail(dot)com

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#4Keith Fiske
keith@omniti.com
In reply to: Robert Haas (#2)
Re: Adding support for Default partition in partitioning

On Thu, Mar 2, 2017 at 9:40 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Wed, Mar 1, 2017 at 6:29 AM, Rahila Syed <rahilasyed90@gmail.com>
wrote:

3. Handling adding a new partition to a partitioned table
with default partition.
This will require moving tuples from existing default partition to
newly created partition if they satisfy its partition bound.

Considering that this patch was submitted at the last minute and isn't
even complete, I can't see this getting into v10. But that doesn't
mean we can't talk about it. I'm curious to hear other opinions on
whether we should have this feature. On the point mentioned above, I
don't think adding a partition should move tuples, necessarily; seems
like it would be good enough - maybe better - for it to fail if there
are any that would need to be moved.

--
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

I'm all for this feature and had suggested it back in the original thread
to add partitioning to 10. I agree that adding a new partition should not
move any data out of the default. It's easy enough to set up a monitor to
watch for data existing in the default. Perhaps also adding a column to
pg_partitioned_table that contains the oid of the default partition so it's
easier to identify from a system catalog perspective and make that
monitoring easier. I don't even see a need for it to fail either and not
quite sure how that would even work? If they can't add a necessary child
due to data being in the default, how can they ever get it out? Just leave
it to the user to keep an eye on the default and fix it as necessary. This
is what I do in pg_partman.

--
Keith Fiske
Database Administrator
OmniTI Computer Consulting, Inc.
http://www.keithf4.com

#5Jim Nasby
jim.nasby@openscg.com
In reply to: Keith Fiske (#4)
Re: Adding support for Default partition in partitioning

On 3/7/17 10:30 AM, Keith Fiske wrote:

I'm all for this feature and had suggested it back in the original

FWIW, I was working with a system just today that has an overflow partition.

thread to add partitioning to 10. I agree that adding a new partition
should not move any data out of the default. It's easy enough to set up

+1

a monitor to watch for data existing in the default. Perhaps also adding
a column to pg_partitioned_table that contains the oid of the default
partition so it's easier to identify from a system catalog perspective
and make that monitoring easier. I don't even see a need for it to fail

I agree that there should be a way to identify the default partition.

either and not quite sure how that would even work? If they can't add a
necessary child due to data being in the default, how can they ever get
it out?

Yeah, was wondering that as well...
--
Jim Nasby, Chief Data Architect, OpenSCG
http://OpenSCG.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#6Rahila Syed
rahilasyed90@gmail.com
In reply to: Keith Fiske (#4)
Re: Adding support for Default partition in partitioning

I agree that adding a new partition should not move any data out of the

default. It's easy enough to set up a monitor to watch for data existing in
the >default. Perhaps also adding a column to pg_partitioned_table that
contains the oid of the default partition so it's easier to identify from a
system >catalog perspective and make that monitoring easier.

Wont it incur overhead of scanning the default partition for matching rows
each time a select happens on any matching partition?
This extra scan would be required until rows satisfying the newly added
partition are left around in default partition.

I don't even see a need for it to fail either and not quite sure how that

would even work? If they can't add a necessary child due to data being in
the >default, how can they ever get it out? Just leave it to the user to
keep an eye on the default and fix it as necessary.
This patch intends to provide a way to insert data that does not satisfy
any of the existing partitions. For this patch, we can disallow adding a
new partition when a default partition with conflicting rows exist. There
can be many solutions to the problem of adding a new partition. One is to
move the relevant tuples from default to the new partition or like you
suggest keep monitoring the default partition until user moves the rows out
of the default.

Thank you,
Rahila Syed

On Tue, Mar 7, 2017 at 10:00 PM, Keith Fiske <keith@omniti.com> wrote:

Show quoted text

On Thu, Mar 2, 2017 at 9:40 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Wed, Mar 1, 2017 at 6:29 AM, Rahila Syed <rahilasyed90@gmail.com>
wrote:

3. Handling adding a new partition to a partitioned table
with default partition.
This will require moving tuples from existing default partition to
newly created partition if they satisfy its partition bound.

Considering that this patch was submitted at the last minute and isn't
even complete, I can't see this getting into v10. But that doesn't
mean we can't talk about it. I'm curious to hear other opinions on
whether we should have this feature. On the point mentioned above, I
don't think adding a partition should move tuples, necessarily; seems
like it would be good enough - maybe better - for it to fail if there
are any that would need to be moved.

--
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

I'm all for this feature and had suggested it back in the original thread
to add partitioning to 10. I agree that adding a new partition should not
move any data out of the default. It's easy enough to set up a monitor to
watch for data existing in the default. Perhaps also adding a column to
pg_partitioned_table that contains the oid of the default partition so it's
easier to identify from a system catalog perspective and make that
monitoring easier. I don't even see a need for it to fail either and not
quite sure how that would even work? If they can't add a necessary child
due to data being in the default, how can they ever get it out? Just leave
it to the user to keep an eye on the default and fix it as necessary. This
is what I do in pg_partman.

--
Keith Fiske
Database Administrator
OmniTI Computer Consulting, Inc.
http://www.keithf4.com

#7Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Robert Haas (#2)
Re: Adding support for Default partition in partitioning

On 3/2/17 21:40, Robert Haas wrote:

On the point mentioned above, I
don't think adding a partition should move tuples, necessarily; seems
like it would be good enough - maybe better - for it to fail if there
are any that would need to be moved.

ISTM that the uses cases of various combinations of adding a default
partition, adding another partition after it, removing the default
partition, clearing out the default partition in order to add more
nondefault partitions, and so on, need to be more clearly spelled out
for each partitioning type. We also need to consider that pg_dump and
pg_upgrade need to be able to reproduce all those states. Seems to be a
bit of work still ...

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#8Robert Haas
robertmhaas@gmail.com
In reply to: Peter Eisentraut (#7)
Re: Adding support for Default partition in partitioning

On Fri, Mar 10, 2017 at 2:17 PM, Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:

On 3/2/17 21:40, Robert Haas wrote:

On the point mentioned above, I
don't think adding a partition should move tuples, necessarily; seems
like it would be good enough - maybe better - for it to fail if there
are any that would need to be moved.

ISTM that the uses cases of various combinations of adding a default
partition, adding another partition after it, removing the default
partition, clearing out the default partition in order to add more
nondefault partitions, and so on, need to be more clearly spelled out
for each partitioning type. We also need to consider that pg_dump and
pg_upgrade need to be able to reproduce all those states. Seems to be a
bit of work still ...

This patch is only targeting list partitioning. It is not entirely
clear that the concept makes sense for range partitioning; you can
already define a partition from the end of the last partitioning up to
infinity, or from minus-infinity up to the starting point of the first
partition. The only thing a default range partition would do is let
you do is have a single partition cover all of the ranges that would
otherwise be unassigned, which might not even be something we want.

I don't know how complete the patch is, but the specification seems
clear enough. If you have partitions for 1, 3, and 5, you get
partition constraints of (a = 1), (a = 3), and (a = 5). If you add a
default partition, you get a constraint of (a != 1 and a != 3 and a !=
5). If you then add a partition for 7, the default partition's
constraint becomes (a != 1 and a != 3 and a != 5 and a != 7). The
partition must be revalidated at that point for conflicting rows,
which we can either try to move to the new partition, or just error
out if there are any, depending on what we decide we want to do. I
don't think any of that requires any special handling for either
pg_dump or pg_upgrade; it all just falls out of getting the
partitioning constraints correct and consistently enforcing them, just
as for any other partition.

--
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

#9Rahila Syed
rahilasyed90@gmail.com
In reply to: Robert Haas (#8)
1 attachment(s)
Re: Adding support for Default partition in partitioning

Hello,

Please find attached a rebased patch with support for pg_dump. I am working
on the patch
to handle adding a new partition after a default partition by throwing an
error if
conflicting rows exist in default partition and adding the partition
successfully otherwise.
Will post an updated patch by tomorrow.

Thank you,
Rahila Syed

On Mon, Mar 13, 2017 at 5:42 AM, Robert Haas <robertmhaas@gmail.com> wrote:

Show quoted text

On Fri, Mar 10, 2017 at 2:17 PM, Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:

On 3/2/17 21:40, Robert Haas wrote:

On the point mentioned above, I
don't think adding a partition should move tuples, necessarily; seems
like it would be good enough - maybe better - for it to fail if there
are any that would need to be moved.

ISTM that the uses cases of various combinations of adding a default
partition, adding another partition after it, removing the default
partition, clearing out the default partition in order to add more
nondefault partitions, and so on, need to be more clearly spelled out
for each partitioning type. We also need to consider that pg_dump and
pg_upgrade need to be able to reproduce all those states. Seems to be a
bit of work still ...

This patch is only targeting list partitioning. It is not entirely
clear that the concept makes sense for range partitioning; you can
already define a partition from the end of the last partitioning up to
infinity, or from minus-infinity up to the starting point of the first
partition. The only thing a default range partition would do is let
you do is have a single partition cover all of the ranges that would
otherwise be unassigned, which might not even be something we want.

I don't know how complete the patch is, but the specification seems
clear enough. If you have partitions for 1, 3, and 5, you get
partition constraints of (a = 1), (a = 3), and (a = 5). If you add a
default partition, you get a constraint of (a != 1 and a != 3 and a !=
5). If you then add a partition for 7, the default partition's
constraint becomes (a != 1 and a != 3 and a != 5 and a != 7). The
partition must be revalidated at that point for conflicting rows,
which we can either try to move to the new partition, or just error
out if there are any, depending on what we decide we want to do. I
don't think any of that requires any special handling for either
pg_dump or pg_upgrade; it all just falls out of getting the
partitioning constraints correct and consistently enforcing them, just
as for any other partition.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

Attachments:

default_partition_v2.patchapplication/x-download; name=default_partition_v2.patchDownload
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index e01ef86..2e675ba 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -90,6 +90,10 @@ typedef struct PartitionBoundInfoData
 								 * for range partitioned tables */
 	int			null_index;		/* Index of the null-accepting partition; -1
 								 * for range partitioned tables */
+	bool		has_def;		/* Is there a default partition? Currently false
+								 * for a range partitioned table */
+	bool		def_index;		/* Index of the default list partition. -1 for
+								 * range partitioned tables */
 } PartitionBoundInfoData;
 
 /*
@@ -166,6 +170,8 @@ RelationBuildPartitionDesc(Relation rel)
 	/* List partitioning specific */
 	PartitionListValue **all_values = NULL;
 	bool		found_null = false;
+	bool		found_def = false;
+	int			def_index = -1;
 	int			null_index = -1;
 
 	/* Range partitioning specific */
@@ -239,6 +245,8 @@ RelationBuildPartitionDesc(Relation rel)
 			i = 0;
 			found_null = false;
 			null_index = -1;
+			found_def = false;
+			def_index = -1;
 			foreach(cell, boundspecs)
 			{
 				ListCell   *c;
@@ -249,6 +257,15 @@ RelationBuildPartitionDesc(Relation rel)
 
 				foreach(c, spec->listdatums)
 				{
+					Node *value = lfirst(c);
+
+					if (IsA(value, DefElem))
+					{
+						found_def = true;
+						def_index = i;
+						continue;
+					}
+
 					Const	   *val = lfirst(c);
 					PartitionListValue *list_value = NULL;
 
@@ -459,6 +476,7 @@ RelationBuildPartitionDesc(Relation rel)
 			case PARTITION_STRATEGY_LIST:
 				{
 					boundinfo->has_null = found_null;
+					boundinfo->has_def = found_def;
 					boundinfo->indexes = (int *) palloc(ndatums * sizeof(int));
 
 					/*
@@ -496,6 +514,11 @@ RelationBuildPartitionDesc(Relation rel)
 						if (mapping[null_index] == -1)
 							mapping[null_index] = next_index++;
 					}
+					if (found_def)
+					{
+						if (mapping[def_index] == -1)
+							mapping[def_index] = next_index++;
+					}
 
 					/* All partition must now have a valid mapping */
 					Assert(next_index == nparts);
@@ -504,6 +527,11 @@ RelationBuildPartitionDesc(Relation rel)
 						boundinfo->null_index = mapping[null_index];
 					else
 						boundinfo->null_index = -1;
+
+					if (found_def)
+						boundinfo->def_index = mapping[def_index];
+					else
+						boundinfo->def_index = -1;
 					break;
 				}
 
@@ -1558,6 +1586,19 @@ generate_partition_qual(Relation rel)
 	bound = stringToNode(TextDatumGetCString(boundDatum));
 	ReleaseSysCache(tuple);
 
+	/* Return if it is a default list partition */
+	PartitionBoundSpec *spec = (PartitionBoundSpec *)bound;
+	ListCell *cell;
+	foreach(cell, spec->listdatums)
+	{
+		Node *value = lfirst(cell);
+		if (IsA(value, DefElem))
+		{
+			/* Keep the parent locked until commit */
+			heap_close(parent, NoLock);
+			return result;
+		}
+	}
 	my_qual = get_qual_from_partbound(rel, parent, bound);
 
 	/* Add the parent's quals to the list (if any) */
@@ -1771,6 +1812,17 @@ get_partition_for_tuple(PartitionDispatch *pd,
 			result = -1;
 			*failed_at = parent;
 			*failed_slot = slot;
+			/*
+			 * If partitioned table has a default partition, return
+			 * its sequence number
+			 */
+			if (partdesc->boundinfo->has_def)
+				result = parent->indexes[partdesc->boundinfo->def_index];
+			else
+			{
+				result = -1;
+				*failed_at = RelationGetRelid(parent->reldesc);
+			}
 			break;
 		}
 		else if (parent->indexes[cur_index] >= 0)
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 6316688..ebb7db7 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2594,6 +2594,7 @@ partbound_datum:
 			Sconst			{ $$ = makeStringConst($1, @1); }
 			| NumericOnly	{ $$ = makeAConst($1, @1); }
 			| NULL_P		{ $$ = makeNullAConst(@1); }
+			| DEFAULT  { $$ = (Node *)makeDefElem("DEFAULT", NULL, @1); }
 		;
 
 partbound_datum_list:
@@ -2601,7 +2602,6 @@ partbound_datum_list:
 			| partbound_datum_list ',' partbound_datum
 												{ $$ = lappend($1, $3); }
 		;
-
 range_datum_list:
 			PartitionRangeDatum					{ $$ = list_make1($1); }
 			| range_datum_list ',' PartitionRangeDatum
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 673276a..ba05ac8 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3088,47 +3088,50 @@ transformPartitionBound(ParseState *pstate, Relation parent, Node *bound)
 		result_spec->listdatums = NIL;
 		foreach(cell, spec->listdatums)
 		{
-			A_Const    *con = (A_Const *) lfirst(cell);
-			Node	   *value;
-			ListCell   *cell2;
-			bool		duplicate;
-
-			value = (Node *) make_const(pstate, &con->val, con->location);
-			value = coerce_to_target_type(pstate,
-										  value, exprType(value),
-										  get_partition_col_typid(key, 0),
-										  get_partition_col_typmod(key, 0),
-										  COERCION_ASSIGNMENT,
-										  COERCE_IMPLICIT_CAST,
-										  -1);
-
-			if (value == NULL)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
-							 format_type_be(get_partition_col_typid(key, 0)),
-								colname),
-						 parser_errposition(pstate,
-											exprLocation((Node *) con))));
-
-			/* Simplify the expression */
-			value = (Node *) expression_planner((Expr *) value);
-
-			/* Don't add to the result if the value is a duplicate */
-			duplicate = false;
-			foreach(cell2, result_spec->listdatums)
+			Node *value = lfirst(cell);
+			/* Perform the transformation only for non default partition */
+			if (!IsA(value, DefElem))
 			{
-				Const	   *value2 = (Const *) lfirst(cell2);
+				A_Const    *con = (A_Const *) lfirst(cell);
+				ListCell   *cell2;
+				bool		duplicate;
+
+				value = (Node *) make_const(pstate, &con->val, con->location);
+				value = coerce_to_target_type(pstate,
+											value, exprType(value),
+											get_partition_col_typid(key, 0),
+											get_partition_col_typmod(key, 0),
+											COERCION_ASSIGNMENT,
+											COERCE_IMPLICIT_CAST,
+											-1);
+
+				if (value == NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+								format_type_be(get_partition_col_typid(key, 0)),
+									colname),
+							parser_errposition(pstate,
+												exprLocation((Node *) con))));
 
-				if (equal(value, value2))
+				/* Simplify the expression */
+				value = (Node *) expression_planner((Expr *) value);
+
+				/* Don't add to the result if the value is a duplicate */
+				duplicate = false;
+				foreach(cell2, result_spec->listdatums)
 				{
-					duplicate = true;
-					break;
+					Const	   *value2 = (Const *) lfirst(cell2);
+
+					if (equal(value, value2))
+					{
+						duplicate = true;
+						break;
+					}
 				}
+				if (duplicate)
+					continue;
 			}
-			if (duplicate)
-				continue;
-
 			result_spec->listdatums = lappend(result_spec->listdatums,
 											  value);
 		}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 5c82325..ffdf633 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8487,8 +8487,13 @@ get_rule_expr(Node *node, deparse_context *context,
 						sep = "";
 						foreach(cell, spec->listdatums)
 						{
+							Node *value = lfirst(cell);
 							Const	   *val = lfirst(cell);
-
+							if (IsA(value, DefElem))
+							{
+								appendStringInfoString(buf, "DEFAULT");
+								continue;
+							}
 							appendStringInfoString(buf, sep);
 							get_const_expr(val, context, -1);
 							sep = ", ";
#10Rushabh Lathia
rushabh.lathia@gmail.com
In reply to: Rahila Syed (#9)
Re: Adding support for Default partition in partitioning

I picked this for review and noticed that patch is not getting
cleanly complied on my environment.

partition.c: In function ‘RelationBuildPartitionDesc’:
partition.c:269:6: error: ISO C90 forbids mixed declarations and code
[-Werror=declaration-after-statement]
Const *val = lfirst(c);
^
partition.c: In function ‘generate_partition_qual’:
partition.c:1590:2: error: ISO C90 forbids mixed declarations and code
[-Werror=declaration-after-statement]
PartitionBoundSpec *spec = (PartitionBoundSpec *)bound;
^
partition.c: In function ‘get_partition_for_tuple’:
partition.c:1820:5: error: array subscript has type ‘char’
[-Werror=char-subscripts]
result = parent->indexes[partdesc->boundinfo->def_index];
^
partition.c:1824:16: error: assignment makes pointer from integer without a
cast [-Werror]
*failed_at = RelationGetRelid(parent->reldesc);
^
cc1: all warnings being treated as errors

Apart from this, I was reading patch here are few more comments:

1) Variable initializing happening at two place.

@@ -166,6 +170,8 @@ RelationBuildPartitionDesc(Relation rel)
/* List partitioning specific */
PartitionListValue **all_values = NULL;
bool found_null = false;
+ bool found_def = false;
+ int def_index = -1;
int null_index = -1;

/* Range partitioning specific */
@@ -239,6 +245,8 @@ RelationBuildPartitionDesc(Relation rel)
i = 0;
found_null = false;
null_index = -1;
+ found_def = false;
+ def_index = -1;
foreach(cell, boundspecs)
{
ListCell *c;
@@ -249,6 +257,15 @@ RelationBuildPartitionDesc(Relation rel)

2)

@@ -1558,6 +1586,19 @@ generate_partition_qual(Relation rel)
bound = stringToNode(TextDatumGetCString(boundDatum));
ReleaseSysCache(tuple);

+    /* Return if it is a default list partition */
+    PartitionBoundSpec *spec = (PartitionBoundSpec *)bound;
+    ListCell *cell;
+    foreach(cell, spec->listdatums)

More comment on above hunk is needed?

Rather then adding check for default here, I think this should be handle
inside
get_qual_for_list().

3) Code is not aligned with existing

diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 6316688..ebb7db7 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2594,6 +2594,7 @@ partbound_datum:
             Sconst            { $$ = makeStringConst($1, @1); }
             | NumericOnly    { $$ = makeAConst($1, @1); }
             | NULL_P        { $$ = makeNullAConst(@1); }
+            | DEFAULT  { $$ = (Node *)makeDefElem("DEFAULT", NULL, @1); }
         ;

4) Unnecessary hunk:

@@ -2601,7 +2602,6 @@ partbound_datum_list:
| partbound_datum_list ',' partbound_datum
{ $$ = lappend($1, $3); }
;
-

Note: this is just an initially review comments, I am yet to do the
detailed review
and the testing for the patch.

Thanks.

On Mon, Mar 20, 2017 at 9:27 AM, Rahila Syed <rahilasyed90@gmail.com> wrote:

Hello,

Please find attached a rebased patch with support for pg_dump. I am
working on the patch
to handle adding a new partition after a default partition by throwing an
error if
conflicting rows exist in default partition and adding the partition
successfully otherwise.
Will post an updated patch by tomorrow.

Thank you,
Rahila Syed

On Mon, Mar 13, 2017 at 5:42 AM, Robert Haas <robertmhaas@gmail.com>
wrote:

On Fri, Mar 10, 2017 at 2:17 PM, Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:

On 3/2/17 21:40, Robert Haas wrote:

On the point mentioned above, I
don't think adding a partition should move tuples, necessarily; seems
like it would be good enough - maybe better - for it to fail if there
are any that would need to be moved.

ISTM that the uses cases of various combinations of adding a default
partition, adding another partition after it, removing the default
partition, clearing out the default partition in order to add more
nondefault partitions, and so on, need to be more clearly spelled out
for each partitioning type. We also need to consider that pg_dump and
pg_upgrade need to be able to reproduce all those states. Seems to be a
bit of work still ...

This patch is only targeting list partitioning. It is not entirely
clear that the concept makes sense for range partitioning; you can
already define a partition from the end of the last partitioning up to
infinity, or from minus-infinity up to the starting point of the first
partition. The only thing a default range partition would do is let
you do is have a single partition cover all of the ranges that would
otherwise be unassigned, which might not even be something we want.

I don't know how complete the patch is, but the specification seems
clear enough. If you have partitions for 1, 3, and 5, you get
partition constraints of (a = 1), (a = 3), and (a = 5). If you add a
default partition, you get a constraint of (a != 1 and a != 3 and a !=
5). If you then add a partition for 7, the default partition's
constraint becomes (a != 1 and a != 3 and a != 5 and a != 7). The
partition must be revalidated at that point for conflicting rows,
which we can either try to move to the new partition, or just error
out if there are any, depending on what we decide we want to do. I
don't think any of that requires any special handling for either
pg_dump or pg_upgrade; it all just falls out of getting the
partitioning constraints correct and consistently enforcing them, just
as for any other partition.

--
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

--
Rushabh Lathia

#11Rahila Syed
rahilasyed90@gmail.com
In reply to: Rushabh Lathia (#10)
1 attachment(s)
Re: Adding support for Default partition in partitioning

Hello Rushabh,

Thank you for reviewing.
Have addressed all your comments in the attached patch. The attached patch
currently throws an
error if a new partition is added after default partition.

Rather then adding check for default here, I think this should be handle

inside

get_qual_for_list().

Have moved the check inside get_qual_for_partbound() as needed to do some
operations
before calling get_qual_for_list() for default partitions.

Thank you,
Rahila Syed

On Tue, Mar 21, 2017 at 11:36 AM, Rushabh Lathia <rushabh.lathia@gmail.com>
wrote:

Show quoted text

I picked this for review and noticed that patch is not getting
cleanly complied on my environment.

partition.c: In function ‘RelationBuildPartitionDesc’:
partition.c:269:6: error: ISO C90 forbids mixed declarations and code
[-Werror=declaration-after-statement]
Const *val = lfirst(c);
^
partition.c: In function ‘generate_partition_qual’:
partition.c:1590:2: error: ISO C90 forbids mixed declarations and code
[-Werror=declaration-after-statement]
PartitionBoundSpec *spec = (PartitionBoundSpec *)bound;
^
partition.c: In function ‘get_partition_for_tuple’:
partition.c:1820:5: error: array subscript has type ‘char’
[-Werror=char-subscripts]
result = parent->indexes[partdesc->boundinfo->def_index];
^
partition.c:1824:16: error: assignment makes pointer from integer without
a cast [-Werror]
*failed_at = RelationGetRelid(parent->reldesc);
^
cc1: all warnings being treated as errors

Apart from this, I was reading patch here are few more comments:

1) Variable initializing happening at two place.

@@ -166,6 +170,8 @@ RelationBuildPartitionDesc(Relation rel)
/* List partitioning specific */
PartitionListValue **all_values = NULL;
bool found_null = false;
+ bool found_def = false;
+ int def_index = -1;
int null_index = -1;

/* Range partitioning specific */
@@ -239,6 +245,8 @@ RelationBuildPartitionDesc(Relation rel)
i = 0;
found_null = false;
null_index = -1;
+ found_def = false;
+ def_index = -1;
foreach(cell, boundspecs)
{
ListCell *c;
@@ -249,6 +257,15 @@ RelationBuildPartitionDesc(Relation rel)

2)

@@ -1558,6 +1586,19 @@ generate_partition_qual(Relation rel)
bound = stringToNode(TextDatumGetCString(boundDatum));
ReleaseSysCache(tuple);

+    /* Return if it is a default list partition */
+    PartitionBoundSpec *spec = (PartitionBoundSpec *)bound;
+    ListCell *cell;
+    foreach(cell, spec->listdatums)

More comment on above hunk is needed?

Rather then adding check for default here, I think this should be handle
inside
get_qual_for_list().

3) Code is not aligned with existing

diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 6316688..ebb7db7 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2594,6 +2594,7 @@ partbound_datum:
Sconst            { $$ = makeStringConst($1, @1); }
| NumericOnly    { $$ = makeAConst($1, @1); }
| NULL_P        { $$ = makeNullAConst(@1); }
+            | DEFAULT  { $$ = (Node *)makeDefElem("DEFAULT", NULL, @1); }
;

4) Unnecessary hunk:

@@ -2601,7 +2602,6 @@ partbound_datum_list:
| partbound_datum_list ',' partbound_datum
{ $$ = lappend($1, $3); }
;
-

Note: this is just an initially review comments, I am yet to do the
detailed review
and the testing for the patch.

Thanks.

On Mon, Mar 20, 2017 at 9:27 AM, Rahila Syed <rahilasyed90@gmail.com>
wrote:

Hello,

Please find attached a rebased patch with support for pg_dump. I am
working on the patch
to handle adding a new partition after a default partition by throwing an
error if
conflicting rows exist in default partition and adding the partition
successfully otherwise.
Will post an updated patch by tomorrow.

Thank you,
Rahila Syed

On Mon, Mar 13, 2017 at 5:42 AM, Robert Haas <robertmhaas@gmail.com>
wrote:

On Fri, Mar 10, 2017 at 2:17 PM, Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:

On 3/2/17 21:40, Robert Haas wrote:

On the point mentioned above, I
don't think adding a partition should move tuples, necessarily; seems
like it would be good enough - maybe better - for it to fail if there
are any that would need to be moved.

ISTM that the uses cases of various combinations of adding a default
partition, adding another partition after it, removing the default
partition, clearing out the default partition in order to add more
nondefault partitions, and so on, need to be more clearly spelled out
for each partitioning type. We also need to consider that pg_dump and
pg_upgrade need to be able to reproduce all those states. Seems to be

a

bit of work still ...

This patch is only targeting list partitioning. It is not entirely
clear that the concept makes sense for range partitioning; you can
already define a partition from the end of the last partitioning up to
infinity, or from minus-infinity up to the starting point of the first
partition. The only thing a default range partition would do is let
you do is have a single partition cover all of the ranges that would
otherwise be unassigned, which might not even be something we want.

I don't know how complete the patch is, but the specification seems
clear enough. If you have partitions for 1, 3, and 5, you get
partition constraints of (a = 1), (a = 3), and (a = 5). If you add a
default partition, you get a constraint of (a != 1 and a != 3 and a !=
5). If you then add a partition for 7, the default partition's
constraint becomes (a != 1 and a != 3 and a != 5 and a != 7). The
partition must be revalidated at that point for conflicting rows,
which we can either try to move to the new partition, or just error
out if there are any, depending on what we decide we want to do. I
don't think any of that requires any special handling for either
pg_dump or pg_upgrade; it all just falls out of getting the
partitioning constraints correct and consistently enforcing them, just
as for any other partition.

--
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

--
Rushabh Lathia

Attachments:

default_partition_v3.patchapplication/x-download; name=default_partition_v3.patchDownload
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index e01ef86..7bc8a8b 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -90,6 +90,10 @@ typedef struct PartitionBoundInfoData
 								 * for range partitioned tables */
 	int			null_index;		/* Index of the null-accepting partition; -1
 								 * for range partitioned tables */
+	bool		has_def;		/* Is there a default partition? Currently false
+								 * for a range partitioned table */
+	int			def_index;		/* Index of the default list partition. -1 for
+								 * range partitioned tables */
 } PartitionBoundInfoData;
 
 /*
@@ -118,7 +122,8 @@ static int32 qsort_partition_list_value_cmp(const void *a, const void *b,
 static int32 qsort_partition_rbound_cmp(const void *a, const void *b,
 						   void *arg);
 
-static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec,
+								bool is_def, List *boundspecs);
 static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
 static Oid get_partition_operator(PartitionKey key, int col,
 					   StrategyNumber strategy, bool *need_relabel);
@@ -166,6 +171,8 @@ RelationBuildPartitionDesc(Relation rel)
 	/* List partitioning specific */
 	PartitionListValue **all_values = NULL;
 	bool		found_null = false;
+	bool		found_def = false;
+	int			def_index = -1;
 	int			null_index = -1;
 
 	/* Range partitioning specific */
@@ -249,9 +256,16 @@ RelationBuildPartitionDesc(Relation rel)
 
 				foreach(c, spec->listdatums)
 				{
+					Node *value = lfirst(c);
 					Const	   *val = lfirst(c);
 					PartitionListValue *list_value = NULL;
 
+					if (IsA(value, DefElem))
+					{
+						found_def = true;
+						def_index = i;
+						continue;
+					}
 					if (!val->constisnull)
 					{
 						list_value = (PartitionListValue *)
@@ -459,6 +473,7 @@ RelationBuildPartitionDesc(Relation rel)
 			case PARTITION_STRATEGY_LIST:
 				{
 					boundinfo->has_null = found_null;
+					boundinfo->has_def = found_def;
 					boundinfo->indexes = (int *) palloc(ndatums * sizeof(int));
 
 					/*
@@ -496,6 +511,11 @@ RelationBuildPartitionDesc(Relation rel)
 						if (mapping[null_index] == -1)
 							mapping[null_index] = next_index++;
 					}
+					if (found_def)
+					{
+						if (mapping[def_index] == -1)
+							mapping[def_index] = next_index++;
+					}
 
 					/* All partition must now have a valid mapping */
 					Assert(next_index == nparts);
@@ -504,6 +524,11 @@ RelationBuildPartitionDesc(Relation rel)
 						boundinfo->null_index = mapping[null_index];
 					else
 						boundinfo->null_index = -1;
+
+					if (found_def)
+						boundinfo->def_index = mapping[def_index];
+					else
+						boundinfo->def_index = -1;
 					break;
 				}
 
@@ -690,6 +715,12 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 						   boundinfo->strategy == PARTITION_STRATEGY_LIST &&
 						   (boundinfo->ndatums > 0 || boundinfo->has_null));
 
+					if (boundinfo->has_def)
+						ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+								errmsg("parent table \"%s\" has a default partition",
+									RelationGetRelationName(parent))));
+
 					foreach(cell, spec->listdatums)
 					{
 						Const	   *val = lfirst(cell);
@@ -894,6 +925,12 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
 	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
 	PartitionKey key = RelationGetPartitionKey(parent);
 	List	   *my_qual = NIL;
+	List	   *inhoids,
+			   *partoids;
+	List	   *boundspecs = NIL;
+	bool		is_def = false;
+	ListCell 	*cell;
+	ListCell 	*cell2;
 
 	Assert(key != NULL);
 
@@ -901,9 +938,78 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
 	{
 		case PARTITION_STRATEGY_LIST:
 			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
-			my_qual = get_qual_for_list(key, spec);
-			break;
+			foreach(cell, spec->listdatums)
+			{
+				Node *value = lfirst(cell);
+				if (IsA(value, DefElem))
+					is_def = true;
+			}
+			if (is_def)
+			{
+				/* Collect bound spec nodes in a list. This is done if the partition is
+				 * a default partition. In case of default partition, constraint is formed
+				 * by performing <> operation over the partition constraints of the
+				 * existing partitions.
+				 */
+				inhoids = find_inheritance_children(RelationGetRelid(parent), NoLock);
+				partoids = NIL;
+				foreach(cell2, inhoids)
+				{
+					Oid			inhrelid = lfirst_oid(cell2);
+					HeapTuple	tuple;
+					Datum		datum;
+					bool		isnull;
+					Node	   *boundspec;
+					bool		def_elem = false;
+					PartitionBoundSpec *bspec;
+					ListCell *cell1;
+					ListCell *cell3;
+
+					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);
+					boundspec = (Node *) stringToNode(TextDatumGetCString(datum));
+					bspec = (PartitionBoundSpec *)boundspec;
+					foreach(cell1, bspec->listdatums)
+					{
+						Node *value = lfirst(cell1);
+						if (IsA(value, DefElem))
+							def_elem = true;
+					}
+					if (def_elem)
+					{
+						ReleaseSysCache(tuple);
+						continue;
+					}
+					foreach(cell3, bspec->listdatums)
+					{
+						Node *value = lfirst(cell3);
+						boundspecs = lappend(boundspecs, value);
+					}
+					partoids = lappend_oid(partoids, inhrelid);
+					ReleaseSysCache(tuple);
+				}
+			}
+			my_qual = get_qual_for_list(key, spec, is_def, boundspecs);
+			break;
 		case PARTITION_STRATEGY_RANGE:
 			Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
 			my_qual = get_qual_for_range(key, spec);
@@ -1147,7 +1253,8 @@ RelationGetPartitionDispatchInfo(Relation rel, int lockmode,
  * Returns a list of expressions to use as a list partition's constraint.
  */
 static List *
-get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
+get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec, bool is_def,
+					List *boundspecs)
 {
 	List	   *result;
 	ArrayExpr  *arr;
@@ -1225,7 +1332,10 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 		: key->parttypid[0];
 	arr->array_collid = key->parttypcoll[0];
 	arr->element_typeid = key->parttypid[0];
-	arr->elements = spec->listdatums;
+	if (is_def)
+		arr->elements = boundspecs;
+	else
+		arr->elements = spec->listdatums;
 	arr->multidims = false;
 	arr->location = -1;
 
@@ -1239,15 +1349,28 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 										  key->partcollation[0],
 										  COERCE_EXPLICIT_CAST);
 
+	/* Build leftop <> ALL(rightop). This is for default partition */
+	if (is_def)
+	{
+		opexpr = makeNode(ScalarArrayOpExpr);
+		opexpr->opno = 518;
+		opexpr->opfuncid = get_opcode(opexpr->opno);
+		opexpr->useOr = false;
+		opexpr->inputcollid = key->partcollation[0];
+		opexpr->args = list_make2(keyCol, arr);
+		opexpr->location = -1;
+	}
 	/* Build leftop = ANY (rightop) */
-	opexpr = makeNode(ScalarArrayOpExpr);
-	opexpr->opno = operoid;
-	opexpr->opfuncid = get_opcode(operoid);
-	opexpr->useOr = true;
-	opexpr->inputcollid = key->partcollation[0];
-	opexpr->args = list_make2(keyCol, arr);
-	opexpr->location = -1;
-
+	else
+	{
+		opexpr = makeNode(ScalarArrayOpExpr);
+		opexpr->opno = operoid;
+		opexpr->opfuncid = get_opcode(operoid);
+		opexpr->useOr = true;
+		opexpr->inputcollid = key->partcollation[0];
+		opexpr->args = list_make2(keyCol, arr);
+		opexpr->location = -1;
+	}
 	if (nulltest1)
 		result = list_make2(nulltest1, opexpr);
 	else if (nulltest2)
@@ -1771,6 +1894,13 @@ get_partition_for_tuple(PartitionDispatch *pd,
 			result = -1;
 			*failed_at = parent;
 			*failed_slot = slot;
+			/*
+			 * If partitioned table has a default partition, return
+			 * its sequence number
+			 */
+			if (partdesc->boundinfo->has_def && key->strategy
+				== PARTITION_STRATEGY_LIST)
+				result = parent->indexes[partdesc->boundinfo->def_index];
 			break;
 		}
 		else if (parent->indexes[cur_index] >= 0)
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 82844a0..5b21dc9 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2595,6 +2595,7 @@ partbound_datum:
 			Sconst			{ $$ = makeStringConst($1, @1); }
 			| NumericOnly	{ $$ = makeAConst($1, @1); }
 			| NULL_P		{ $$ = makeNullAConst(@1); }
+			| DEFAULT  { $$ = (Node *)makeDefElem("DEFAULT", NULL, @1); }
 		;
 
 partbound_datum_list:
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 673276a..ba05ac8 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3088,47 +3088,50 @@ transformPartitionBound(ParseState *pstate, Relation parent, Node *bound)
 		result_spec->listdatums = NIL;
 		foreach(cell, spec->listdatums)
 		{
-			A_Const    *con = (A_Const *) lfirst(cell);
-			Node	   *value;
-			ListCell   *cell2;
-			bool		duplicate;
-
-			value = (Node *) make_const(pstate, &con->val, con->location);
-			value = coerce_to_target_type(pstate,
-										  value, exprType(value),
-										  get_partition_col_typid(key, 0),
-										  get_partition_col_typmod(key, 0),
-										  COERCION_ASSIGNMENT,
-										  COERCE_IMPLICIT_CAST,
-										  -1);
-
-			if (value == NULL)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
-							 format_type_be(get_partition_col_typid(key, 0)),
-								colname),
-						 parser_errposition(pstate,
-											exprLocation((Node *) con))));
-
-			/* Simplify the expression */
-			value = (Node *) expression_planner((Expr *) value);
-
-			/* Don't add to the result if the value is a duplicate */
-			duplicate = false;
-			foreach(cell2, result_spec->listdatums)
+			Node *value = lfirst(cell);
+			/* Perform the transformation only for non default partition */
+			if (!IsA(value, DefElem))
 			{
-				Const	   *value2 = (Const *) lfirst(cell2);
+				A_Const    *con = (A_Const *) lfirst(cell);
+				ListCell   *cell2;
+				bool		duplicate;
+
+				value = (Node *) make_const(pstate, &con->val, con->location);
+				value = coerce_to_target_type(pstate,
+											value, exprType(value),
+											get_partition_col_typid(key, 0),
+											get_partition_col_typmod(key, 0),
+											COERCION_ASSIGNMENT,
+											COERCE_IMPLICIT_CAST,
+											-1);
+
+				if (value == NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+								format_type_be(get_partition_col_typid(key, 0)),
+									colname),
+							parser_errposition(pstate,
+												exprLocation((Node *) con))));
 
-				if (equal(value, value2))
+				/* Simplify the expression */
+				value = (Node *) expression_planner((Expr *) value);
+
+				/* Don't add to the result if the value is a duplicate */
+				duplicate = false;
+				foreach(cell2, result_spec->listdatums)
 				{
-					duplicate = true;
-					break;
+					Const	   *value2 = (Const *) lfirst(cell2);
+
+					if (equal(value, value2))
+					{
+						duplicate = true;
+						break;
+					}
 				}
+				if (duplicate)
+					continue;
 			}
-			if (duplicate)
-				continue;
-
 			result_spec->listdatums = lappend(result_spec->listdatums,
 											  value);
 		}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 5c82325..ffdf633 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8487,8 +8487,13 @@ get_rule_expr(Node *node, deparse_context *context,
 						sep = "";
 						foreach(cell, spec->listdatums)
 						{
+							Node *value = lfirst(cell);
 							Const	   *val = lfirst(cell);
-
+							if (IsA(value, DefElem))
+							{
+								appendStringInfoString(buf, "DEFAULT");
+								continue;
+							}
 							appendStringInfoString(buf, sep);
 							get_const_expr(val, context, -1);
 							sep = ", ";
#12Rushabh Lathia
rushabh.lathia@gmail.com
In reply to: Rahila Syed (#11)
Re: Adding support for Default partition in partitioning

I applied the patch and was trying to perform some testing, but its
ending up with server crash with the test shared by you in your starting
mail:

postgres=# CREATE TABLE list_partitioned (
postgres(# a int
postgres(# ) PARTITION BY LIST (a);
CREATE TABLE
postgres=#
postgres=# CREATE TABLE part_default PARTITION OF list_partitioned FOR
VALUES IN (DEFAULT);
CREATE TABLE

postgres=# CREATE TABLE part_1 PARTITION OF list_partitioned FOR VALUES IN
(4,5);
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.

Apart from this, few more explanation in the patch is needed to explain the
changes for the DEFAULT partition. Like I am not quite sure what exactly the
latest version of patch supports, like does that support the tuple row
movement,
or adding new partition will be allowed having partition table having
DEFAULT
partition, which is quite difficult to understand through the code changes.

Another part which is missing in the patch is the test coverage, adding
proper test coverage, which explain what is supported and what's not.

Thanks,

On Fri, Mar 24, 2017 at 3:25 PM, Rahila Syed <rahilasyed90@gmail.com> wrote:

Hello Rushabh,

Thank you for reviewing.
Have addressed all your comments in the attached patch. The attached patch
currently throws an
error if a new partition is added after default partition.

Rather then adding check for default here, I think this should be handle

inside

get_qual_for_list().

Have moved the check inside get_qual_for_partbound() as needed to do some
operations
before calling get_qual_for_list() for default partitions.

Thank you,
Rahila Syed

On Tue, Mar 21, 2017 at 11:36 AM, Rushabh Lathia <rushabh.lathia@gmail.com

wrote:

I picked this for review and noticed that patch is not getting
cleanly complied on my environment.

partition.c: In function ‘RelationBuildPartitionDesc’:
partition.c:269:6: error: ISO C90 forbids mixed declarations and code
[-Werror=declaration-after-statement]
Const *val = lfirst(c);
^
partition.c: In function ‘generate_partition_qual’:
partition.c:1590:2: error: ISO C90 forbids mixed declarations and code
[-Werror=declaration-after-statement]
PartitionBoundSpec *spec = (PartitionBoundSpec *)bound;
^
partition.c: In function ‘get_partition_for_tuple’:
partition.c:1820:5: error: array subscript has type ‘char’
[-Werror=char-subscripts]
result = parent->indexes[partdesc->boundinfo->def_index];
^
partition.c:1824:16: error: assignment makes pointer from integer without
a cast [-Werror]
*failed_at = RelationGetRelid(parent->reldesc);
^
cc1: all warnings being treated as errors

Apart from this, I was reading patch here are few more comments:

1) Variable initializing happening at two place.

@@ -166,6 +170,8 @@ RelationBuildPartitionDesc(Relation rel)
/* List partitioning specific */
PartitionListValue **all_values = NULL;
bool found_null = false;
+ bool found_def = false;
+ int def_index = -1;
int null_index = -1;

/* Range partitioning specific */
@@ -239,6 +245,8 @@ RelationBuildPartitionDesc(Relation rel)
i = 0;
found_null = false;
null_index = -1;
+ found_def = false;
+ def_index = -1;
foreach(cell, boundspecs)
{
ListCell *c;
@@ -249,6 +257,15 @@ RelationBuildPartitionDesc(Relation rel)

2)

@@ -1558,6 +1586,19 @@ generate_partition_qual(Relation rel)
bound = stringToNode(TextDatumGetCString(boundDatum));
ReleaseSysCache(tuple);

+    /* Return if it is a default list partition */
+    PartitionBoundSpec *spec = (PartitionBoundSpec *)bound;
+    ListCell *cell;
+    foreach(cell, spec->listdatums)

More comment on above hunk is needed?

Rather then adding check for default here, I think this should be handle
inside
get_qual_for_list().

3) Code is not aligned with existing

diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 6316688..ebb7db7 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2594,6 +2594,7 @@ partbound_datum:
Sconst            { $$ = makeStringConst($1, @1); }
| NumericOnly    { $$ = makeAConst($1, @1); }
| NULL_P        { $$ = makeNullAConst(@1); }
+            | DEFAULT  { $$ = (Node *)makeDefElem("DEFAULT", NULL, @1); }
;

4) Unnecessary hunk:

@@ -2601,7 +2602,6 @@ partbound_datum_list:
| partbound_datum_list ',' partbound_datum
{ $$ = lappend($1, $3); }
;
-

Note: this is just an initially review comments, I am yet to do the
detailed review
and the testing for the patch.

Thanks.

On Mon, Mar 20, 2017 at 9:27 AM, Rahila Syed <rahilasyed90@gmail.com>
wrote:

Hello,

Please find attached a rebased patch with support for pg_dump. I am
working on the patch
to handle adding a new partition after a default partition by throwing
an error if
conflicting rows exist in default partition and adding the partition
successfully otherwise.
Will post an updated patch by tomorrow.

Thank you,
Rahila Syed

On Mon, Mar 13, 2017 at 5:42 AM, Robert Haas <robertmhaas@gmail.com>
wrote:

On Fri, Mar 10, 2017 at 2:17 PM, Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:

On 3/2/17 21:40, Robert Haas wrote:

On the point mentioned above, I
don't think adding a partition should move tuples, necessarily; seems
like it would be good enough - maybe better - for it to fail if there
are any that would need to be moved.

ISTM that the uses cases of various combinations of adding a default
partition, adding another partition after it, removing the default
partition, clearing out the default partition in order to add more
nondefault partitions, and so on, need to be more clearly spelled out
for each partitioning type. We also need to consider that pg_dump and
pg_upgrade need to be able to reproduce all those states. Seems to

be a

bit of work still ...

This patch is only targeting list partitioning. It is not entirely
clear that the concept makes sense for range partitioning; you can
already define a partition from the end of the last partitioning up to
infinity, or from minus-infinity up to the starting point of the first
partition. The only thing a default range partition would do is let
you do is have a single partition cover all of the ranges that would
otherwise be unassigned, which might not even be something we want.

I don't know how complete the patch is, but the specification seems
clear enough. If you have partitions for 1, 3, and 5, you get
partition constraints of (a = 1), (a = 3), and (a = 5). If you add a
default partition, you get a constraint of (a != 1 and a != 3 and a !=
5). If you then add a partition for 7, the default partition's
constraint becomes (a != 1 and a != 3 and a != 5 and a != 7). The
partition must be revalidated at that point for conflicting rows,
which we can either try to move to the new partition, or just error
out if there are any, depending on what we decide we want to do. I
don't think any of that requires any special handling for either
pg_dump or pg_upgrade; it all just falls out of getting the
partitioning constraints correct and consistently enforcing them, just
as for any other partition.

--
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

--
Rushabh Lathia

--
Rushabh Lathia

#13Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Rushabh Lathia (#12)
Re: Adding support for Default partition in partitioning

Hi Rahila,

IIUC, your default_partition_v3.patch is trying to implement an error if new
partition is added to a table already having a default partition.

I too tried to run the test and similar to Rushabh, I see the server is
crashing
with the given test.

However, if I reverse the order of creating the partitions, i.e. if I
create a
partition with list first and later create the default partition.

The reason is, while defining new relation DefineRelation() checks for
overlapping partitions by calling check_new_partition_bound(). Where in case
of list partitions it assumes that the ndatums should be > 0, but in case of
default partition that is 0.

The crash here seems to be coming because, following assertion getting
failed in
function check_new_partition_bound():

Assert(boundinfo &&
boundinfo->strategy == PARTITION_STRATEGY_LIST &&
(boundinfo->ndatums > 0 || boundinfo->has_null));

So, I think the error you have added needs to be moved before this
assertion:

@@ -690,6 +715,12 @@ check_new_partition_bound(char *relname, Relation
parent, Node *bound)
boundinfo->strategy == PARTITION_STRATEGY_LIST &&
(boundinfo->ndatums > 0 || boundinfo->has_null));

+ if (boundinfo->has_def)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("parent table \"%s\" has a default partition",
+ RelationGetRelationName(parent))));

If I do so, the server does not run into crash, and instead throws an error:

postgres=# CREATE TABLE list_partitioned (
a int
) PARTITION BY LIST (a);
CREATE TABLE
postgres=# CREATE TABLE part_default PARTITION OF list_partitioned FOR
VALUES IN (DEFAULT);
CREATE TABLE
postgres=# CREATE TABLE part_1 PARTITION OF list_partitioned FOR VALUES IN
(4,5);
ERROR: parent table "list_partitioned" has a default partition

Regards,
Jeevan Ladhe

On Mon, Mar 27, 2017 at 12:10 PM, Rushabh Lathia <rushabh.lathia@gmail.com>
wrote:

Show quoted text

I applied the patch and was trying to perform some testing, but its
ending up with server crash with the test shared by you in your starting
mail:

postgres=# CREATE TABLE list_partitioned (
postgres(# a int
postgres(# ) PARTITION BY LIST (a);
CREATE TABLE
postgres=#
postgres=# CREATE TABLE part_default PARTITION OF list_partitioned FOR
VALUES IN (DEFAULT);
CREATE TABLE

postgres=# CREATE TABLE part_1 PARTITION OF list_partitioned FOR VALUES IN
(4,5);
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.

Apart from this, few more explanation in the patch is needed to explain the
changes for the DEFAULT partition. Like I am not quite sure what exactly
the
latest version of patch supports, like does that support the tuple row
movement,
or adding new partition will be allowed having partition table having
DEFAULT
partition, which is quite difficult to understand through the code changes.

Another part which is missing in the patch is the test coverage, adding
proper test coverage, which explain what is supported and what's not.

Thanks,

On Fri, Mar 24, 2017 at 3:25 PM, Rahila Syed <rahilasyed90@gmail.com>
wrote:

Hello Rushabh,

Thank you for reviewing.
Have addressed all your comments in the attached patch. The attached
patch currently throws an
error if a new partition is added after default partition.

Rather then adding check for default here, I think this should be

handle inside

get_qual_for_list().

Have moved the check inside get_qual_for_partbound() as needed to do some
operations
before calling get_qual_for_list() for default partitions.

Thank you,
Rahila Syed

On Tue, Mar 21, 2017 at 11:36 AM, Rushabh Lathia <
rushabh.lathia@gmail.com> wrote:

I picked this for review and noticed that patch is not getting
cleanly complied on my environment.

partition.c: In function ‘RelationBuildPartitionDesc’:
partition.c:269:6: error: ISO C90 forbids mixed declarations and code
[-Werror=declaration-after-statement]
Const *val = lfirst(c);
^
partition.c: In function ‘generate_partition_qual’:
partition.c:1590:2: error: ISO C90 forbids mixed declarations and code
[-Werror=declaration-after-statement]
PartitionBoundSpec *spec = (PartitionBoundSpec *)bound;
^
partition.c: In function ‘get_partition_for_tuple’:
partition.c:1820:5: error: array subscript has type ‘char’
[-Werror=char-subscripts]
result = parent->indexes[partdesc->boundinfo->def_index];
^
partition.c:1824:16: error: assignment makes pointer from integer
without a cast [-Werror]
*failed_at = RelationGetRelid(parent->reldesc);
^
cc1: all warnings being treated as errors

Apart from this, I was reading patch here are few more comments:

1) Variable initializing happening at two place.

@@ -166,6 +170,8 @@ RelationBuildPartitionDesc(Relation rel)
/* List partitioning specific */
PartitionListValue **all_values = NULL;
bool found_null = false;
+ bool found_def = false;
+ int def_index = -1;
int null_index = -1;

/* Range partitioning specific */
@@ -239,6 +245,8 @@ RelationBuildPartitionDesc(Relation rel)
i = 0;
found_null = false;
null_index = -1;
+ found_def = false;
+ def_index = -1;
foreach(cell, boundspecs)
{
ListCell *c;
@@ -249,6 +257,15 @@ RelationBuildPartitionDesc(Relation rel)

2)

@@ -1558,6 +1586,19 @@ generate_partition_qual(Relation rel)
bound = stringToNode(TextDatumGetCString(boundDatum));
ReleaseSysCache(tuple);

+    /* Return if it is a default list partition */
+    PartitionBoundSpec *spec = (PartitionBoundSpec *)bound;
+    ListCell *cell;
+    foreach(cell, spec->listdatums)

More comment on above hunk is needed?

Rather then adding check for default here, I think this should be handle
inside
get_qual_for_list().

3) Code is not aligned with existing

diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 6316688..ebb7db7 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2594,6 +2594,7 @@ partbound_datum:
Sconst            { $$ = makeStringConst($1, @1); }
| NumericOnly    { $$ = makeAConst($1, @1); }
| NULL_P        { $$ = makeNullAConst(@1); }
+            | DEFAULT  { $$ = (Node *)makeDefElem("DEFAULT", NULL, @1);
}
;

4) Unnecessary hunk:

@@ -2601,7 +2602,6 @@ partbound_datum_list:
| partbound_datum_list ',' partbound_datum
{ $$ = lappend($1, $3);
}
;
-

Note: this is just an initially review comments, I am yet to do the
detailed review
and the testing for the patch.

Thanks.

On Mon, Mar 20, 2017 at 9:27 AM, Rahila Syed <rahilasyed90@gmail.com>
wrote:

Hello,

Please find attached a rebased patch with support for pg_dump. I am
working on the patch
to handle adding a new partition after a default partition by throwing
an error if
conflicting rows exist in default partition and adding the partition
successfully otherwise.
Will post an updated patch by tomorrow.

Thank you,
Rahila Syed

On Mon, Mar 13, 2017 at 5:42 AM, Robert Haas <robertmhaas@gmail.com>
wrote:

On Fri, Mar 10, 2017 at 2:17 PM, Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:

On 3/2/17 21:40, Robert Haas wrote:

On the point mentioned above, I
don't think adding a partition should move tuples, necessarily;

seems

like it would be good enough - maybe better - for it to fail if

there

are any that would need to be moved.

ISTM that the uses cases of various combinations of adding a default
partition, adding another partition after it, removing the default
partition, clearing out the default partition in order to add more
nondefault partitions, and so on, need to be more clearly spelled out
for each partitioning type. We also need to consider that pg_dump

and

pg_upgrade need to be able to reproduce all those states. Seems to

be a

bit of work still ...

This patch is only targeting list partitioning. It is not entirely
clear that the concept makes sense for range partitioning; you can
already define a partition from the end of the last partitioning up to
infinity, or from minus-infinity up to the starting point of the first
partition. The only thing a default range partition would do is let
you do is have a single partition cover all of the ranges that would
otherwise be unassigned, which might not even be something we want.

I don't know how complete the patch is, but the specification seems
clear enough. If you have partitions for 1, 3, and 5, you get
partition constraints of (a = 1), (a = 3), and (a = 5). If you add a
default partition, you get a constraint of (a != 1 and a != 3 and a !=
5). If you then add a partition for 7, the default partition's
constraint becomes (a != 1 and a != 3 and a != 5 and a != 7). The
partition must be revalidated at that point for conflicting rows,
which we can either try to move to the new partition, or just error
out if there are any, depending on what we decide we want to do. I
don't think any of that requires any special handling for either
pg_dump or pg_upgrade; it all just falls out of getting the
partitioning constraints correct and consistently enforcing them, just
as for any other partition.

--
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

--
Rushabh Lathia

--
Rushabh Lathia

#14Rahila Syed
rahilasyed90@gmail.com
In reply to: Rushabh Lathia (#12)
Re: Adding support for Default partition in partitioning

Thanks for reporting. I have identified the problem and have a fix.
Currently working on allowing
adding a partition after default partition if the default partition does
not have any conflicting rows.
Will update the patch with both of these.

Thank you,
Rahila Syed

On Mon, Mar 27, 2017 at 12:10 PM, Rushabh Lathia <rushabh.lathia@gmail.com>
wrote:

Show quoted text

I applied the patch and was trying to perform some testing, but its
ending up with server crash with the test shared by you in your starting
mail:

postgres=# CREATE TABLE list_partitioned (
postgres(# a int
postgres(# ) PARTITION BY LIST (a);
CREATE TABLE
postgres=#
postgres=# CREATE TABLE part_default PARTITION OF list_partitioned FOR
VALUES IN (DEFAULT);
CREATE TABLE

postgres=# CREATE TABLE part_1 PARTITION OF list_partitioned FOR VALUES IN
(4,5);
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.

Apart from this, few more explanation in the patch is needed to explain the
changes for the DEFAULT partition. Like I am not quite sure what exactly
the
latest version of patch supports, like does that support the tuple row
movement,
or adding new partition will be allowed having partition table having
DEFAULT
partition, which is quite difficult to understand through the code changes.

Another part which is missing in the patch is the test coverage, adding
proper test coverage, which explain what is supported and what's not.

Thanks,

On Fri, Mar 24, 2017 at 3:25 PM, Rahila Syed <rahilasyed90@gmail.com>
wrote:

Hello Rushabh,

Thank you for reviewing.
Have addressed all your comments in the attached patch. The attached
patch currently throws an
error if a new partition is added after default partition.

Rather then adding check for default here, I think this should be

handle inside

get_qual_for_list().

Have moved the check inside get_qual_for_partbound() as needed to do some
operations
before calling get_qual_for_list() for default partitions.

Thank you,
Rahila Syed

On Tue, Mar 21, 2017 at 11:36 AM, Rushabh Lathia <
rushabh.lathia@gmail.com> wrote:

I picked this for review and noticed that patch is not getting
cleanly complied on my environment.

partition.c: In function ‘RelationBuildPartitionDesc’:
partition.c:269:6: error: ISO C90 forbids mixed declarations and code
[-Werror=declaration-after-statement]
Const *val = lfirst(c);
^
partition.c: In function ‘generate_partition_qual’:
partition.c:1590:2: error: ISO C90 forbids mixed declarations and code
[-Werror=declaration-after-statement]
PartitionBoundSpec *spec = (PartitionBoundSpec *)bound;
^
partition.c: In function ‘get_partition_for_tuple’:
partition.c:1820:5: error: array subscript has type ‘char’
[-Werror=char-subscripts]
result = parent->indexes[partdesc->boundinfo->def_index];
^
partition.c:1824:16: error: assignment makes pointer from integer
without a cast [-Werror]
*failed_at = RelationGetRelid(parent->reldesc);
^
cc1: all warnings being treated as errors

Apart from this, I was reading patch here are few more comments:

1) Variable initializing happening at two place.

@@ -166,6 +170,8 @@ RelationBuildPartitionDesc(Relation rel)
/* List partitioning specific */
PartitionListValue **all_values = NULL;
bool found_null = false;
+ bool found_def = false;
+ int def_index = -1;
int null_index = -1;

/* Range partitioning specific */
@@ -239,6 +245,8 @@ RelationBuildPartitionDesc(Relation rel)
i = 0;
found_null = false;
null_index = -1;
+ found_def = false;
+ def_index = -1;
foreach(cell, boundspecs)
{
ListCell *c;
@@ -249,6 +257,15 @@ RelationBuildPartitionDesc(Relation rel)

2)

@@ -1558,6 +1586,19 @@ generate_partition_qual(Relation rel)
bound = stringToNode(TextDatumGetCString(boundDatum));
ReleaseSysCache(tuple);

+    /* Return if it is a default list partition */
+    PartitionBoundSpec *spec = (PartitionBoundSpec *)bound;
+    ListCell *cell;
+    foreach(cell, spec->listdatums)

More comment on above hunk is needed?

Rather then adding check for default here, I think this should be handle
inside
get_qual_for_list().

3) Code is not aligned with existing

diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 6316688..ebb7db7 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2594,6 +2594,7 @@ partbound_datum:
Sconst            { $$ = makeStringConst($1, @1); }
| NumericOnly    { $$ = makeAConst($1, @1); }
| NULL_P        { $$ = makeNullAConst(@1); }
+            | DEFAULT  { $$ = (Node *)makeDefElem("DEFAULT", NULL, @1);
}
;

4) Unnecessary hunk:

@@ -2601,7 +2602,6 @@ partbound_datum_list:
| partbound_datum_list ',' partbound_datum
{ $$ = lappend($1, $3);
}
;
-

Note: this is just an initially review comments, I am yet to do the
detailed review
and the testing for the patch.

Thanks.

On Mon, Mar 20, 2017 at 9:27 AM, Rahila Syed <rahilasyed90@gmail.com>
wrote:

Hello,

Please find attached a rebased patch with support for pg_dump. I am
working on the patch
to handle adding a new partition after a default partition by throwing
an error if
conflicting rows exist in default partition and adding the partition
successfully otherwise.
Will post an updated patch by tomorrow.

Thank you,
Rahila Syed

On Mon, Mar 13, 2017 at 5:42 AM, Robert Haas <robertmhaas@gmail.com>
wrote:

On Fri, Mar 10, 2017 at 2:17 PM, Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:

On 3/2/17 21:40, Robert Haas wrote:

On the point mentioned above, I
don't think adding a partition should move tuples, necessarily;

seems

like it would be good enough - maybe better - for it to fail if

there

are any that would need to be moved.

ISTM that the uses cases of various combinations of adding a default
partition, adding another partition after it, removing the default
partition, clearing out the default partition in order to add more
nondefault partitions, and so on, need to be more clearly spelled out
for each partitioning type. We also need to consider that pg_dump

and

pg_upgrade need to be able to reproduce all those states. Seems to

be a

bit of work still ...

This patch is only targeting list partitioning. It is not entirely
clear that the concept makes sense for range partitioning; you can
already define a partition from the end of the last partitioning up to
infinity, or from minus-infinity up to the starting point of the first
partition. The only thing a default range partition would do is let
you do is have a single partition cover all of the ranges that would
otherwise be unassigned, which might not even be something we want.

I don't know how complete the patch is, but the specification seems
clear enough. If you have partitions for 1, 3, and 5, you get
partition constraints of (a = 1), (a = 3), and (a = 5). If you add a
default partition, you get a constraint of (a != 1 and a != 3 and a !=
5). If you then add a partition for 7, the default partition's
constraint becomes (a != 1 and a != 3 and a != 5 and a != 7). The
partition must be revalidated at that point for conflicting rows,
which we can either try to move to the new partition, or just error
out if there are any, depending on what we decide we want to do. I
don't think any of that requires any special handling for either
pg_dump or pg_upgrade; it all just falls out of getting the
partitioning constraints correct and consistently enforcing them, just
as for any other partition.

--
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

--
Rushabh Lathia

--
Rushabh Lathia

#15David Steele
david@pgmasters.net
In reply to: Rahila Syed (#14)
Re: Adding support for Default partition in partitioning

On 3/29/17 8:13 AM, Rahila Syed wrote:

Thanks for reporting. I have identified the problem and have a fix.
Currently working on allowing
adding a partition after default partition if the default partition does
not have any conflicting rows.
Will update the patch with both of these.

The CF has been extended but until April 7 but time is still growing
short. Please respond with a new patch by 2017-04-04 00:00 AoE (UTC-12)
or this submission will be marked "Returned with Feedback".

Thanks,
--
-David
david@pgmasters.net

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#16David Steele
david@pgmasters.net
In reply to: David Steele (#15)
Re: Adding support for Default partition in partitioning

On 3/31/17 10:45 AM, David Steele wrote:

On 3/29/17 8:13 AM, Rahila Syed wrote:

Thanks for reporting. I have identified the problem and have a fix.
Currently working on allowing
adding a partition after default partition if the default partition does
not have any conflicting rows.
Will update the patch with both of these.

The CF has been extended but until April 7 but time is still growing
short. Please respond with a new patch by 2017-04-04 00:00 AoE (UTC-12)
or this submission will be marked "Returned with Feedback".

This submission has been marked "Returned with Feedback". Please feel
free to resubmit to a future commitfest.

Regards,
--
-David
david@pgmasters.net

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#17Rahila Syed
rahilasyed90@gmail.com
In reply to: David Steele (#16)
1 attachment(s)
Re: Adding support for Default partition in partitioning

Hello,

Please find attached an updated patch.
Following has been accomplished in this update:

1. A new partition can be added after default partition if there are no
conflicting rows in default partition.
2. Solved the crash reported earlier.

Thank you,
Rahila Syed

On Tue, Apr 4, 2017 at 6:22 PM, David Steele <david@pgmasters.net> wrote:

Show quoted text

On 3/31/17 10:45 AM, David Steele wrote:

On 3/29/17 8:13 AM, Rahila Syed wrote:

Thanks for reporting. I have identified the problem and have a fix.
Currently working on allowing
adding a partition after default partition if the default partition does
not have any conflicting rows.
Will update the patch with both of these.

The CF has been extended but until April 7 but time is still growing
short. Please respond with a new patch by 2017-04-04 00:00 AoE (UTC-12)
or this submission will be marked "Returned with Feedback".

This submission has been marked "Returned with Feedback". Please feel
free to resubmit to a future commitfest.

Regards,
--
-David
david@pgmasters.net

Attachments:

default_partition_v4.patchapplication/x-download; name=default_partition_v4.patchDownload
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index ab891f6..f5a9f44 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -34,6 +34,7 @@
 #include "nodes/nodeFuncs.h"
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
+#include "optimizer/prep.h"
 #include "optimizer/planmain.h"
 #include "optimizer/var.h"
 #include "rewrite/rewriteManip.h"
@@ -90,6 +91,10 @@ typedef struct PartitionBoundInfoData
 								 * for range partitioned tables */
 	int			null_index;		/* Index of the null-accepting partition; -1
 								 * for range partitioned tables */
+	bool		has_def;		/* Is there a default partition? Currently false
+								 * for a range partitioned table */
+	int			def_index;		/* Index of the default list partition. -1 for
+								 * range partitioned tables */
 } PartitionBoundInfoData;
 
 /*
@@ -118,7 +123,8 @@ static int32 qsort_partition_list_value_cmp(const void *a, const void *b,
 static int32 qsort_partition_rbound_cmp(const void *a, const void *b,
 						   void *arg);
 
-static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec,
+								bool is_def, List *boundspecs);
 static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
 static Oid get_partition_operator(PartitionKey key, int col,
 					   StrategyNumber strategy, bool *need_relabel);
@@ -166,6 +172,8 @@ RelationBuildPartitionDesc(Relation rel)
 	/* List partitioning specific */
 	PartitionListValue **all_values = NULL;
 	bool		found_null = false;
+	bool		found_def = false;
+	int			def_index = -1;
 	int			null_index = -1;
 
 	/* Range partitioning specific */
@@ -249,9 +257,16 @@ RelationBuildPartitionDesc(Relation rel)
 
 				foreach(c, spec->listdatums)
 				{
+					Node *value = lfirst(c);
 					Const	   *val = lfirst(c);
 					PartitionListValue *list_value = NULL;
 
+					if (IsA(value, DefElem))
+					{
+						found_def = true;
+						def_index = i;
+						continue;
+					}
 					if (!val->constisnull)
 					{
 						list_value = (PartitionListValue *)
@@ -459,6 +474,7 @@ RelationBuildPartitionDesc(Relation rel)
 			case PARTITION_STRATEGY_LIST:
 				{
 					boundinfo->has_null = found_null;
+					boundinfo->has_def = found_def;
 					boundinfo->indexes = (int *) palloc(ndatums * sizeof(int));
 
 					/*
@@ -496,6 +512,11 @@ RelationBuildPartitionDesc(Relation rel)
 						if (mapping[null_index] == -1)
 							mapping[null_index] = next_index++;
 					}
+					if (found_def)
+					{
+						if (mapping[def_index] == -1)
+							mapping[def_index] = next_index++;
+					}
 
 					/* All partition must now have a valid mapping */
 					Assert(next_index == nparts);
@@ -504,6 +525,11 @@ RelationBuildPartitionDesc(Relation rel)
 						boundinfo->null_index = mapping[null_index];
 					else
 						boundinfo->null_index = -1;
+
+					if (found_def)
+						boundinfo->def_index = mapping[def_index];
+					else
+						boundinfo->def_index = -1;
 					break;
 				}
 
@@ -671,6 +697,7 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
 	PartitionKey key = RelationGetPartitionKey(parent);
 	PartitionDesc partdesc = RelationGetPartitionDesc(parent);
+	PartitionBoundInfo boundinfo;
 	ParseState *pstate = make_parsestate(NULL);
 	int			with = -1;
 	bool		overlap = false;
@@ -679,6 +706,8 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 	{
 		case PARTITION_STRATEGY_LIST:
 			{
+				boundinfo = partdesc->boundinfo;
+
 				Assert(spec->strategy == PARTITION_STRATEGY_LIST);
 
 				if (partdesc->nparts > 0)
@@ -688,7 +717,8 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 
 					Assert(boundinfo &&
 						   boundinfo->strategy == PARTITION_STRATEGY_LIST &&
-						   (boundinfo->ndatums > 0 || boundinfo->has_null));
+						   (boundinfo->ndatums > 0 || boundinfo->has_null
+							|| boundinfo->has_def));
 
 					foreach(cell, spec->listdatums)
 					{
@@ -836,6 +866,76 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 						relname, get_rel_name(partdesc->oids[with])),
 				 parser_errposition(pstate, spec->location)));
 	}
+
+	/*
+	 * When adding a list partition after default partition, scan the
+	 * default partiton for rows satisfying the new partition
+	 * constraint. If found dont 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_def)
+	{
+		List	       *partConstraint = NIL;
+		ExprContext    *econtext;
+		EState	       *estate;
+		Relation	    defrel;
+		HeapScanDesc    scan;
+		HeapTuple	    tuple;
+		ExprState	   *partqualstate = NULL;
+		Snapshot	    snapshot;
+		Oid			    defid;
+		MemoryContext   oldCxt;
+		TupleTableSlot *tupslot;
+		TupleDesc	    tupdesc;
+
+		partConstraint = generate_qual_for_defaultpart(parent, bound, &defid);
+		partConstraint = (List *) eval_const_expressions(NULL,
+													(Node *) partConstraint);
+		partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
+		partConstraint = list_make1(make_ands_explicit(partConstraint));
+
+		/*
+		 * Generate the constraint and default execution states
+		 */
+		estate = CreateExecutorState();
+
+		defrel = heap_open(defid, AccessShareLock);
+		tupdesc = CreateTupleDescCopy(RelationGetDescr(defrel));
+
+		/* Build expression execution states for partition check quals */
+		partqualstate = ExecPrepareExpr((Expr *) partConstraint,
+						estate);
+
+		econtext = GetPerTupleExprContext(estate);
+		snapshot = RegisterSnapshot(GetLatestSnapshot());
+		scan = heap_beginscan(defrel, snapshot, 0, NULL);
+		tupslot = MakeSingleTupleTableSlot(tupdesc);
+
+		/*
+		 * Switch to per-tuple memory context and reset it for each tuple
+		 * produced, so we don't leak memory.
+		 */
+		oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		{
+			ExecStoreTuple(tuple, tupslot, InvalidBuffer, false);
+			econtext->ecxt_scantuple = tupslot;
+			if (partqualstate && !ExecCheck(partqualstate, econtext))
+				ereport(ERROR,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+				errmsg("new default partition constraint is violated by some row")));
+			ResetExprContext(econtext);
+			CHECK_FOR_INTERRUPTS();
+		}
+		MemoryContextSwitchTo(oldCxt);
+		heap_endscan(scan);
+		UnregisterSnapshot(snapshot);
+		heap_close(defrel, AccessShareLock);
+		ExecDropSingleTupleTableSlot(tupslot);
+	}
+
 }
 
 /*
@@ -884,6 +984,90 @@ get_partition_parent(Oid relid)
 }
 
 /*
+ * Return a list of executable expressions as new partition constraint
+ * for default partition while adding a new partition after default
+ */
+List *
+generate_qual_for_defaultpart(Relation parent, Node *bound, Oid * defid)
+{
+	PartitionKey key = RelationGetPartitionKey(parent);
+	PartitionBoundSpec *spec;
+	List	   *partConstraint = NIL;
+	List	   *inhoids;
+	ListCell   *cell2;
+	ListCell   *cell4;
+	List       *boundspecs = NIL;
+	bool		is_def = true;
+
+	spec = (PartitionBoundSpec *) bound;
+
+	inhoids = find_inheritance_children(RelationGetRelid(parent), NoLock);
+
+	foreach(cell2, inhoids)
+	{
+		PartitionBoundSpec *bspec;
+		Oid			inhrelid = lfirst_oid(cell2);
+		HeapTuple	tuple;
+		Datum		datum;
+		bool		isnull;
+		Node	   *boundspec;
+		bool		def_elem = false;
+		ListCell   *cell1;
+		ListCell   *cell3;
+
+		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);
+		boundspec = (Node *) stringToNode(TextDatumGetCString(datum));
+		bspec = (PartitionBoundSpec *)boundspec;
+		foreach(cell1, bspec->listdatums)
+		{
+			Node *value = lfirst(cell1);
+			if (IsA(value, DefElem))
+			{
+				def_elem = true;
+				*defid = inhrelid;
+			}
+		}
+		if (def_elem)
+		{
+			ReleaseSysCache(tuple);
+			continue;
+		}
+		foreach(cell3, bspec->listdatums)
+		{
+			Node *value = lfirst(cell3);
+			boundspecs = lappend(boundspecs, value);
+		}
+		ReleaseSysCache(tuple);
+	}
+	foreach(cell4, spec->listdatums)
+	{
+		Node *value = lfirst(cell4);
+		boundspecs = lappend(boundspecs, value);
+	}
+	partConstraint = get_qual_for_list(key, spec, is_def, boundspecs);
+	return partConstraint;
+}
+/*
  * get_qual_from_partbound
  *		Given a parser node for partition bound, return the list of executable
  *		expressions as partition constraint
@@ -894,6 +1078,12 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
 	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
 	PartitionKey key = RelationGetPartitionKey(parent);
 	List	   *my_qual = NIL;
+	List	   *inhoids,
+			   *partoids;
+	List	   *boundspecs = NIL;
+	bool		is_def = false;
+	ListCell   *cell;
+	ListCell   *cell2;
 
 	Assert(key != NULL);
 
@@ -901,9 +1091,78 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
 	{
 		case PARTITION_STRATEGY_LIST:
 			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
-			my_qual = get_qual_for_list(key, spec);
-			break;
+			foreach(cell, spec->listdatums)
+			{
+				Node *value = lfirst(cell);
+				if (IsA(value, DefElem))
+					is_def = true;
+			}
+			if (is_def)
+			{
+				/* Collect bound spec nodes in a list. This is done if the partition is
+				 * a default partition. In case of default partition, constraint is formed
+				 * by performing <> operation over the partition constraints of the
+				 * existing partitions.
+				 */
+				inhoids = find_inheritance_children(RelationGetRelid(parent), NoLock);
+				partoids = NIL;
+				foreach(cell2, inhoids)
+				{
+					Oid			inhrelid = lfirst_oid(cell2);
+					HeapTuple	tuple;
+					Datum		datum;
+					bool		isnull;
+					Node	   *boundspec;
+					bool		def_elem = false;
+					PartitionBoundSpec *bspec;
+					ListCell *cell1;
+					ListCell *cell3;
+
+					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);
+					boundspec = (Node *) stringToNode(TextDatumGetCString(datum));
+					bspec = (PartitionBoundSpec *)boundspec;
+					foreach(cell1, bspec->listdatums)
+					{
+						Node *value = lfirst(cell1);
+						if (IsA(value, DefElem))
+							def_elem = true;
+					}
+					if (def_elem)
+					{
+						ReleaseSysCache(tuple);
+						continue;
+					}
+					foreach(cell3, bspec->listdatums)
+					{
+						Node *value = lfirst(cell3);
+						boundspecs = lappend(boundspecs, value);
+					}
+					partoids = lappend_oid(partoids, inhrelid);
+					ReleaseSysCache(tuple);
+				}
+			}
+			my_qual = get_qual_for_list(key, spec, is_def, boundspecs);
+			break;
 		case PARTITION_STRATEGY_RANGE:
 			Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
 			my_qual = get_qual_for_range(key, spec);
@@ -1151,7 +1410,8 @@ RelationGetPartitionDispatchInfo(Relation rel, int lockmode,
  * Returns a list of expressions to use as a list partition's constraint.
  */
 static List *
-get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
+get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec, bool is_def,
+					List *boundspecs)
 {
 	List	   *result;
 	ArrayExpr  *arr;
@@ -1229,7 +1489,10 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 		: key->parttypid[0];
 	arr->array_collid = key->parttypcoll[0];
 	arr->element_typeid = key->parttypid[0];
-	arr->elements = spec->listdatums;
+	if (is_def)
+		arr->elements = boundspecs;
+	else
+		arr->elements = spec->listdatums;
 	arr->multidims = false;
 	arr->location = -1;
 
@@ -1243,15 +1506,28 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 										  key->partcollation[0],
 										  COERCE_EXPLICIT_CAST);
 
+	/* Build leftop <> ALL(rightop). This is for default partition */
+	if (is_def)
+	{
+		opexpr = makeNode(ScalarArrayOpExpr);
+		opexpr->opno = 518;
+		opexpr->opfuncid = get_opcode(opexpr->opno);
+		opexpr->useOr = false;
+		opexpr->inputcollid = key->partcollation[0];
+		opexpr->args = list_make2(keyCol, arr);
+		opexpr->location = -1;
+	}
 	/* Build leftop = ANY (rightop) */
-	opexpr = makeNode(ScalarArrayOpExpr);
-	opexpr->opno = operoid;
-	opexpr->opfuncid = get_opcode(operoid);
-	opexpr->useOr = true;
-	opexpr->inputcollid = key->partcollation[0];
-	opexpr->args = list_make2(keyCol, arr);
-	opexpr->location = -1;
-
+	else
+	{
+		opexpr = makeNode(ScalarArrayOpExpr);
+		opexpr->opno = operoid;
+		opexpr->opfuncid = get_opcode(operoid);
+		opexpr->useOr = true;
+		opexpr->inputcollid = key->partcollation[0];
+		opexpr->args = list_make2(keyCol, arr);
+		opexpr->location = -1;
+	}
 	if (nulltest1)
 		result = list_make2(nulltest1, opexpr);
 	else if (nulltest2)
@@ -1778,6 +2054,13 @@ get_partition_for_tuple(PartitionDispatch *pd,
 			result = -1;
 			*failed_at = parent;
 			*failed_slot = slot;
+			/*
+			 * If partitioned table has a default partition, return
+			 * its sequence number
+			 */
+			if (partdesc->boundinfo->has_def && key->strategy
+				== PARTITION_STRATEGY_LIST)
+				result = parent->indexes[partdesc->boundinfo->def_index];
 			break;
 		}
 		else if (parent->indexes[cur_index] >= 0)
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d418d56..69268b1 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -750,6 +750,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		ParseState *pstate;
 		Oid			parentId = linitial_oid(inheritOids);
 		Relation	parent;
+		PartitionDesc partdesc;
 
 		/* Already have strong enough lock on the parent */
 		parent = heap_open(parentId, NoLock);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9d53a29..7114d7f 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2596,6 +2596,7 @@ partbound_datum:
 			Sconst			{ $$ = makeStringConst($1, @1); }
 			| NumericOnly	{ $$ = makeAConst($1, @1); }
 			| NULL_P		{ $$ = makeNullAConst(@1); }
+			| DEFAULT  { $$ = (Node *)makeDefElem("DEFAULT", NULL, @1); }
 		;
 
 partbound_datum_list:
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 1ae43dc..17605c3 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3088,47 +3088,50 @@ transformPartitionBound(ParseState *pstate, Relation parent, Node *bound)
 		result_spec->listdatums = NIL;
 		foreach(cell, spec->listdatums)
 		{
-			A_Const    *con = (A_Const *) lfirst(cell);
-			Node	   *value;
-			ListCell   *cell2;
-			bool		duplicate;
-
-			value = (Node *) make_const(pstate, &con->val, con->location);
-			value = coerce_to_target_type(pstate,
-										  value, exprType(value),
-										  get_partition_col_typid(key, 0),
-										  get_partition_col_typmod(key, 0),
-										  COERCION_ASSIGNMENT,
-										  COERCE_IMPLICIT_CAST,
-										  -1);
-
-			if (value == NULL)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
-							 format_type_be(get_partition_col_typid(key, 0)),
-								colname),
-						 parser_errposition(pstate,
-											exprLocation((Node *) con))));
-
-			/* Simplify the expression */
-			value = (Node *) expression_planner((Expr *) value);
-
-			/* Don't add to the result if the value is a duplicate */
-			duplicate = false;
-			foreach(cell2, result_spec->listdatums)
+			Node *value = lfirst(cell);
+			/* Perform the transformation only for non default partition */
+			if (!IsA(value, DefElem))
 			{
-				Const	   *value2 = (Const *) lfirst(cell2);
+				A_Const    *con = (A_Const *) lfirst(cell);
+				ListCell   *cell2;
+				bool		duplicate;
+
+				value = (Node *) make_const(pstate, &con->val, con->location);
+				value = coerce_to_target_type(pstate,
+											value, exprType(value),
+											get_partition_col_typid(key, 0),
+											get_partition_col_typmod(key, 0),
+											COERCION_ASSIGNMENT,
+											COERCE_IMPLICIT_CAST,
+											-1);
+
+				if (value == NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+								format_type_be(get_partition_col_typid(key, 0)),
+									colname),
+							parser_errposition(pstate,
+												exprLocation((Node *) con))));
 
-				if (equal(value, value2))
+				/* Simplify the expression */
+				value = (Node *) expression_planner((Expr *) value);
+
+				/* Don't add to the result if the value is a duplicate */
+				duplicate = false;
+				foreach(cell2, result_spec->listdatums)
 				{
-					duplicate = true;
-					break;
+					Const	   *value2 = (Const *) lfirst(cell2);
+
+					if (equal(value, value2))
+					{
+						duplicate = true;
+						break;
+					}
 				}
+				if (duplicate)
+					continue;
 			}
-			if (duplicate)
-				continue;
-
 			result_spec->listdatums = lappend(result_spec->listdatums,
 											  value);
 		}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 0c1a201..5732bbc 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8559,8 +8559,14 @@ get_rule_expr(Node *node, deparse_context *context,
 						sep = "";
 						foreach(cell, spec->listdatums)
 						{
+							Node *value = lfirst(cell);
 							Const	   *val = lfirst(cell);
 
+							if (IsA(value, DefElem))
+							{
+								appendStringInfoString(buf, "DEFAULT");
+								continue;
+							}
 							appendStringInfoString(buf, sep);
 							get_const_expr(val, context, -1);
 							sep = ", ";
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 421644c..9c92a8a 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -77,6 +77,7 @@ extern bool partition_bounds_equal(PartitionKey key,
 extern void check_new_partition_bound(char *relname, Relation parent, Node *bound);
 extern Oid	get_partition_parent(Oid relid);
 extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
+extern List *generate_qual_for_defaultpart(Relation parent, Node *bound ,Oid *defid);
 extern List *map_partition_varattnos(List *expr, int target_varno,
 						Relation partrel, Relation parent);
 extern List *RelationGetPartitionQual(Relation rel);
#18Keith Fiske
keith@omniti.com
In reply to: Rahila Syed (#17)
Re: Adding support for Default partition in partitioning

On Tue, Apr 4, 2017 at 9:30 AM, Rahila Syed <rahilasyed90@gmail.com> wrote:

Hello,

Please find attached an updated patch.
Following has been accomplished in this update:

1. A new partition can be added after default partition if there are no
conflicting rows in default partition.
2. Solved the crash reported earlier.

Thank you,
Rahila Syed

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Installed and compiled against commit
60a0b2ec8943451186dfa22907f88334d97cb2e0 (Date: Tue Apr 4 12:36:15 2017
-0400) without any issue

However, running your original example, I'm getting this error

keith@keith=# CREATE TABLE list_partitioned (
keith(# a int
keith(# ) PARTITION BY LIST (a);
CREATE TABLE
Time: 4.933 ms
keith@keith=# CREATE TABLE part_default PARTITION OF list_partitioned FOR
VALUES IN (DEFAULT);
CREATE TABLE
Time: 3.492 ms
keith@keith=# CREATE TABLE part_1 PARTITION OF list_partitioned FOR VALUES
IN (4,5);
ERROR: unrecognized node type: 216
Time: 0.979 ms

Also, I'm still of the opinion that denying future partitions of values in
the default would be a cause of confusion. In order to move the data out of
the default and into a proper child it would require first removing that
data from the default, storing it elsewhere, creating the child, then
moving it back. If it's only a small amount of data it may not be a big
deal, but if it's a large amount, that could cause quite a lot of
contention if done in a single transaction. Either that or the user would
have to deal with data existing in the table, disappearing, then
reappearing again.

This also makes it harder to migrate an existing table easily. Essentially
no child tables for a large, existing data set could ever be created
without going through one of the two situations above.

However, thinking through this, I'm not sure I can see a solution without
the global index support. If this restriction is removed, there's still an
issue of data duplication after the necessary child table is added. So
guess it's a matter of deciding which user experience is better for the
moment?

--
Keith Fiske
Database Administrator
OmniTI Computer Consulting, Inc.
http://www.keithf4.com

#19Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Keith Fiske (#18)
Re: Adding support for Default partition in partitioning

On 2017/04/05 6:22, Keith Fiske wrote:

On Tue, Apr 4, 2017 at 9:30 AM, Rahila Syed wrote:

Please find attached an updated patch.
Following has been accomplished in this update:

1. A new partition can be added after default partition if there are no
conflicting rows in default partition.
2. Solved the crash reported earlier.

Installed and compiled against commit
60a0b2ec8943451186dfa22907f88334d97cb2e0 (Date: Tue Apr 4 12:36:15 2017
-0400) without any issue

However, running your original example, I'm getting this error

keith@keith=# CREATE TABLE list_partitioned (
keith(# a int
keith(# ) PARTITION BY LIST (a);
CREATE TABLE
Time: 4.933 ms
keith@keith=# CREATE TABLE part_default PARTITION OF list_partitioned FOR
VALUES IN (DEFAULT);
CREATE TABLE
Time: 3.492 ms
keith@keith=# CREATE TABLE part_1 PARTITION OF list_partitioned FOR VALUES
IN (4,5);
ERROR: unrecognized node type: 216

It seems like the new ExecPrepareCheck should be used in the place of
ExecPrepareExpr in the code added in check_new_partition_bound().

Also, I'm still of the opinion that denying future partitions of values in
the default would be a cause of confusion. In order to move the data out of
the default and into a proper child it would require first removing that
data from the default, storing it elsewhere, creating the child, then
moving it back. If it's only a small amount of data it may not be a big
deal, but if it's a large amount, that could cause quite a lot of
contention if done in a single transaction. Either that or the user would
have to deal with data existing in the table, disappearing, then
reappearing again.

This also makes it harder to migrate an existing table easily. Essentially
no child tables for a large, existing data set could ever be created
without going through one of the two situations above.

I thought of the following possible way to allow future partitions when
the default partition exists which might contain rows that belong to the
newly created partition (unfortunately, nothing that we could implement at
this point for v10):

Suppose you want to add a new partition which will accept key=3 rows.

1. If no default partition exists, we're done; no key=3 rows would have
been accepted by any of the table's existing partitions, so no need to
move any rows

2. Default partition exists which might contain key=3 rows, which we'll
need to move. If we do this in the same transaction, as you say, it
might result in unnecessary unavailability of table's data. So, it's
better to delegate that responsibility to a background process. The
current transaction will only add the new key=3 partition, so any key=3
rows will be routed to the new partition from this point on. But we
haven't updated the default partition's constraint yet to say that it
no longer contains key=3 rows (constraint that the planner consumes),
so it will continue to be scanned for queries that request key=3 rows
(there should be some metadata to indicate that the default partition's
constraint is invalid), along with the new partition.

3. A background process receives a "work item" requesting it to move all
key=3 rows from the default partition heap to the new partition's heap.
The movement occurs without causing the table to become unavailable;
once all rows have been moved, we momentarily lock the table to update
the default partition's constraint to mark it valid, so that it will
no longer be accessed by queries that want to see key=3 rows.

Regarding 2, there is a question of whether it should not be possible for
the row movement to occur in the same transaction. Somebody may want that
to happen because they chose to run the command during a maintenance
window, when the table's becoming unavailable is not an issue. In that
case, we need to think of the interface more carefully.

Regarding 3, I think the new autovacuum work items infrastructure added by
the following commit looks very promising:

* BRIN auto-summarization *
https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=7526e10224f0792201e99631567bbe44492bbde4

However, thinking through this, I'm not sure I can see a solution without
the global index support. If this restriction is removed, there's still an
issue of data duplication after the necessary child table is added. So
guess it's a matter of deciding which user experience is better for the
moment?

I'm not sure about the fate of this particular patch for v10, but until we
implement a solution to move rows and design appropriate interface for the
same, we could error out if moving rows is required at all, like the patch
does.

Could you briefly elaborate why you think the lack global index support
would be a problem in this regard?

I agree that some design is required here to implement a solution
redistribution of rows; not only in the context of supporting the notion
of default partitions, but also to allow the feature to split/merge range
(only?) partitions. I'd like to work on the latter for v11 for which I
would like to post a proposal soon; if anyone would like to collaborate
(ideas, code, review), I look forward to. (sorry for hijacking this thread.)

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#20Rushabh Lathia
rushabh.lathia@gmail.com
In reply to: Amit Langote (#19)
Re: Adding support for Default partition in partitioning

On Wed, Apr 5, 2017 at 10:59 AM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp

wrote:

On 2017/04/05 6:22, Keith Fiske wrote:

On Tue, Apr 4, 2017 at 9:30 AM, Rahila Syed wrote:

Please find attached an updated patch.
Following has been accomplished in this update:

1. A new partition can be added after default partition if there are no
conflicting rows in default partition.
2. Solved the crash reported earlier.

Installed and compiled against commit
60a0b2ec8943451186dfa22907f88334d97cb2e0 (Date: Tue Apr 4 12:36:15 2017
-0400) without any issue

However, running your original example, I'm getting this error

keith@keith=# CREATE TABLE list_partitioned (
keith(# a int
keith(# ) PARTITION BY LIST (a);
CREATE TABLE
Time: 4.933 ms
keith@keith=# CREATE TABLE part_default PARTITION OF list_partitioned

FOR

VALUES IN (DEFAULT);
CREATE TABLE
Time: 3.492 ms
keith@keith=# CREATE TABLE part_1 PARTITION OF list_partitioned FOR

VALUES

IN (4,5);
ERROR: unrecognized node type: 216

It seems like the new ExecPrepareCheck should be used in the place of
ExecPrepareExpr in the code added in check_new_partition_bound().

Also, I'm still of the opinion that denying future partitions of values

in

the default would be a cause of confusion. In order to move the data out

of

the default and into a proper child it would require first removing that
data from the default, storing it elsewhere, creating the child, then
moving it back. If it's only a small amount of data it may not be a big
deal, but if it's a large amount, that could cause quite a lot of
contention if done in a single transaction. Either that or the user would
have to deal with data existing in the table, disappearing, then
reappearing again.

This also makes it harder to migrate an existing table easily.

Essentially

no child tables for a large, existing data set could ever be created
without going through one of the two situations above.

I thought of the following possible way to allow future partitions when
the default partition exists which might contain rows that belong to the
newly created partition (unfortunately, nothing that we could implement at
this point for v10):

Suppose you want to add a new partition which will accept key=3 rows.

1. If no default partition exists, we're done; no key=3 rows would have
been accepted by any of the table's existing partitions, so no need to
move any rows

2. Default partition exists which might contain key=3 rows, which we'll
need to move. If we do this in the same transaction, as you say, it
might result in unnecessary unavailability of table's data. So, it's
better to delegate that responsibility to a background process. The
current transaction will only add the new key=3 partition, so any key=3
rows will be routed to the new partition from this point on. But we
haven't updated the default partition's constraint yet to say that it
no longer contains key=3 rows (constraint that the planner consumes),
so it will continue to be scanned for queries that request key=3 rows
(there should be some metadata to indicate that the default partition's
constraint is invalid), along with the new partition.

3. A background process receives a "work item" requesting it to move all
key=3 rows from the default partition heap to the new partition's heap.
The movement occurs without causing the table to become unavailable;
once all rows have been moved, we momentarily lock the table to update
the default partition's constraint to mark it valid, so that it will
no longer be accessed by queries that want to see key=3 rows.

Regarding 2, there is a question of whether it should not be possible for
the row movement to occur in the same transaction. Somebody may want that
to happen because they chose to run the command during a maintenance
window, when the table's becoming unavailable is not an issue. In that
case, we need to think of the interface more carefully.

Regarding 3, I think the new autovacuum work items infrastructure added by
the following commit looks very promising:

* BRIN auto-summarization *
https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=
7526e10224f0792201e99631567bbe44492bbde4

However, thinking through this, I'm not sure I can see a solution without
the global index support. If this restriction is removed, there's still

an

issue of data duplication after the necessary child table is added. So
guess it's a matter of deciding which user experience is better for the
moment?

I'm not sure about the fate of this particular patch for v10, but until we
implement a solution to move rows and design appropriate interface for the
same, we could error out if moving rows is required at all, like the patch
does.

+1

I agree about the future plan about the row movement, how that is I am
not quite sure at this stage.

I was thinking that CREATE new partition is the DDL command, so even
if row-movement works with holding the lock on the new partition table,
that should be fine. I am not quire sure, why row movement should be
happen in the back-ground process.

Of-course, one idea is that if someone don't want feature of row-movement,
then we might add that under some GUC or may be as another option into
the CREATE partition table.

Could you briefly elaborate why you think the lack global index support

would be a problem in this regard?

I agree that some design is required here to implement a solution
redistribution of rows; not only in the context of supporting the notion
of default partitions, but also to allow the feature to split/merge range
(only?) partitions. I'd like to work on the latter for v11 for which I
would like to post a proposal soon; if anyone would like to collaborate
(ideas, code, review), I look forward to. (sorry for hijacking this
thread.)

Thanks,
Amit

--
Rushabh Lathia

#21Rahila Syed
rahilasyed90@gmail.com
In reply to: Keith Fiske (#18)
1 attachment(s)
Re: Adding support for Default partition in partitioning

Hi,

However, running your original example, I'm getting this error

Thank you for testing. Please find attached an updated patch which fixes
the above.

Thank you,
Rahila Syed

Attachments:

default_partition_v5.patchapplication/x-download; name=default_partition_v5.patchDownload
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index ab891f6..3aaf59f 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -34,6 +34,7 @@
 #include "nodes/nodeFuncs.h"
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
+#include "optimizer/prep.h"
 #include "optimizer/planmain.h"
 #include "optimizer/var.h"
 #include "rewrite/rewriteManip.h"
@@ -90,6 +91,10 @@ typedef struct PartitionBoundInfoData
 								 * for range partitioned tables */
 	int			null_index;		/* Index of the null-accepting partition; -1
 								 * for range partitioned tables */
+	bool		has_def;		/* Is there a default partition? Currently false
+								 * for a range partitioned table */
+	int			def_index;		/* Index of the default list partition. -1 for
+								 * range partitioned tables */
 } PartitionBoundInfoData;
 
 /*
@@ -118,7 +123,8 @@ static int32 qsort_partition_list_value_cmp(const void *a, const void *b,
 static int32 qsort_partition_rbound_cmp(const void *a, const void *b,
 						   void *arg);
 
-static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec,
+								bool is_def, List *boundspecs);
 static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
 static Oid get_partition_operator(PartitionKey key, int col,
 					   StrategyNumber strategy, bool *need_relabel);
@@ -166,6 +172,8 @@ RelationBuildPartitionDesc(Relation rel)
 	/* List partitioning specific */
 	PartitionListValue **all_values = NULL;
 	bool		found_null = false;
+	bool		found_def = false;
+	int			def_index = -1;
 	int			null_index = -1;
 
 	/* Range partitioning specific */
@@ -249,9 +257,16 @@ RelationBuildPartitionDesc(Relation rel)
 
 				foreach(c, spec->listdatums)
 				{
+					Node *value = lfirst(c);
 					Const	   *val = lfirst(c);
 					PartitionListValue *list_value = NULL;
 
+					if (IsA(value, DefElem))
+					{
+						found_def = true;
+						def_index = i;
+						continue;
+					}
 					if (!val->constisnull)
 					{
 						list_value = (PartitionListValue *)
@@ -459,6 +474,7 @@ RelationBuildPartitionDesc(Relation rel)
 			case PARTITION_STRATEGY_LIST:
 				{
 					boundinfo->has_null = found_null;
+					boundinfo->has_def = found_def;
 					boundinfo->indexes = (int *) palloc(ndatums * sizeof(int));
 
 					/*
@@ -496,6 +512,11 @@ RelationBuildPartitionDesc(Relation rel)
 						if (mapping[null_index] == -1)
 							mapping[null_index] = next_index++;
 					}
+					if (found_def)
+					{
+						if (mapping[def_index] == -1)
+							mapping[def_index] = next_index++;
+					}
 
 					/* All partition must now have a valid mapping */
 					Assert(next_index == nparts);
@@ -504,6 +525,11 @@ RelationBuildPartitionDesc(Relation rel)
 						boundinfo->null_index = mapping[null_index];
 					else
 						boundinfo->null_index = -1;
+
+					if (found_def)
+						boundinfo->def_index = mapping[def_index];
+					else
+						boundinfo->def_index = -1;
 					break;
 				}
 
@@ -671,6 +697,7 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
 	PartitionKey key = RelationGetPartitionKey(parent);
 	PartitionDesc partdesc = RelationGetPartitionDesc(parent);
+	PartitionBoundInfo boundinfo;
 	ParseState *pstate = make_parsestate(NULL);
 	int			with = -1;
 	bool		overlap = false;
@@ -679,6 +706,8 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 	{
 		case PARTITION_STRATEGY_LIST:
 			{
+				boundinfo = partdesc->boundinfo;
+
 				Assert(spec->strategy == PARTITION_STRATEGY_LIST);
 
 				if (partdesc->nparts > 0)
@@ -688,7 +717,8 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 
 					Assert(boundinfo &&
 						   boundinfo->strategy == PARTITION_STRATEGY_LIST &&
-						   (boundinfo->ndatums > 0 || boundinfo->has_null));
+						   (boundinfo->ndatums > 0 || boundinfo->has_null
+							|| boundinfo->has_def));
 
 					foreach(cell, spec->listdatums)
 					{
@@ -836,6 +866,76 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 						relname, get_rel_name(partdesc->oids[with])),
 				 parser_errposition(pstate, spec->location)));
 	}
+
+	/*
+	 * When adding a list partition after default partition, scan the
+	 * default partiton for rows satisfying the new partition
+	 * constraint. If found dont 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_def)
+	{
+		List	       *partConstraint = NIL;
+		ExprContext    *econtext;
+		EState	       *estate;
+		Relation	    defrel;
+		HeapScanDesc    scan;
+		HeapTuple	    tuple;
+		ExprState	   *partqualstate = NULL;
+		Snapshot	    snapshot;
+		Oid			    defid;
+		MemoryContext   oldCxt;
+		TupleTableSlot *tupslot;
+		TupleDesc	    tupdesc;
+
+		partConstraint = generate_qual_for_defaultpart(parent, bound, &defid);
+		partConstraint = (List *) eval_const_expressions(NULL,
+													(Node *) partConstraint);
+		partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
+		partConstraint = list_make1(make_ands_explicit(partConstraint));
+
+		/*
+		 * Generate the constraint and default execution states
+		 */
+		estate = CreateExecutorState();
+
+		defrel = heap_open(defid, AccessShareLock);
+		tupdesc = CreateTupleDescCopy(RelationGetDescr(defrel));
+
+		/* Build expression execution states for partition check quals */
+		partqualstate = ExecPrepareCheck(partConstraint,
+						estate);
+
+		econtext = GetPerTupleExprContext(estate);
+		snapshot = RegisterSnapshot(GetLatestSnapshot());
+		scan = heap_beginscan(defrel, snapshot, 0, NULL);
+		tupslot = MakeSingleTupleTableSlot(tupdesc);
+
+		/*
+		 * Switch to per-tuple memory context and reset it for each tuple
+		 * produced, so we don't leak memory.
+		 */
+		oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		{
+			ExecStoreTuple(tuple, tupslot, InvalidBuffer, false);
+			econtext->ecxt_scantuple = tupslot;
+			if (partqualstate && !ExecCheck(partqualstate, econtext))
+				ereport(ERROR,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+				errmsg("new default partition constraint is violated by some row")));
+			ResetExprContext(econtext);
+			CHECK_FOR_INTERRUPTS();
+		}
+		MemoryContextSwitchTo(oldCxt);
+		heap_endscan(scan);
+		UnregisterSnapshot(snapshot);
+		heap_close(defrel, AccessShareLock);
+		ExecDropSingleTupleTableSlot(tupslot);
+	}
+
 }
 
 /*
@@ -884,6 +984,90 @@ get_partition_parent(Oid relid)
 }
 
 /*
+ * Return a list of executable expressions as new partition constraint
+ * for default partition while adding a new partition after default
+ */
+List *
+generate_qual_for_defaultpart(Relation parent, Node *bound, Oid * defid)
+{
+	PartitionKey key = RelationGetPartitionKey(parent);
+	PartitionBoundSpec *spec;
+	List	   *partConstraint = NIL;
+	List	   *inhoids;
+	ListCell   *cell2;
+	ListCell   *cell4;
+	List       *boundspecs = NIL;
+	bool		is_def = true;
+
+	spec = (PartitionBoundSpec *) bound;
+
+	inhoids = find_inheritance_children(RelationGetRelid(parent), NoLock);
+
+	foreach(cell2, inhoids)
+	{
+		PartitionBoundSpec *bspec;
+		Oid			inhrelid = lfirst_oid(cell2);
+		HeapTuple	tuple;
+		Datum		datum;
+		bool		isnull;
+		Node	   *boundspec;
+		bool		def_elem = false;
+		ListCell   *cell1;
+		ListCell   *cell3;
+
+		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);
+		boundspec = (Node *) stringToNode(TextDatumGetCString(datum));
+		bspec = (PartitionBoundSpec *)boundspec;
+		foreach(cell1, bspec->listdatums)
+		{
+			Node *value = lfirst(cell1);
+			if (IsA(value, DefElem))
+			{
+				def_elem = true;
+				*defid = inhrelid;
+			}
+		}
+		if (def_elem)
+		{
+			ReleaseSysCache(tuple);
+			continue;
+		}
+		foreach(cell3, bspec->listdatums)
+		{
+			Node *value = lfirst(cell3);
+			boundspecs = lappend(boundspecs, value);
+		}
+		ReleaseSysCache(tuple);
+	}
+	foreach(cell4, spec->listdatums)
+	{
+		Node *value = lfirst(cell4);
+		boundspecs = lappend(boundspecs, value);
+	}
+	partConstraint = get_qual_for_list(key, spec, is_def, boundspecs);
+	return partConstraint;
+}
+/*
  * get_qual_from_partbound
  *		Given a parser node for partition bound, return the list of executable
  *		expressions as partition constraint
@@ -894,6 +1078,12 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
 	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
 	PartitionKey key = RelationGetPartitionKey(parent);
 	List	   *my_qual = NIL;
+	List	   *inhoids,
+			   *partoids;
+	List	   *boundspecs = NIL;
+	bool		is_def = false;
+	ListCell   *cell;
+	ListCell   *cell2;
 
 	Assert(key != NULL);
 
@@ -901,9 +1091,78 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
 	{
 		case PARTITION_STRATEGY_LIST:
 			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
-			my_qual = get_qual_for_list(key, spec);
-			break;
+			foreach(cell, spec->listdatums)
+			{
+				Node *value = lfirst(cell);
+				if (IsA(value, DefElem))
+					is_def = true;
+			}
+			if (is_def)
+			{
+				/* Collect bound spec nodes in a list. This is done if the partition is
+				 * a default partition. In case of default partition, constraint is formed
+				 * by performing <> operation over the partition constraints of the
+				 * existing partitions.
+				 */
+				inhoids = find_inheritance_children(RelationGetRelid(parent), NoLock);
+				partoids = NIL;
+				foreach(cell2, inhoids)
+				{
+					Oid			inhrelid = lfirst_oid(cell2);
+					HeapTuple	tuple;
+					Datum		datum;
+					bool		isnull;
+					Node	   *boundspec;
+					bool		def_elem = false;
+					PartitionBoundSpec *bspec;
+					ListCell *cell1;
+					ListCell *cell3;
+
+					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);
+					boundspec = (Node *) stringToNode(TextDatumGetCString(datum));
+					bspec = (PartitionBoundSpec *)boundspec;
+					foreach(cell1, bspec->listdatums)
+					{
+						Node *value = lfirst(cell1);
+						if (IsA(value, DefElem))
+							def_elem = true;
+					}
+					if (def_elem)
+					{
+						ReleaseSysCache(tuple);
+						continue;
+					}
+					foreach(cell3, bspec->listdatums)
+					{
+						Node *value = lfirst(cell3);
+						boundspecs = lappend(boundspecs, value);
+					}
+					partoids = lappend_oid(partoids, inhrelid);
+					ReleaseSysCache(tuple);
+				}
+			}
+			my_qual = get_qual_for_list(key, spec, is_def, boundspecs);
+			break;
 		case PARTITION_STRATEGY_RANGE:
 			Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
 			my_qual = get_qual_for_range(key, spec);
@@ -1151,7 +1410,8 @@ RelationGetPartitionDispatchInfo(Relation rel, int lockmode,
  * Returns a list of expressions to use as a list partition's constraint.
  */
 static List *
-get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
+get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec, bool is_def,
+					List *boundspecs)
 {
 	List	   *result;
 	ArrayExpr  *arr;
@@ -1229,7 +1489,10 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 		: key->parttypid[0];
 	arr->array_collid = key->parttypcoll[0];
 	arr->element_typeid = key->parttypid[0];
-	arr->elements = spec->listdatums;
+	if (is_def)
+		arr->elements = boundspecs;
+	else
+		arr->elements = spec->listdatums;
 	arr->multidims = false;
 	arr->location = -1;
 
@@ -1243,15 +1506,28 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 										  key->partcollation[0],
 										  COERCE_EXPLICIT_CAST);
 
+	/* Build leftop <> ALL(rightop). This is for default partition */
+	if (is_def)
+	{
+		opexpr = makeNode(ScalarArrayOpExpr);
+		opexpr->opno = 518;
+		opexpr->opfuncid = get_opcode(opexpr->opno);
+		opexpr->useOr = false;
+		opexpr->inputcollid = key->partcollation[0];
+		opexpr->args = list_make2(keyCol, arr);
+		opexpr->location = -1;
+	}
 	/* Build leftop = ANY (rightop) */
-	opexpr = makeNode(ScalarArrayOpExpr);
-	opexpr->opno = operoid;
-	opexpr->opfuncid = get_opcode(operoid);
-	opexpr->useOr = true;
-	opexpr->inputcollid = key->partcollation[0];
-	opexpr->args = list_make2(keyCol, arr);
-	opexpr->location = -1;
-
+	else
+	{
+		opexpr = makeNode(ScalarArrayOpExpr);
+		opexpr->opno = operoid;
+		opexpr->opfuncid = get_opcode(operoid);
+		opexpr->useOr = true;
+		opexpr->inputcollid = key->partcollation[0];
+		opexpr->args = list_make2(keyCol, arr);
+		opexpr->location = -1;
+	}
 	if (nulltest1)
 		result = list_make2(nulltest1, opexpr);
 	else if (nulltest2)
@@ -1778,6 +2054,13 @@ get_partition_for_tuple(PartitionDispatch *pd,
 			result = -1;
 			*failed_at = parent;
 			*failed_slot = slot;
+			/*
+			 * If partitioned table has a default partition, return
+			 * its sequence number
+			 */
+			if (partdesc->boundinfo->has_def && key->strategy
+				== PARTITION_STRATEGY_LIST)
+				result = parent->indexes[partdesc->boundinfo->def_index];
 			break;
 		}
 		else if (parent->indexes[cur_index] >= 0)
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d418d56..69268b1 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -750,6 +750,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		ParseState *pstate;
 		Oid			parentId = linitial_oid(inheritOids);
 		Relation	parent;
+		PartitionDesc partdesc;
 
 		/* Already have strong enough lock on the parent */
 		parent = heap_open(parentId, NoLock);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9d53a29..7114d7f 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2596,6 +2596,7 @@ partbound_datum:
 			Sconst			{ $$ = makeStringConst($1, @1); }
 			| NumericOnly	{ $$ = makeAConst($1, @1); }
 			| NULL_P		{ $$ = makeNullAConst(@1); }
+			| DEFAULT  { $$ = (Node *)makeDefElem("DEFAULT", NULL, @1); }
 		;
 
 partbound_datum_list:
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 1ae43dc..17605c3 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3088,47 +3088,50 @@ transformPartitionBound(ParseState *pstate, Relation parent, Node *bound)
 		result_spec->listdatums = NIL;
 		foreach(cell, spec->listdatums)
 		{
-			A_Const    *con = (A_Const *) lfirst(cell);
-			Node	   *value;
-			ListCell   *cell2;
-			bool		duplicate;
-
-			value = (Node *) make_const(pstate, &con->val, con->location);
-			value = coerce_to_target_type(pstate,
-										  value, exprType(value),
-										  get_partition_col_typid(key, 0),
-										  get_partition_col_typmod(key, 0),
-										  COERCION_ASSIGNMENT,
-										  COERCE_IMPLICIT_CAST,
-										  -1);
-
-			if (value == NULL)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
-							 format_type_be(get_partition_col_typid(key, 0)),
-								colname),
-						 parser_errposition(pstate,
-											exprLocation((Node *) con))));
-
-			/* Simplify the expression */
-			value = (Node *) expression_planner((Expr *) value);
-
-			/* Don't add to the result if the value is a duplicate */
-			duplicate = false;
-			foreach(cell2, result_spec->listdatums)
+			Node *value = lfirst(cell);
+			/* Perform the transformation only for non default partition */
+			if (!IsA(value, DefElem))
 			{
-				Const	   *value2 = (Const *) lfirst(cell2);
+				A_Const    *con = (A_Const *) lfirst(cell);
+				ListCell   *cell2;
+				bool		duplicate;
+
+				value = (Node *) make_const(pstate, &con->val, con->location);
+				value = coerce_to_target_type(pstate,
+											value, exprType(value),
+											get_partition_col_typid(key, 0),
+											get_partition_col_typmod(key, 0),
+											COERCION_ASSIGNMENT,
+											COERCE_IMPLICIT_CAST,
+											-1);
+
+				if (value == NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+								format_type_be(get_partition_col_typid(key, 0)),
+									colname),
+							parser_errposition(pstate,
+												exprLocation((Node *) con))));
 
-				if (equal(value, value2))
+				/* Simplify the expression */
+				value = (Node *) expression_planner((Expr *) value);
+
+				/* Don't add to the result if the value is a duplicate */
+				duplicate = false;
+				foreach(cell2, result_spec->listdatums)
 				{
-					duplicate = true;
-					break;
+					Const	   *value2 = (Const *) lfirst(cell2);
+
+					if (equal(value, value2))
+					{
+						duplicate = true;
+						break;
+					}
 				}
+				if (duplicate)
+					continue;
 			}
-			if (duplicate)
-				continue;
-
 			result_spec->listdatums = lappend(result_spec->listdatums,
 											  value);
 		}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 0c1a201..5732bbc 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8559,8 +8559,14 @@ get_rule_expr(Node *node, deparse_context *context,
 						sep = "";
 						foreach(cell, spec->listdatums)
 						{
+							Node *value = lfirst(cell);
 							Const	   *val = lfirst(cell);
 
+							if (IsA(value, DefElem))
+							{
+								appendStringInfoString(buf, "DEFAULT");
+								continue;
+							}
 							appendStringInfoString(buf, sep);
 							get_const_expr(val, context, -1);
 							sep = ", ";
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 421644c..9c92a8a 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -77,6 +77,7 @@ extern bool partition_bounds_equal(PartitionKey key,
 extern void check_new_partition_bound(char *relname, Relation parent, Node *bound);
 extern Oid	get_partition_parent(Oid relid);
 extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
+extern List *generate_qual_for_defaultpart(Relation parent, Node *bound ,Oid *defid);
 extern List *map_partition_varattnos(List *expr, int target_varno,
 						Relation partrel, Relation parent);
 extern List *RelationGetPartitionQual(Relation rel);
#22Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Rushabh Lathia (#20)
Re: Adding support for Default partition in partitioning

On 2017/04/05 14:41, Rushabh Lathia wrote:

I agree about the future plan about the row movement, how that is I am
not quite sure at this stage.

I was thinking that CREATE new partition is the DDL command, so even
if row-movement works with holding the lock on the new partition table,
that should be fine. I am not quire sure, why row movement should be
happen in the back-ground process.

I think to improve the availability of access to the partitioned table.

Consider that the default partition may have gotten pretty large.
Scanning it and moving rows to the newly added partition while holding an
AccessExclusiveLock on the parent will block any and all of the concurrent
activity on it until the row-movement is finished. One may be prepared to
pay this cost, for which there should definitely be an option to perform
the row-movement in the same transaction (also possibly the default behavior).

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#23Rahila Syed
rahilasyed90@gmail.com
In reply to: Amit Langote (#22)
Re: Adding support for Default partition in partitioning

Hello Amit,

Could you briefly elaborate why you think the lack global index support
would be a problem in this regard?

I think following can happen if we allow rows satisfying the new partition
to lie around in the
default partition until background process moves it.
Consider a scenario where partition key is a primary key and the data in
the default partition is
not yet moved into the newly added partition. If now, new data is added
into the new partition
which also exists(same key) in default partition there will be data
duplication. If now
we scan the partitioned table for that key(from both the default and new
partition as we
have not moved the rows) it will fetch the both rows.
Unless we have global indexes for partitioned tables, there is chance of
data duplication between
child table added after default partition and the default partition.

Scanning it and moving rows to the newly added partition while holding an
AccessExclusiveLock on the parent will block any and all of the concurrent
activity on it until the row-movement is finished.

Can you explain why this will require AccessExclusiveLock on parent and
not just the default partition and newly added partition?

Thank you,
Rahila Syed

On Wed, Apr 5, 2017 at 1:22 PM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
wrote:

Show quoted text

On 2017/04/05 14:41, Rushabh Lathia wrote:

I agree about the future plan about the row movement, how that is I am
not quite sure at this stage.

I was thinking that CREATE new partition is the DDL command, so even
if row-movement works with holding the lock on the new partition table,
that should be fine. I am not quire sure, why row movement should be
happen in the back-ground process.

I think to improve the availability of access to the partitioned table.

Consider that the default partition may have gotten pretty large.
Scanning it and moving rows to the newly added partition while holding an
AccessExclusiveLock on the parent will block any and all of the concurrent
activity on it until the row-movement is finished. One may be prepared to
pay this cost, for which there should definitely be an option to perform
the row-movement in the same transaction (also possibly the default
behavior).

Thanks,
Amit

#24Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Rahila Syed (#23)
Re: Adding support for Default partition in partitioning

Hi Rahila,

On 2017/04/05 18:57, Rahila Syed wrote:

Hello Amit,

Could you briefly elaborate why you think the lack global index support
would be a problem in this regard?

I think following can happen if we allow rows satisfying the new partition
to lie around in the
default partition until background process moves it.
Consider a scenario where partition key is a primary key and the data in
the default partition is
not yet moved into the newly added partition. If now, new data is added
into the new partition
which also exists(same key) in default partition there will be data
duplication. If now
we scan the partitioned table for that key(from both the default and new
partition as we
have not moved the rows) it will fetch the both rows.
Unless we have global indexes for partitioned tables, there is chance of
data duplication between
child table added after default partition and the default partition.

Ah, okay. I think I wrote that question before even reading the next
sentence in Keith's message ("there's still an issue of data duplication
after the necessary child table is added.")

Maybe we can disallow background row movement if such global constraint
exists.

Scanning it and moving rows to the newly added partition while holding an
AccessExclusiveLock on the parent will block any and all of the concurrent
activity on it until the row-movement is finished.

Can you explain why this will require AccessExclusiveLock on parent and
not just the default partition and newly added partition?

Because we take an AccessExclusiveLock on the parent table when
adding/removing a partition in general. We do that because concurrent
accessors of the parent table rely on its partition descriptor from not
changing under them.

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#25Robert Haas
robertmhaas@gmail.com
In reply to: Rahila Syed (#23)
Re: Adding support for Default partition in partitioning

On Wed, Apr 5, 2017 at 5:57 AM, Rahila Syed <rahilasyed90@gmail.com> wrote:

Could you briefly elaborate why you think the lack global index support
would be a problem in this regard?

I think following can happen if we allow rows satisfying the new partition
to lie around in the
default partition until background process moves it.
Consider a scenario where partition key is a primary key and the data in the
default partition is
not yet moved into the newly added partition. If now, new data is added into
the new partition
which also exists(same key) in default partition there will be data
duplication. If now
we scan the partitioned table for that key(from both the default and new
partition as we
have not moved the rows) it will fetch the both rows.
Unless we have global indexes for partitioned tables, there is chance of
data duplication between
child table added after default partition and the default partition.

Yes, I think it would be completely crazy to try to migrate the data
in the background:

- The migration might never complete because of a UNIQUE or CHECK
constraint on the partition to which rows are being migrated.

- Even if the migration eventually succeeded, such a design abandons
all hope of making INSERT .. ON CONFLICT DO NOTHING work sensibly
while the migration is in progress, unless the new partition has no
UNIQUE constraints.

- Partition-wise join and partition-wise aggregate would need to have
special case handling for the case of an unfinished migration, as
would any user code that accesses partitions directly.

- More generally, I think users expect that when a DDL command
finishes execution, it's done all of the work that there is to do (or
at the very least, that any remaining work has no user-visible
consequences, which would not be the case here).

IMV, the question of whether we have efficient ways to move data
around between partitions is somewhat separate from the question of
whether partitions can be defined in a certain way in the first place.
The problems that Keith refers to upthread already exist for
subpartitioning; you've got to detach the old partition, create a new
one, and then reinsert the data. And for partitioning an
unpartitioned table: create a replacement table, insert all the data,
substitute it for the original table. The fact that we have these
limitation is not good, but we're not going to rip out partitioning
entirely because we don't have clever ways of migrating the data in
those cases, and the proposed behavior here is not any worse.

Also, waiting for those problems to get fixed might be waiting for
Godot. I'm not really all that sanguine about our chances of coming
up with a really nice way of handling these cases. In a designed
based on table inheritance, you can leave it murky where certain data
is supposed to end up and migrate it on-line and you might get away
with that, but a major point of having declarative partitioning at all
is to remove that sort of murkiness. It's probably not that hard to
come up with a command that locks the parent and moves data around via
full table scans, but I'm not sure how far that really gets us; you
could do the same thing easily enough with a sequence of commands
generated via a script. And being able to do this in a general way
without a full table lock looks pretty hard - it doesn't seem
fundamentally different from trying to perform a table-rewriting
operation like CLUSTER without a full table lock, which we also don't
support. The executor is not built to cope with any aspect of the
table definition shifting under it, and that includes the set of child
tables with are partitions of the table mentioned in the query. Maybe
the executor can be taught to survive such definitional changes at
least in limited cases, but that's a much different project than
allowing default partitions.

--
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

#26Keith Fiske
keith@omniti.com
In reply to: Robert Haas (#25)
Re: Adding support for Default partition in partitioning

On Wed, Apr 5, 2017 at 11:19 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Wed, Apr 5, 2017 at 5:57 AM, Rahila Syed <rahilasyed90@gmail.com>
wrote:

Could you briefly elaborate why you think the lack global index support
would be a problem in this regard?

I think following can happen if we allow rows satisfying the new

partition

to lie around in the
default partition until background process moves it.
Consider a scenario where partition key is a primary key and the data in

the

default partition is
not yet moved into the newly added partition. If now, new data is added

into

the new partition
which also exists(same key) in default partition there will be data
duplication. If now
we scan the partitioned table for that key(from both the default and new
partition as we
have not moved the rows) it will fetch the both rows.
Unless we have global indexes for partitioned tables, there is chance of
data duplication between
child table added after default partition and the default partition.

Yes, I think it would be completely crazy to try to migrate the data
in the background:

- The migration might never complete because of a UNIQUE or CHECK
constraint on the partition to which rows are being migrated.

- Even if the migration eventually succeeded, such a design abandons
all hope of making INSERT .. ON CONFLICT DO NOTHING work sensibly
while the migration is in progress, unless the new partition has no
UNIQUE constraints.

- Partition-wise join and partition-wise aggregate would need to have
special case handling for the case of an unfinished migration, as
would any user code that accesses partitions directly.

- More generally, I think users expect that when a DDL command
finishes execution, it's done all of the work that there is to do (or
at the very least, that any remaining work has no user-visible
consequences, which would not be the case here).

IMV, the question of whether we have efficient ways to move data
around between partitions is somewhat separate from the question of
whether partitions can be defined in a certain way in the first place.
The problems that Keith refers to upthread already exist for
subpartitioning; you've got to detach the old partition, create a new
one, and then reinsert the data. And for partitioning an
unpartitioned table: create a replacement table, insert all the data,
substitute it for the original table. The fact that we have these
limitation is not good, but we're not going to rip out partitioning
entirely because we don't have clever ways of migrating the data in
those cases, and the proposed behavior here is not any worse.

Also, waiting for those problems to get fixed might be waiting for
Godot. I'm not really all that sanguine about our chances of coming
up with a really nice way of handling these cases. In a designed
based on table inheritance, you can leave it murky where certain data
is supposed to end up and migrate it on-line and you might get away
with that, but a major point of having declarative partitioning at all
is to remove that sort of murkiness. It's probably not that hard to
come up with a command that locks the parent and moves data around via
full table scans, but I'm not sure how far that really gets us; you
could do the same thing easily enough with a sequence of commands
generated via a script. And being able to do this in a general way
without a full table lock looks pretty hard - it doesn't seem
fundamentally different from trying to perform a table-rewriting
operation like CLUSTER without a full table lock, which we also don't
support. The executor is not built to cope with any aspect of the
table definition shifting under it, and that includes the set of child
tables with are partitions of the table mentioned in the query. Maybe
the executor can be taught to survive such definitional changes at
least in limited cases, but that's a much different project than
allowing default partitions.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

Confirmed that v5 patch works with examples given in the original post but
segfaulted when I tried the examples I used in my blog post (taken from the
documentation at the time I wrote it).
https://www.keithf4.com/postgresql-10-built-in-partitioning/

keith@keith=# drop table cities;
DROP TABLE
Time: 6.055 ms
keith@keith=# CREATE TABLE cities (
city_id bigserial not null,
name text not null,
population int
) PARTITION BY LIST (initcap(name));
CREATE TABLE
Time: 7.130 ms
keith@keith=# CREATE TABLE cities_west
PARTITION OF cities (
CONSTRAINT city_id_nonzero CHECK (city_id != 0)
) FOR VALUES IN ('Los Angeles', 'San Francisco');
CREATE TABLE
Time: 6.690 ms
keith@keith=# CREATE TABLE cities_default
keith-# PARTITION OF cities FOR VALUES IN (DEFAULT);
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: WARNING:
terminating connection because of crash of another server process
DETAIL: The postmaster has commanded this server process to roll back the
current transaction and exit, because another server process exited
abnormally and possibly corrupted shared memory.
HINT: In a moment you should be able to reconnect to the database and
repeat your command.
Failed.
Time: 387.887 ms

After reading responses, I think I'd be fine with how Rahila implemented
this with disallowing the child until the data is removed from the default
if this would allow it to be included in v10. As was mentioned, there just
doesn't seem to be a way to easily handle the data conflicts cleanly at
this time, but I think the value of the default to be able to catch
accidental data vs returning an error is worth it. It also at least gives a
slightly easier migration path vs having to migrate to a completely new
table. Any chance this could be adapted for range partitioning as well? I'd
be happy to create some pgtap tests with pg_partman for this then to make
sure it works.

Only issue I see with this, and I'm not sure if it is an issue, is what
happens to that default constraint clause when 1000s of partitions start
getting added? From what I gather the default's constraint is built based
off the cumulative opposite of all other child constraints. I don't
understand the code well enough to see what it's actually doing, but if
there are no gaps, is the method used smart enough to aggregate all the
child constraints to make a simpler constraint that is simply outside the
current min/max boundaries? If so, for serial/time range partitioning this
should typically work out fine since there are rarely gaps. This actually
seems more of an issue for list partitioning where each child is a distinct
value or range of values that are completely arbitrary. Won't that check
and re-evaluation of the default's constraint just get worse and worse as
more children are added? Is there really even a need for the default to
have an opposite constraint like this? Not sure on how the planner works
with partitioning now, but wouldn't it be better to first check all
non-default children for a match the same as it does now without a default
and, failing that, then route to the default if one is declared? The
default should accept any data then so I don't see the need for the
constraint unless it's required for the current implementation. If that's
the case, could that be changed?

Keith

#27Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#25)
Re: Adding support for Default partition in partitioning

On 2017/04/06 0:19, Robert Haas wrote:

On Wed, Apr 5, 2017 at 5:57 AM, Rahila Syed <rahilasyed90@gmail.com> wrote:

Could you briefly elaborate why you think the lack global index support
would be a problem in this regard?

I think following can happen if we allow rows satisfying the new partition
to lie around in the
default partition until background process moves it.
Consider a scenario where partition key is a primary key and the data in the
default partition is
not yet moved into the newly added partition. If now, new data is added into
the new partition
which also exists(same key) in default partition there will be data
duplication. If now
we scan the partitioned table for that key(from both the default and new
partition as we
have not moved the rows) it will fetch the both rows.
Unless we have global indexes for partitioned tables, there is chance of
data duplication between
child table added after default partition and the default partition.

Yes, I think it would be completely crazy to try to migrate the data
in the background:

- The migration might never complete because of a UNIQUE or CHECK
constraint on the partition to which rows are being migrated.

- Even if the migration eventually succeeded, such a design abandons
all hope of making INSERT .. ON CONFLICT DO NOTHING work sensibly
while the migration is in progress, unless the new partition has no
UNIQUE constraints.

- Partition-wise join and partition-wise aggregate would need to have
special case handling for the case of an unfinished migration, as
would any user code that accesses partitions directly.

- More generally, I think users expect that when a DDL command
finishes execution, it's done all of the work that there is to do (or
at the very least, that any remaining work has no user-visible
consequences, which would not be the case here).

OK, I realize the background migration was a poorly thought out idea. And
a *first* version that will handle the row-movement should be doing that
as part of the same command anyway.

IMV, the question of whether we have efficient ways to move data
around between partitions is somewhat separate from the question of
whether partitions can be defined in a certain way in the first place.
The problems that Keith refers to upthread already exist for
subpartitioning; you've got to detach the old partition, create a new
one, and then reinsert the data. And for partitioning an
unpartitioned table: create a replacement table, insert all the data,
substitute it for the original table. The fact that we have these
limitation is not good, but we're not going to rip out partitioning
entirely because we don't have clever ways of migrating the data in
those cases, and the proposed behavior here is not any worse.

Also, waiting for those problems to get fixed might be waiting for
Godot. I'm not really all that sanguine about our chances of coming
up with a really nice way of handling these cases. In a designed
based on table inheritance, you can leave it murky where certain data
is supposed to end up and migrate it on-line and you might get away
with that, but a major point of having declarative partitioning at all
is to remove that sort of murkiness. It's probably not that hard to
come up with a command that locks the parent and moves data around via
full table scans, but I'm not sure how far that really gets us; you
could do the same thing easily enough with a sequence of commands
generated via a script. And being able to do this in a general way
without a full table lock looks pretty hard - it doesn't seem
fundamentally different from trying to perform a table-rewriting
operation like CLUSTER without a full table lock, which we also don't
support. The executor is not built to cope with any aspect of the
table definition shifting under it, and that includes the set of child
tables with are partitions of the table mentioned in the query. Maybe
the executor can be taught to survive such definitional changes at
least in limited cases, but that's a much different project than
allowing default partitions.

Agreed.

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#28Keith Fiske
keith@omniti.com
In reply to: Keith Fiske (#26)
Re: Adding support for Default partition in partitioning

On Wed, Apr 5, 2017 at 2:51 PM, Keith Fiske <keith@omniti.com> wrote:

Only issue I see with this, and I'm not sure if it is an issue, is what
happens to that default constraint clause when 1000s of partitions start
getting added? From what I gather the default's constraint is built based
off the cumulative opposite of all other child constraints. I don't
understand the code well enough to see what it's actually doing, but if
there are no gaps, is the method used smart enough to aggregate all the
child constraints to make a simpler constraint that is simply outside the
current min/max boundaries? If so, for serial/time range partitioning this
should typically work out fine since there are rarely gaps. This actually
seems more of an issue for list partitioning where each child is a distinct
value or range of values that are completely arbitrary. Won't that check
and re-evaluation of the default's constraint just get worse and worse as
more children are added? Is there really even a need for the default to
have an opposite constraint like this? Not sure on how the planner works
with partitioning now, but wouldn't it be better to first check all
non-default children for a match the same as it does now without a default
and, failing that, then route to the default if one is declared? The
default should accept any data then so I don't see the need for the
constraint unless it's required for the current implementation. If that's
the case, could that be changed?

Keith

Actually, thinking on this more, I realized this does again come back to
the lack of a global index. Without the constraint, data could be put
directly into the default that could technically conflict with the
partition scheme elsewhere. Perhaps, instead of the constraint, inserts
directly to the default could be prevented on the user level. Writing to
valid children directly certainly has its place, but been thinking about
it, and I can't see any reason why one would ever want to write directly to
the default. It's use case seems to be around being a sort of temporary
storage until that data can be moved to a valid location. Would still need
to allow removal of data, though.

Not sure if that's even a workable solution. Just trying to think of ways
around the current limitations and still allow this feature.

#29Rushabh Lathia
rushabh.lathia@gmail.com
In reply to: Keith Fiske (#28)
Re: Adding support for Default partition in partitioning

On 2017/04/06 0:19, Robert Haas wrote:

On Wed, Apr 5, 2017 at 5:57 AM, Rahila Syed <rahilasyed90@gmail.com>

wrote:

Could you briefly elaborate why you think the lack global index support
would be a problem in this regard?

I think following can happen if we allow rows satisfying the new

partition

to lie around in the
default partition until background process moves it.
Consider a scenario where partition key is a primary key and the data in

the

default partition is
not yet moved into the newly added partition. If now, new data is added

into

the new partition
which also exists(same key) in default partition there will be data
duplication. If now
we scan the partitioned table for that key(from both the default and new
partition as we
have not moved the rows) it will fetch the both rows.
Unless we have global indexes for partitioned tables, there is chance of
data duplication between
child table added after default partition and the default partition.

Yes, I think it would be completely crazy to try to migrate the data
in the background:

- The migration might never complete because of a UNIQUE or CHECK
constraint on the partition to which rows are being migrated.

- Even if the migration eventually succeeded, such a design abandons
all hope of making INSERT .. ON CONFLICT DO NOTHING work sensibly
while the migration is in progress, unless the new partition has no
UNIQUE constraints.

- Partition-wise join and partition-wise aggregate would need to have
special case handling for the case of an unfinished migration, as
would any user code that accesses partitions directly.

- More generally, I think users expect that when a DDL command
finishes execution, it's done all of the work that there is to do (or
at the very least, that any remaining work has no user-visible
consequences, which would not be the case here).

Thanks Robert for this explanation. This makes it more clear, why row
movement by background is not sensible idea.

On Thu, Apr 6, 2017 at 9:38 AM, Keith Fiske <keith@omniti.com> wrote:

On Wed, Apr 5, 2017 at 2:51 PM, Keith Fiske <keith@omniti.com> wrote:

Only issue I see with this, and I'm not sure if it is an issue, is what
happens to that default constraint clause when 1000s of partitions start
getting added? From what I gather the default's constraint is built based
off the cumulative opposite of all other child constraints. I don't
understand the code well enough to see what it's actually doing, but if
there are no gaps, is the method used smart enough to aggregate all the
child constraints to make a simpler constraint that is simply outside the
current min/max boundaries? If so, for serial/time range partitioning this
should typically work out fine since there are rarely gaps. This actually
seems more of an issue for list partitioning where each child is a distinct
value or range of values that are completely arbitrary. Won't that check
and re-evaluation of the default's constraint just get worse and worse as
more children are added? Is there really even a need for the default to
have an opposite constraint like this? Not sure on how the planner works
with partitioning now, but wouldn't it be better to first check all
non-default children for a match the same as it does now without a default
and, failing that, then route to the default if one is declared? The
default should accept any data then so I don't see the need for the
constraint unless it's required for the current implementation. If that's
the case, could that be changed?

Keith

Actually, thinking on this more, I realized this does again come back to
the lack of a global index. Without the constraint, data could be put
directly into the default that could technically conflict with the
partition scheme elsewhere. Perhaps, instead of the constraint, inserts
directly to the default could be prevented on the user level. Writing to
valid children directly certainly has its place, but been thinking about
it, and I can't see any reason why one would ever want to write directly to
the default. It's use case seems to be around being a sort of temporary
storage until that data can be moved to a valid location. Would still need
to allow removal of data, though.

Not sure if that's even a workable solution. Just trying to think of ways
around the current limitations and still allow this feature.

I like the idea about having DEFAULT partition for the range partition.
With the
way partition is designed it can have holes into range partition. I think
DEFAULT
for the range partition is a good idea, generally when the range having
holes. When
range is serial then of course DEFAULT partition doen't much sense.

Regarda,

Rushabh Lathia
www.EnterpriseDB.com

#30Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Keith Fiske (#28)
Re: Adding support for Default partition in partitioning

On 2017/04/06 13:08, Keith Fiske wrote:

On Wed, Apr 5, 2017 at 2:51 PM, Keith Fiske wrote:

Only issue I see with this, and I'm not sure if it is an issue, is what
happens to that default constraint clause when 1000s of partitions start
getting added? From what I gather the default's constraint is built based
off the cumulative opposite of all other child constraints. I don't
understand the code well enough to see what it's actually doing, but if
there are no gaps, is the method used smart enough to aggregate all the
child constraints to make a simpler constraint that is simply outside the
current min/max boundaries? If so, for serial/time range partitioning this
should typically work out fine since there are rarely gaps. This actually
seems more of an issue for list partitioning where each child is a distinct
value or range of values that are completely arbitrary. Won't that check
and re-evaluation of the default's constraint just get worse and worse as
more children are added? Is there really even a need for the default to
have an opposite constraint like this? Not sure on how the planner works
with partitioning now, but wouldn't it be better to first check all
non-default children for a match the same as it does now without a default
and, failing that, then route to the default if one is declared? The
default should accept any data then so I don't see the need for the
constraint unless it's required for the current implementation. If that's
the case, could that be changed?

Unless I misread your last sentence, I think there might be some
confusion. Currently, the partition constraint (think of these as you
would of user-defined check constraints) is needed for two reasons: 1. to
prevent direct insertion of rows into the default partition for which a
non-default partition exists; no two partitions should ever have duplicate
rows. 2. so that planner can use the constraint to determine if the
default partition needs to be scanned for a query using constraint
exclusion; no need, for example, to scan the default partition if the
query requests only key=3 rows and a partition for the same exists (no
other partition should have key=3 rows by definition, not even the
default). As things stand today, planner needs to look at every partition
individually for using constraint exclusion to possibly exclude it, *even*
with declarative partitioning and that would include the default partition.

Actually, thinking on this more, I realized this does again come back to
the lack of a global index. Without the constraint, data could be put
directly into the default that could technically conflict with the
partition scheme elsewhere. Perhaps, instead of the constraint, inserts
directly to the default could be prevented on the user level. Writing to
valid children directly certainly has its place, but been thinking about
it, and I can't see any reason why one would ever want to write directly to
the default. It's use case seems to be around being a sort of temporary
storage until that data can be moved to a valid location. Would still need
to allow removal of data, though.

As mentioned above, the default partition will not allow directly
inserting a row whose key maps to some existing (non-default) partition.

As far as tuple-routing is concerned, it will choose the default partition
only if no other partition is found for the key. Tuple-routing doesn't
use the partition constraints directly per se, like one of the two things
mentioned above do. One could say that tuple-routing assigns the incoming
rows to partitions such that their individual partition constraints are
not violated.

Finally, we don't yet offer global guarantees for constraints like unique.
The only guarantee that's in place is that no two partitions can contain
the same partition key.

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#31Rahila Syed
rahilasyed90@gmail.com
In reply to: Keith Fiske (#26)
1 attachment(s)
Re: Adding support for Default partition in partitioning

Hello,

Thanks a lot for testing and reporting this. Please find attached an
updated patch with the fix. The patch also contains a fix
regarding operator used at the time of creating expression as default
partition constraint. This was notified offlist by Amit Langote.

Thank you,
Rahila Syed

On Thu, Apr 6, 2017 at 12:21 AM, Keith Fiske <keith@omniti.com> wrote:

Show quoted text

On Wed, Apr 5, 2017 at 11:19 AM, Robert Haas <robertmhaas@gmail.com>
wrote:

On Wed, Apr 5, 2017 at 5:57 AM, Rahila Syed <rahilasyed90@gmail.com>
wrote:

Could you briefly elaborate why you think the lack global index support
would be a problem in this regard?

I think following can happen if we allow rows satisfying the new

partition

to lie around in the
default partition until background process moves it.
Consider a scenario where partition key is a primary key and the data

in the

default partition is
not yet moved into the newly added partition. If now, new data is added

into

the new partition
which also exists(same key) in default partition there will be data
duplication. If now
we scan the partitioned table for that key(from both the default and new
partition as we
have not moved the rows) it will fetch the both rows.
Unless we have global indexes for partitioned tables, there is chance of
data duplication between
child table added after default partition and the default partition.

Yes, I think it would be completely crazy to try to migrate the data
in the background:

- The migration might never complete because of a UNIQUE or CHECK
constraint on the partition to which rows are being migrated.

- Even if the migration eventually succeeded, such a design abandons
all hope of making INSERT .. ON CONFLICT DO NOTHING work sensibly
while the migration is in progress, unless the new partition has no
UNIQUE constraints.

- Partition-wise join and partition-wise aggregate would need to have
special case handling for the case of an unfinished migration, as
would any user code that accesses partitions directly.

- More generally, I think users expect that when a DDL command
finishes execution, it's done all of the work that there is to do (or
at the very least, that any remaining work has no user-visible
consequences, which would not be the case here).

IMV, the question of whether we have efficient ways to move data
around between partitions is somewhat separate from the question of
whether partitions can be defined in a certain way in the first place.
The problems that Keith refers to upthread already exist for
subpartitioning; you've got to detach the old partition, create a new
one, and then reinsert the data. And for partitioning an
unpartitioned table: create a replacement table, insert all the data,
substitute it for the original table. The fact that we have these
limitation is not good, but we're not going to rip out partitioning
entirely because we don't have clever ways of migrating the data in
those cases, and the proposed behavior here is not any worse.

Also, waiting for those problems to get fixed might be waiting for
Godot. I'm not really all that sanguine about our chances of coming
up with a really nice way of handling these cases. In a designed
based on table inheritance, you can leave it murky where certain data
is supposed to end up and migrate it on-line and you might get away
with that, but a major point of having declarative partitioning at all
is to remove that sort of murkiness. It's probably not that hard to
come up with a command that locks the parent and moves data around via
full table scans, but I'm not sure how far that really gets us; you
could do the same thing easily enough with a sequence of commands
generated via a script. And being able to do this in a general way
without a full table lock looks pretty hard - it doesn't seem
fundamentally different from trying to perform a table-rewriting
operation like CLUSTER without a full table lock, which we also don't
support. The executor is not built to cope with any aspect of the
table definition shifting under it, and that includes the set of child
tables with are partitions of the table mentioned in the query. Maybe
the executor can be taught to survive such definitional changes at
least in limited cases, but that's a much different project than
allowing default partitions.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

Confirmed that v5 patch works with examples given in the original post but
segfaulted when I tried the examples I used in my blog post (taken from the
documentation at the time I wrote it). https://www.keithf4.com/
postgresql-10-built-in-partitioning/

keith@keith=# drop table cities;
DROP TABLE
Time: 6.055 ms
keith@keith=# CREATE TABLE cities (
city_id bigserial not null,
name text not null,
population int
) PARTITION BY LIST (initcap(name));
CREATE TABLE
Time: 7.130 ms
keith@keith=# CREATE TABLE cities_west
PARTITION OF cities (
CONSTRAINT city_id_nonzero CHECK (city_id != 0)
) FOR VALUES IN ('Los Angeles', 'San Francisco');
CREATE TABLE
Time: 6.690 ms
keith@keith=# CREATE TABLE cities_default
keith-# PARTITION OF cities FOR VALUES IN (DEFAULT);
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: WARNING:
terminating connection because of crash of another server process
DETAIL: The postmaster has commanded this server process to roll back the
current transaction and exit, because another server process exited
abnormally and possibly corrupted shared memory.
HINT: In a moment you should be able to reconnect to the database and
repeat your command.
Failed.
Time: 387.887 ms

After reading responses, I think I'd be fine with how Rahila implemented
this with disallowing the child until the data is removed from the default
if this would allow it to be included in v10. As was mentioned, there just
doesn't seem to be a way to easily handle the data conflicts cleanly at
this time, but I think the value of the default to be able to catch
accidental data vs returning an error is worth it. It also at least gives a
slightly easier migration path vs having to migrate to a completely new
table. Any chance this could be adapted for range partitioning as well? I'd
be happy to create some pgtap tests with pg_partman for this then to make
sure it works.

Only issue I see with this, and I'm not sure if it is an issue, is what
happens to that default constraint clause when 1000s of partitions start
getting added? From what I gather the default's constraint is built based
off the cumulative opposite of all other child constraints. I don't
understand the code well enough to see what it's actually doing, but if
there are no gaps, is the method used smart enough to aggregate all the
child constraints to make a simpler constraint that is simply outside the
current min/max boundaries? If so, for serial/time range partitioning this
should typically work out fine since there are rarely gaps. This actually
seems more of an issue for list partitioning where each child is a distinct
value or range of values that are completely arbitrary. Won't that check
and re-evaluation of the default's constraint just get worse and worse as
more children are added? Is there really even a need for the default to
have an opposite constraint like this? Not sure on how the planner works
with partitioning now, but wouldn't it be better to first check all
non-default children for a match the same as it does now without a default
and, failing that, then route to the default if one is declared? The
default should accept any data then so I don't see the need for the
constraint unless it's required for the current implementation. If that's
the case, could that be changed?

Keith

Attachments:

default_partition_v6.patchapplication/x-download; name=default_partition_v6.patchDownload
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index ab891f6..b7c3e85 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -34,6 +34,7 @@
 #include "nodes/nodeFuncs.h"
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
+#include "optimizer/prep.h"
 #include "optimizer/planmain.h"
 #include "optimizer/var.h"
 #include "rewrite/rewriteManip.h"
@@ -90,6 +91,10 @@ typedef struct PartitionBoundInfoData
 								 * for range partitioned tables */
 	int			null_index;		/* Index of the null-accepting partition; -1
 								 * for range partitioned tables */
+	bool		has_def;		/* Is there a default partition? Currently false
+								 * for a range partitioned table */
+	int			def_index;		/* Index of the default list partition. -1 for
+								 * range partitioned tables */
 } PartitionBoundInfoData;
 
 /*
@@ -118,7 +123,8 @@ static int32 qsort_partition_list_value_cmp(const void *a, const void *b,
 static int32 qsort_partition_rbound_cmp(const void *a, const void *b,
 						   void *arg);
 
-static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec,
+								bool is_def, List *boundspecs);
 static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
 static Oid get_partition_operator(PartitionKey key, int col,
 					   StrategyNumber strategy, bool *need_relabel);
@@ -166,6 +172,8 @@ RelationBuildPartitionDesc(Relation rel)
 	/* List partitioning specific */
 	PartitionListValue **all_values = NULL;
 	bool		found_null = false;
+	bool		found_def = false;
+	int			def_index = -1;
 	int			null_index = -1;
 
 	/* Range partitioning specific */
@@ -249,9 +257,16 @@ RelationBuildPartitionDesc(Relation rel)
 
 				foreach(c, spec->listdatums)
 				{
+					Node *value = lfirst(c);
 					Const	   *val = lfirst(c);
 					PartitionListValue *list_value = NULL;
 
+					if (IsA(value, DefElem))
+					{
+						found_def = true;
+						def_index = i;
+						continue;
+					}
 					if (!val->constisnull)
 					{
 						list_value = (PartitionListValue *)
@@ -459,6 +474,7 @@ RelationBuildPartitionDesc(Relation rel)
 			case PARTITION_STRATEGY_LIST:
 				{
 					boundinfo->has_null = found_null;
+					boundinfo->has_def = found_def;
 					boundinfo->indexes = (int *) palloc(ndatums * sizeof(int));
 
 					/*
@@ -496,6 +512,11 @@ RelationBuildPartitionDesc(Relation rel)
 						if (mapping[null_index] == -1)
 							mapping[null_index] = next_index++;
 					}
+					if (found_def)
+					{
+						if (mapping[def_index] == -1)
+							mapping[def_index] = next_index++;
+					}
 
 					/* All partition must now have a valid mapping */
 					Assert(next_index == nparts);
@@ -504,6 +525,11 @@ RelationBuildPartitionDesc(Relation rel)
 						boundinfo->null_index = mapping[null_index];
 					else
 						boundinfo->null_index = -1;
+
+					if (found_def)
+						boundinfo->def_index = mapping[def_index];
+					else
+						boundinfo->def_index = -1;
 					break;
 				}
 
@@ -671,6 +697,7 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
 	PartitionKey key = RelationGetPartitionKey(parent);
 	PartitionDesc partdesc = RelationGetPartitionDesc(parent);
+	PartitionBoundInfo boundinfo;
 	ParseState *pstate = make_parsestate(NULL);
 	int			with = -1;
 	bool		overlap = false;
@@ -679,6 +706,8 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 	{
 		case PARTITION_STRATEGY_LIST:
 			{
+				boundinfo = partdesc->boundinfo;
+
 				Assert(spec->strategy == PARTITION_STRATEGY_LIST);
 
 				if (partdesc->nparts > 0)
@@ -688,13 +717,15 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 
 					Assert(boundinfo &&
 						   boundinfo->strategy == PARTITION_STRATEGY_LIST &&
-						   (boundinfo->ndatums > 0 || boundinfo->has_null));
+						   (boundinfo->ndatums > 0 || boundinfo->has_null
+							|| boundinfo->has_def));
 
 					foreach(cell, spec->listdatums)
 					{
-						Const	   *val = lfirst(cell);
+						Node	   *value = lfirst(cell);
+						Const	   *val = (Const *) value;
 
-						if (!val->constisnull)
+						if (!val->constisnull && !(IsA(value, DefElem)))
 						{
 							int			offset;
 							bool		equal;
@@ -709,7 +740,13 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 								break;
 							}
 						}
-						else if (boundinfo->has_null)
+						else if (IsA(value, DefElem) && boundinfo->has_def)
+						{
+							overlap = true;
+							with = boundinfo->def_index;
+							break;
+						}
+						else if (val->constisnull && boundinfo->has_null)
 						{
 							overlap = true;
 							with = boundinfo->null_index;
@@ -717,7 +754,6 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 						}
 					}
 				}
-
 				break;
 			}
 
@@ -836,6 +872,76 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 						relname, get_rel_name(partdesc->oids[with])),
 				 parser_errposition(pstate, spec->location)));
 	}
+
+	/*
+	 * When adding a list partition after default partition, scan the
+	 * default partiton for rows satisfying the new partition
+	 * constraint. If found dont 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_def)
+	{
+		List	       *partConstraint = NIL;
+		ExprContext    *econtext;
+		EState	       *estate;
+		Relation	    defrel;
+		HeapScanDesc    scan;
+		HeapTuple	    tuple;
+		ExprState	   *partqualstate = NULL;
+		Snapshot	    snapshot;
+		Oid			    defid;
+		MemoryContext   oldCxt;
+		TupleTableSlot *tupslot;
+		TupleDesc	    tupdesc;
+
+		partConstraint = generate_qual_for_defaultpart(parent, bound, &defid);
+		partConstraint = (List *) eval_const_expressions(NULL,
+													(Node *) partConstraint);
+		partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
+		partConstraint = list_make1(make_ands_explicit(partConstraint));
+
+		/*
+		 * Generate the constraint and default execution states
+		 */
+		estate = CreateExecutorState();
+
+		defrel = heap_open(defid, AccessShareLock);
+		tupdesc = CreateTupleDescCopy(RelationGetDescr(defrel));
+
+		/* Build expression execution states for partition check quals */
+		partqualstate = ExecPrepareCheck(partConstraint,
+						estate);
+
+		econtext = GetPerTupleExprContext(estate);
+		snapshot = RegisterSnapshot(GetLatestSnapshot());
+		scan = heap_beginscan(defrel, snapshot, 0, NULL);
+		tupslot = MakeSingleTupleTableSlot(tupdesc);
+
+		/*
+		 * Switch to per-tuple memory context and reset it for each tuple
+		 * produced, so we don't leak memory.
+		 */
+		oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		{
+			ExecStoreTuple(tuple, tupslot, InvalidBuffer, false);
+			econtext->ecxt_scantuple = tupslot;
+			if (partqualstate && !ExecCheck(partqualstate, econtext))
+				ereport(ERROR,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+				errmsg("new default partition constraint is violated by some row")));
+			ResetExprContext(econtext);
+			CHECK_FOR_INTERRUPTS();
+		}
+		MemoryContextSwitchTo(oldCxt);
+		heap_endscan(scan);
+		UnregisterSnapshot(snapshot);
+		heap_close(defrel, AccessShareLock);
+		ExecDropSingleTupleTableSlot(tupslot);
+	}
+
 }
 
 /*
@@ -884,6 +990,90 @@ get_partition_parent(Oid relid)
 }
 
 /*
+ * Return a list of executable expressions as new partition constraint
+ * for default partition while adding a new partition after default
+ */
+List *
+generate_qual_for_defaultpart(Relation parent, Node *bound, Oid * defid)
+{
+	PartitionKey key = RelationGetPartitionKey(parent);
+	PartitionBoundSpec *spec;
+	List	   *partConstraint = NIL;
+	List	   *inhoids;
+	ListCell   *cell2;
+	ListCell   *cell4;
+	List       *boundspecs = NIL;
+	bool		is_def = true;
+
+	spec = (PartitionBoundSpec *) bound;
+
+	inhoids = find_inheritance_children(RelationGetRelid(parent), NoLock);
+
+	foreach(cell2, inhoids)
+	{
+		PartitionBoundSpec *bspec;
+		Oid			inhrelid = lfirst_oid(cell2);
+		HeapTuple	tuple;
+		Datum		datum;
+		bool		isnull;
+		Node	   *boundspec;
+		bool		def_elem = false;
+		ListCell   *cell1;
+		ListCell   *cell3;
+
+		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);
+		boundspec = (Node *) stringToNode(TextDatumGetCString(datum));
+		bspec = (PartitionBoundSpec *)boundspec;
+		foreach(cell1, bspec->listdatums)
+		{
+			Node *value = lfirst(cell1);
+			if (IsA(value, DefElem))
+			{
+				def_elem = true;
+				*defid = inhrelid;
+			}
+		}
+		if (def_elem)
+		{
+			ReleaseSysCache(tuple);
+			continue;
+		}
+		foreach(cell3, bspec->listdatums)
+		{
+			Node *value = lfirst(cell3);
+			boundspecs = lappend(boundspecs, value);
+		}
+		ReleaseSysCache(tuple);
+	}
+	foreach(cell4, spec->listdatums)
+	{
+		Node *value = lfirst(cell4);
+		boundspecs = lappend(boundspecs, value);
+	}
+	partConstraint = get_qual_for_list(key, spec, is_def, boundspecs);
+	return partConstraint;
+}
+/*
  * get_qual_from_partbound
  *		Given a parser node for partition bound, return the list of executable
  *		expressions as partition constraint
@@ -894,6 +1084,12 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
 	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
 	PartitionKey key = RelationGetPartitionKey(parent);
 	List	   *my_qual = NIL;
+	List	   *inhoids,
+			   *partoids;
+	List	   *boundspecs = NIL;
+	bool		is_def = false;
+	ListCell   *cell;
+	ListCell   *cell2;
 
 	Assert(key != NULL);
 
@@ -901,9 +1097,78 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
 	{
 		case PARTITION_STRATEGY_LIST:
 			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
-			my_qual = get_qual_for_list(key, spec);
-			break;
+			foreach(cell, spec->listdatums)
+			{
+				Node *value = lfirst(cell);
+				if (IsA(value, DefElem))
+					is_def = true;
+			}
+			if (is_def)
+			{
+				/* Collect bound spec nodes in a list. This is done if the partition is
+				 * a default partition. In case of default partition, constraint is formed
+				 * by performing <> operation over the partition constraints of the
+				 * existing partitions.
+				 */
+				inhoids = find_inheritance_children(RelationGetRelid(parent), NoLock);
+				partoids = NIL;
+				foreach(cell2, inhoids)
+				{
+					Oid			inhrelid = lfirst_oid(cell2);
+					HeapTuple	tuple;
+					Datum		datum;
+					bool		isnull;
+					Node	   *boundspec;
+					bool		def_elem = false;
+					PartitionBoundSpec *bspec;
+					ListCell *cell1;
+					ListCell *cell3;
+
+					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);
+					boundspec = (Node *) stringToNode(TextDatumGetCString(datum));
+					bspec = (PartitionBoundSpec *)boundspec;
+					foreach(cell1, bspec->listdatums)
+					{
+						Node *value = lfirst(cell1);
+						if (IsA(value, DefElem))
+							def_elem = true;
+					}
+					if (def_elem)
+					{
+						ReleaseSysCache(tuple);
+						continue;
+					}
+					foreach(cell3, bspec->listdatums)
+					{
+						Node *value = lfirst(cell3);
+						boundspecs = lappend(boundspecs, value);
+					}
+					partoids = lappend_oid(partoids, inhrelid);
+					ReleaseSysCache(tuple);
+				}
+			}
+			my_qual = get_qual_for_list(key, spec, is_def, boundspecs);
+			break;
 		case PARTITION_STRATEGY_RANGE:
 			Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
 			my_qual = get_qual_for_range(key, spec);
@@ -1151,7 +1416,8 @@ RelationGetPartitionDispatchInfo(Relation rel, int lockmode,
  * Returns a list of expressions to use as a list partition's constraint.
  */
 static List *
-get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
+get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec, bool is_def,
+					List *boundspecs)
 {
 	List	   *result;
 	ArrayExpr  *arr;
@@ -1229,7 +1495,10 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 		: key->parttypid[0];
 	arr->array_collid = key->parttypcoll[0];
 	arr->element_typeid = key->parttypid[0];
-	arr->elements = spec->listdatums;
+	if (is_def)
+		arr->elements = boundspecs;
+	else
+		arr->elements = spec->listdatums;
 	arr->multidims = false;
 	arr->location = -1;
 
@@ -1243,15 +1512,33 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 										  key->partcollation[0],
 										  COERCE_EXPLICIT_CAST);
 
+	/* Build leftop <> ALL(rightop). This is for default partition */
+	if (is_def)
+	{
+		/* Find the negator for the partition operator above */
+		if(get_negator(operoid) == InvalidOid)
+			elog(ERROR, "no negator found for partition operator %u",
+				 operoid);
+		operoid = get_negator(operoid);
+		opexpr = makeNode(ScalarArrayOpExpr);
+		opexpr->opno = operoid;
+		opexpr->opfuncid = get_opcode(operoid);
+		opexpr->useOr = false;
+		opexpr->inputcollid = key->partcollation[0];
+		opexpr->args = list_make2(keyCol, arr);
+		opexpr->location = -1;
+	}
 	/* Build leftop = ANY (rightop) */
-	opexpr = makeNode(ScalarArrayOpExpr);
-	opexpr->opno = operoid;
-	opexpr->opfuncid = get_opcode(operoid);
-	opexpr->useOr = true;
-	opexpr->inputcollid = key->partcollation[0];
-	opexpr->args = list_make2(keyCol, arr);
-	opexpr->location = -1;
-
+	else
+	{
+		opexpr = makeNode(ScalarArrayOpExpr);
+		opexpr->opno = operoid;
+		opexpr->opfuncid = get_opcode(operoid);
+		opexpr->useOr = true;
+		opexpr->inputcollid = key->partcollation[0];
+		opexpr->args = list_make2(keyCol, arr);
+		opexpr->location = -1;
+	}
 	if (nulltest1)
 		result = list_make2(nulltest1, opexpr);
 	else if (nulltest2)
@@ -1778,6 +2065,13 @@ get_partition_for_tuple(PartitionDispatch *pd,
 			result = -1;
 			*failed_at = parent;
 			*failed_slot = slot;
+			/*
+			 * If partitioned table has a default partition, return
+			 * its sequence number
+			 */
+			if (partdesc->boundinfo->has_def && key->strategy
+				== PARTITION_STRATEGY_LIST)
+				result = parent->indexes[partdesc->boundinfo->def_index];
 			break;
 		}
 		else if (parent->indexes[cur_index] >= 0)
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d418d56..69268b1 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -750,6 +750,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		ParseState *pstate;
 		Oid			parentId = linitial_oid(inheritOids);
 		Relation	parent;
+		PartitionDesc partdesc;
 
 		/* Already have strong enough lock on the parent */
 		parent = heap_open(parentId, NoLock);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9d53a29..7114d7f 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2596,6 +2596,7 @@ partbound_datum:
 			Sconst			{ $$ = makeStringConst($1, @1); }
 			| NumericOnly	{ $$ = makeAConst($1, @1); }
 			| NULL_P		{ $$ = makeNullAConst(@1); }
+			| DEFAULT  { $$ = (Node *)makeDefElem("DEFAULT", NULL, @1); }
 		;
 
 partbound_datum_list:
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 1ae43dc..17605c3 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3088,47 +3088,50 @@ transformPartitionBound(ParseState *pstate, Relation parent, Node *bound)
 		result_spec->listdatums = NIL;
 		foreach(cell, spec->listdatums)
 		{
-			A_Const    *con = (A_Const *) lfirst(cell);
-			Node	   *value;
-			ListCell   *cell2;
-			bool		duplicate;
-
-			value = (Node *) make_const(pstate, &con->val, con->location);
-			value = coerce_to_target_type(pstate,
-										  value, exprType(value),
-										  get_partition_col_typid(key, 0),
-										  get_partition_col_typmod(key, 0),
-										  COERCION_ASSIGNMENT,
-										  COERCE_IMPLICIT_CAST,
-										  -1);
-
-			if (value == NULL)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
-							 format_type_be(get_partition_col_typid(key, 0)),
-								colname),
-						 parser_errposition(pstate,
-											exprLocation((Node *) con))));
-
-			/* Simplify the expression */
-			value = (Node *) expression_planner((Expr *) value);
-
-			/* Don't add to the result if the value is a duplicate */
-			duplicate = false;
-			foreach(cell2, result_spec->listdatums)
+			Node *value = lfirst(cell);
+			/* Perform the transformation only for non default partition */
+			if (!IsA(value, DefElem))
 			{
-				Const	   *value2 = (Const *) lfirst(cell2);
+				A_Const    *con = (A_Const *) lfirst(cell);
+				ListCell   *cell2;
+				bool		duplicate;
+
+				value = (Node *) make_const(pstate, &con->val, con->location);
+				value = coerce_to_target_type(pstate,
+											value, exprType(value),
+											get_partition_col_typid(key, 0),
+											get_partition_col_typmod(key, 0),
+											COERCION_ASSIGNMENT,
+											COERCE_IMPLICIT_CAST,
+											-1);
+
+				if (value == NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+								format_type_be(get_partition_col_typid(key, 0)),
+									colname),
+							parser_errposition(pstate,
+												exprLocation((Node *) con))));
 
-				if (equal(value, value2))
+				/* Simplify the expression */
+				value = (Node *) expression_planner((Expr *) value);
+
+				/* Don't add to the result if the value is a duplicate */
+				duplicate = false;
+				foreach(cell2, result_spec->listdatums)
 				{
-					duplicate = true;
-					break;
+					Const	   *value2 = (Const *) lfirst(cell2);
+
+					if (equal(value, value2))
+					{
+						duplicate = true;
+						break;
+					}
 				}
+				if (duplicate)
+					continue;
 			}
-			if (duplicate)
-				continue;
-
 			result_spec->listdatums = lappend(result_spec->listdatums,
 											  value);
 		}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 0c1a201..5732bbc 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8559,8 +8559,14 @@ get_rule_expr(Node *node, deparse_context *context,
 						sep = "";
 						foreach(cell, spec->listdatums)
 						{
+							Node *value = lfirst(cell);
 							Const	   *val = lfirst(cell);
 
+							if (IsA(value, DefElem))
+							{
+								appendStringInfoString(buf, "DEFAULT");
+								continue;
+							}
 							appendStringInfoString(buf, sep);
 							get_const_expr(val, context, -1);
 							sep = ", ";
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 421644c..9c92a8a 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -77,6 +77,7 @@ extern bool partition_bounds_equal(PartitionKey key,
 extern void check_new_partition_bound(char *relname, Relation parent, Node *bound);
 extern Oid	get_partition_parent(Oid relid);
 extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
+extern List *generate_qual_for_defaultpart(Relation parent, Node *bound ,Oid *defid);
 extern List *map_partition_varattnos(List *expr, int target_varno,
 						Relation partrel, Relation parent);
 extern List *RelationGetPartitionQual(Relation rel);
#32Keith Fiske
keith@omniti.com
In reply to: Amit Langote (#30)
Re: Adding support for Default partition in partitioning

On Thu, Apr 6, 2017 at 1:18 AM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
wrote:

On 2017/04/06 13:08, Keith Fiske wrote:

On Wed, Apr 5, 2017 at 2:51 PM, Keith Fiske wrote:

Only issue I see with this, and I'm not sure if it is an issue, is what
happens to that default constraint clause when 1000s of partitions start
getting added? From what I gather the default's constraint is built

based

off the cumulative opposite of all other child constraints. I don't
understand the code well enough to see what it's actually doing, but if
there are no gaps, is the method used smart enough to aggregate all the
child constraints to make a simpler constraint that is simply outside

the

current min/max boundaries? If so, for serial/time range partitioning

this

should typically work out fine since there are rarely gaps. This

actually

seems more of an issue for list partitioning where each child is a

distinct

value or range of values that are completely arbitrary. Won't that check
and re-evaluation of the default's constraint just get worse and worse

as

more children are added? Is there really even a need for the default to
have an opposite constraint like this? Not sure on how the planner works
with partitioning now, but wouldn't it be better to first check all
non-default children for a match the same as it does now without a

default

and, failing that, then route to the default if one is declared? The
default should accept any data then so I don't see the need for the
constraint unless it's required for the current implementation. If

that's

the case, could that be changed?

Unless I misread your last sentence, I think there might be some
confusion. Currently, the partition constraint (think of these as you
would of user-defined check constraints) is needed for two reasons: 1. to
prevent direct insertion of rows into the default partition for which a
non-default partition exists; no two partitions should ever have duplicate
rows. 2. so that planner can use the constraint to determine if the
default partition needs to be scanned for a query using constraint
exclusion; no need, for example, to scan the default partition if the
query requests only key=3 rows and a partition for the same exists (no
other partition should have key=3 rows by definition, not even the
default). As things stand today, planner needs to look at every partition
individually for using constraint exclusion to possibly exclude it, *even*
with declarative partitioning and that would include the default partition.

Forgot about constraint exclusion. My follow up email that you answered
below was addressing the prevention of data to the default if there was no
constraint on the default. I guess my main concern was with how manageable
that cumulative opposite constraint of the default would be over time,
especially with list partitioning. And also that it's smart enough to
consolidate constraint conditions to simplify things if it's found that two
or more conditions cover a continuous range.

Actually, thinking on this more, I realized this does again come back to
the lack of a global index. Without the constraint, data could be put
directly into the default that could technically conflict with the
partition scheme elsewhere. Perhaps, instead of the constraint, inserts
directly to the default could be prevented on the user level. Writing to
valid children directly certainly has its place, but been thinking about
it, and I can't see any reason why one would ever want to write directly

to

the default. It's use case seems to be around being a sort of temporary
storage until that data can be moved to a valid location. Would still

need

to allow removal of data, though.

As mentioned above, the default partition will not allow directly
inserting a row whose key maps to some existing (non-default) partition.

As far as tuple-routing is concerned, it will choose the default partition
only if no other partition is found for the key. Tuple-routing doesn't
use the partition constraints directly per se, like one of the two things
mentioned above do. One could say that tuple-routing assigns the incoming
rows to partitions such that their individual partition constraints are
not violated.

Finally, we don't yet offer global guarantees for constraints like unique.

Show quoted text

The only guarantee that's in place is that no two partitions can contain
the same partition key.

Thanks,
Amit

#33Keith Fiske
keith@omniti.com
In reply to: Rahila Syed (#31)
Re: Adding support for Default partition in partitioning

On Thu, Apr 6, 2017 at 7:30 AM, Rahila Syed <rahilasyed90@gmail.com> wrote:

Hello,

Thanks a lot for testing and reporting this. Please find attached an
updated patch with the fix. The patch also contains a fix
regarding operator used at the time of creating expression as default
partition constraint. This was notified offlist by Amit Langote.

Thank you,
Rahila Syed

Could probably use some more extensive testing, but all examples I had on
my previously mentioned blog post are now working.

Keith

#34Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Keith Fiske (#33)
Re: Adding support for Default partition in partitioning

Hi Rahila,

With your latest patch:

Consider a case when a table is partitioned on a boolean key.

Even when there are existing separate partitions for 'true' and

'false', still default partition can be created.

I think this should not be allowed.

Consider following case:

postgres=# CREATE TABLE list_partitioned (

a bool

) PARTITION BY LIST (a);

CREATE TABLE

postgres=# CREATE TABLE part_1 PARTITION OF list_partitioned FOR VALUES IN
('false');

CREATE TABLE

postgres=# CREATE TABLE part_2 PARTITION OF list_partitioned FOR VALUES IN
('true');

CREATE TABLE

postgres=# CREATE TABLE part_default PARTITION OF list_partitioned FOR
VALUES IN (DEFAULT);

CREATE TABLE

The creation of table part_default should have failed instead.

Thanks,

Jeevan Ladhe

On Thu, Apr 6, 2017 at 9:37 PM, Keith Fiske <keith@omniti.com> wrote:

Show quoted text

On Thu, Apr 6, 2017 at 7:30 AM, Rahila Syed <rahilasyed90@gmail.com>
wrote:

Hello,

Thanks a lot for testing and reporting this. Please find attached an
updated patch with the fix. The patch also contains a fix
regarding operator used at the time of creating expression as default
partition constraint. This was notified offlist by Amit Langote.

Thank you,
Rahila Syed

Could probably use some more extensive testing, but all examples I had on
my previously mentioned blog post are now working.

Keith

#35Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Jeevan Ladhe (#34)
Re: Adding support for Default partition in partitioning

Hi Rahila,

I tried to review the code, and here are some of my early comments:

1.
When I configure using "-Werror", I see unused variable in function
DefineRelation:

tablecmds.c: In function ‘DefineRelation’:
tablecmds.c:761:17: error: unused variable ‘partdesc’
[-Werror=unused-variable]
PartitionDesc partdesc;
^

2.
Typo in comment:
+ /*
+ * When adding a list partition after default partition, scan the
+ * default partiton for rows satisfying the new partition
+ * constraint. If found dont allow addition of a new partition.
+ * Otherwise continue with the creation of new  partition.
+ */

partition
don't

3.
I think instead of a period '.', it will be good if you can use semicolon
';'
in following declaration similar to the comment for 'null_index'.

+ int def_index; /* Index of the default list partition. -1 for
+ * range partitioned tables */

4.
You may want to consider 80 column alignment for changes done in function
get_qual_from_partbound, and other places as applicable.

5.
It would be good if the patch has some test coverage that explains what is
being achieved, what kind of error handling is done etc.

6.
There are some places having code like following:

+ Node *value = lfirst(c);
Const *val = lfirst(c);
PartitionListValue *list_value = NULL;

+ if (IsA(value, DefElem))

The additional variable is not needed and you can call IsA on val itself.

7.
Also, in places like below where you are just trying to check for node is a
DefaultElem, you can avoid an extra variable:

+ foreach(cell1, bspec->listdatums)
+ {
+ Node *value = lfirst(cell1);
+ if (IsA(value, DefElem))
+ {
+ def_elem = true;
+ *defid = inhrelid;
+ }
Can be written as:
+ foreach(cell1, bspec->listdatums)
+ {
+ if (IsA(lfirst(cell1), DefElem))
+ {
+ def_elem = true;
+ *defid = inhrelid;
+ }
+ }

Regards,
Jeevan Ladhe

On Mon, Apr 10, 2017 at 8:12 PM, Jeevan Ladhe <jeevan.ladhe@enterprisedb.com

Show quoted text

wrote:

Hi Rahila,

With your latest patch:

Consider a case when a table is partitioned on a boolean key.

Even when there are existing separate partitions for 'true' and

'false', still default partition can be created.

I think this should not be allowed.

Consider following case:

postgres=# CREATE TABLE list_partitioned (

a bool

) PARTITION BY LIST (a);

CREATE TABLE

postgres=# CREATE TABLE part_1 PARTITION OF list_partitioned FOR VALUES IN
('false');

CREATE TABLE

postgres=# CREATE TABLE part_2 PARTITION OF list_partitioned FOR VALUES IN
('true');

CREATE TABLE

postgres=# CREATE TABLE part_default PARTITION OF list_partitioned FOR
VALUES IN (DEFAULT);

CREATE TABLE

The creation of table part_default should have failed instead.

Thanks,

Jeevan Ladhe

On Thu, Apr 6, 2017 at 9:37 PM, Keith Fiske <keith@omniti.com> wrote:

On Thu, Apr 6, 2017 at 7:30 AM, Rahila Syed <rahilasyed90@gmail.com>
wrote:

Hello,

Thanks a lot for testing and reporting this. Please find attached an
updated patch with the fix. The patch also contains a fix
regarding operator used at the time of creating expression as default
partition constraint. This was notified offlist by Amit Langote.

Thank you,
Rahila Syed

Could probably use some more extensive testing, but all examples I had on
my previously mentioned blog post are now working.

Keith

#36Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Jeevan Ladhe (#34)
Re: Adding support for Default partition in partitioning

On Mon, Apr 10, 2017 at 8:12 PM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

Hi Rahila,

With your latest patch:

Consider a case when a table is partitioned on a boolean key.

Even when there are existing separate partitions for 'true' and

'false', still default partition can be created.

I think this should not be allowed.

Well, boolean columns can have "NULL" values which will go into
default partition if no NULL partition exists. So, probably we should
add check for NULL partition there.

--
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

#37Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Ashutosh Bapat (#36)
Re: Adding support for Default partition in partitioning

Hi Ashutosh,

On Tue, Apr 11, 2017 at 6:02 PM, Ashutosh Bapat <
ashutosh.bapat@enterprisedb.com> wrote:

On Mon, Apr 10, 2017 at 8:12 PM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

Hi Rahila,

With your latest patch:

Consider a case when a table is partitioned on a boolean key.

Even when there are existing separate partitions for 'true' and

'false', still default partition can be created.

I think this should not be allowed.

Well, boolean columns can have "NULL" values which will go into
default partition if no NULL partition exists. So, probably we should
add check for NULL partition there.

I have checked for NULLs too, and the default partition can be created even
when there are partitions for each TRUE, FALSE and NULL.

Consider the example below:

postgres=# CREATE TABLE list_partitioned (
a bool
) PARTITION BY LIST (a);
CREATE TABLE
postgres=# CREATE TABLE part_1 PARTITION OF list_partitioned FOR VALUES IN
('false');
CREATE TABLE
postgres=# CREATE TABLE part_2 PARTITION OF list_partitioned FOR VALUES IN
('true');
CREATE TABLE
postgres=# CREATE TABLE part_3 PARTITION OF list_partitioned FOR VALUES IN
(null);
CREATE TABLE
postgres=# CREATE TABLE part_default PARTITION OF list_partitioned FOR
VALUES IN (DEFAULT);
CREATE TABLE

Regards,
Jeevan Ladhe

#38Robert Haas
robertmhaas@gmail.com
In reply to: Jeevan Ladhe (#37)
Re: Adding support for Default partition in partitioning

On Tue, Apr 11, 2017 at 9:41 AM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

I have checked for NULLs too, and the default partition can be created even
when there are partitions for each TRUE, FALSE and NULL.

Consider the example below:

postgres=# CREATE TABLE list_partitioned (
a bool
) PARTITION BY LIST (a);
CREATE TABLE
postgres=# CREATE TABLE part_1 PARTITION OF list_partitioned FOR VALUES IN
('false');
CREATE TABLE
postgres=# CREATE TABLE part_2 PARTITION OF list_partitioned FOR VALUES IN
('true');
CREATE TABLE
postgres=# CREATE TABLE part_3 PARTITION OF list_partitioned FOR VALUES IN
(null);
CREATE TABLE
postgres=# CREATE TABLE part_default PARTITION OF list_partitioned FOR
VALUES IN (DEFAULT);
CREATE TABLE

In my opinion, that's absolutely fine, and it would be very strange to
try to prevent it. The partitioning method shouldn't have specific
knowledge of the properties of individual data types.

--
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

#39Robert Haas
robertmhaas@gmail.com
In reply to: Rushabh Lathia (#29)
Re: Adding support for Default partition in partitioning

On Thu, Apr 6, 2017 at 1:17 AM, Rushabh Lathia <rushabh.lathia@gmail.com> wrote:

I like the idea about having DEFAULT partition for the range partition. With
the
way partition is designed it can have holes into range partition. I think
DEFAULT
for the range partition is a good idea, generally when the range having
holes. When
range is serial then of course DEFAULT partition doen't much sense.

Yes, I like that idea, too. I think the DEFAULT partition should be
allowed to be created for either range or list partitioning regardless
of whether we think there are any holes, but if you create a DEFAULT
partition when there are no holes, you just won't be able to put any
data into it. It's silly, but it's not worth the code that it would
take to try to prevent it.

--
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

#40Robert Haas
robertmhaas@gmail.com
In reply to: Rahila Syed (#31)
Re: Adding support for Default partition in partitioning

On Thu, Apr 6, 2017 at 7:30 AM, Rahila Syed <rahilasyed90@gmail.com> wrote:

Thanks a lot for testing and reporting this. Please find attached an updated
patch with the fix. The patch also contains a fix
regarding operator used at the time of creating expression as default
partition constraint. This was notified offlist by Amit Langote.

I think that the syntax for this patch should probably be revised.
Right now the proposal is for:

CREATE TABLE .. PARTITION OF ... FOR VALUES IN (DEFAULT);

But that's not a good idea for several reasons. For one thing, you
can also write FOR VALUES IN (DEFAULT, 5) or which isn't sensible.
For another thing, this kind of syntax won't generalize to range
partitioning, which we've talked about making this feature support.
Maybe something like:

CREATE TABLE .. PARTITION OF .. DEFAULT;

This patch makes the assumption throughout that any DefElem represents
the word DEFAULT, which is true in the patch as written but doesn't
seem very future-proof. I think the "def" in "DefElem" stands for
"definition" or "define" or something like that, so this is actually
pretty confusing. Maybe we should introduce a dedicated node type to
represent a default-specification in the parser grammar. If not, then
let's at least encapsulate the test a little better, e.g. by adding
isDefaultPartitionBound() which tests not only IsA(..., DefElem) but
also whether the name is DEFAULT as expected. BTW, we typically use
lower-case internally, so if we stick with this representation it
should really be "default" not "DEFAULT".

Useless hunk:

+    bool        has_def;        /* Is there a default partition?
Currently false
+                                 * for a range partitioned table */
+    int            def_index;        /* Index of the default list
partition. -1 for
+                                 * range partitioned tables */

Why abbreviate "default" to def here? Seems pointless.

+                    if (found_def)
+                    {
+                        if (mapping[def_index] == -1)
+                            mapping[def_index] = next_index++;
+                    }

Consider &&

@@ -717,7 +754,6 @@ check_new_partition_bound(char *relname, Relation
parent, Node *bound)
}
}
}
-
break;
}

+ * default partiton for rows satisfying the new partition

Spelling.

+ * constraint. If found dont allow addition of a new partition.

Missing apostrophe.

+        defrel = heap_open(defid, AccessShareLock);
+        tupdesc = CreateTupleDescCopy(RelationGetDescr(defrel));
+
+        /* Build expression execution states for partition check quals */
+        partqualstate = ExecPrepareCheck(partConstraint,
+                        estate);
+
+        econtext = GetPerTupleExprContext(estate);
+        snapshot = RegisterSnapshot(GetLatestSnapshot());

Definitely not safe against concurrency, since AccessShareLock won't
exclude somebody else's update. In fact, it won't even cover somebody
else's already-in-flight transaction.

+ errmsg("new default partition constraint is violated
by some row")));

Normally in such cases we try to give more detail using
ExecBuildSlotValueDescription.

+ bool is_def = true;

This variable starts out true and is never set to any value other than
true. Just get rid of it and, in the one place where it is currently
used, write "true". That's shorter and clearer.

+ inhoids = find_inheritance_children(RelationGetRelid(parent), NoLock);

If it's actually safe to do this with no lock, there ought to be a
comment with a very compelling explanation of why it's safe.

+        boundspec = (Node *) stringToNode(TextDatumGetCString(datum));
+        bspec = (PartitionBoundSpec *)boundspec;

There's not really a reason to cast the result of stringToNode() to
Node * and then turn around and cast it to PartitionBoundSpec *. Just
cast it directly to whatever it needs to be. And use the new castNode
macro.

+        foreach(cell1, bspec->listdatums)
+        {
+            Node *value = lfirst(cell1);
+            if (IsA(value, DefElem))
+            {
+                def_elem = true;
+                *defid = inhrelid;
+            }
+        }
+        if (def_elem)
+        {
+            ReleaseSysCache(tuple);
+            continue;
+        }
+        foreach(cell3, bspec->listdatums)
+        {
+            Node *value = lfirst(cell3);
+            boundspecs = lappend(boundspecs, value);
+        }
+        ReleaseSysCache(tuple);
+    }
+    foreach(cell4, spec->listdatums)
+    {
+        Node *value = lfirst(cell4);
+        boundspecs = lappend(boundspecs, value);
+    }

cell1, cell2, cell3, and cell4 are not very clear variable names.
Between that and the lack of comments, this is not easy to understand.
It's sort of spaghetti logic, too. The if (def_elem) test continues
early, but if the point is that the loop using cell3 shouldn't execute
in that case, why not just put if (!def_elem) { foreach(cell3, ...) {
... } } instead of reiterating the ReleaseSysCache in two places?

+                /* Collect bound spec nodes in a list. This is done
if the partition is
+                 * a default partition. In case of default partition,
constraint is formed
+                 * by performing <> operation over the partition
constraints of the
+                 * existing partitions.
+                 */

I doubt that handles NULLs properly.

+ inhoids =
find_inheritance_children(RelationGetRelid(parent), NoLock);

Again, no lock? Really?

The logic which follows looks largely cut-and-pasted, which makes me
think you need to do some refactoring here to make it more clear
what's going on, so that you have the relevant logic in just one
place. It seems wrong anyway to shove all of this logic specific to
the default case into get_qual_from_partbound() when the logic for the
non-default case is inside get_qual_for_list. Where there were 2
lines of code before you've now got something like 30.

+        if(get_negator(operoid) == InvalidOid)
+            elog(ERROR, "no negator found for partition operator %u",
+                 operoid);

I really doubt that's OK. elog() shouldn't be reachable, but this
will be reachable if the partitioning operator does not have a
negator. And there's the NULL-handling issue I mentioned above, too.

+            if (partdesc->boundinfo->has_def && key->strategy
+                == PARTITION_STRATEGY_LIST)
+                result = parent->indexes[partdesc->boundinfo->def_index];

Testing for PARTITION_STRATEGY_LIST here seems unnecessary. If
has_def (or has_default, as it probably should be) isn't allowed for
range partitions, then it's redundant; if it is allowed, then that
case should be handled too. Also, at this point we've already set
*failed_at and *failed_slot; presumably you'd want to make this check
before you get to that point.

I suspect there are quite a few more problems here in addition to the
ones mentioned above, but I don't think it makes sense to spend too
much time searching for them until some of this basic stuff is cleaned
up.

--
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

#41Rahila Syed
rahilasyed90@gmail.com
In reply to: Robert Haas (#40)
1 attachment(s)
Re: Adding support for Default partition in partitioning

Hello,

Thank you for reviewing.

But that's not a good idea for several reasons. For one thing, you
can also write FOR VALUES IN (DEFAULT, 5) or which isn't sensible.
For another thing, this kind of syntax won't generalize to range
partitioning, which we've talked about making this feature support.
Maybe something like:

CREATE TABLE .. PARTITION OF .. DEFAULT;

I agree that the syntax should be changed to also support range
partitioning.

Following can also be considered as it specifies more clearly that the
partition holds default values.

CREATE TABLE ...PARTITION OF...FOR VALUES DEFAULT;

Maybe we should introduce a dedicated node type to
represent a default-specification in the parser grammar. If not, then
let's at least encapsulate the test a little better, e.g. by adding
isDefaultPartitionBound() which tests not only IsA(..., DefElem) but
also whether the name is DEFAULT as expected. BTW, we typically use
lower-case internally, so if we stick with this representation it
should really be "default" not "DEFAULT".

isDefaultPartitionBound() function is created in the attached patch which
checks for both node type and name.

Why abbreviate "default" to def here? Seems pointless.

Corrected in the attached.

Consider &&

Fixed.

+ * default partiton for rows satisfying the new partition
Spelling.

Fixed.

Missing apostrophe

Fixed.

Definitely not safe against concurrency, since AccessShareLock won't
exclude somebody else's update. In fact, it won't even cover somebody
else's already-in-flight transaction

Changed it to AccessExclusiveLock

Normally in such cases we try to give more detail using
ExecBuildSlotValueDescription.

This function is used in execMain.c and the error is being
reported in partition.c.
Do you mean the error reporting should be moved into execMain.c
to use ExecBuildSlotValueDescription?

This variable starts out true and is never set to any value other than
true. Just get rid of it and, in the one place where it is currently
used, write "true". That's shorter and clearer.

Fixed.

There's not really a reason to cast the result of stringToNode() to
Node * and then turn around and cast it to PartitionBoundSpec *. Just
cast it directly to whatever it needs to be. And use the new castNode
macro

Fixed. castNode macro takes as input Node * whereas stringToNode() takes
string.
IIUC, castNode cant be used here.

The if (def_elem) test continues
early, but if the point is that the loop using cell3 shouldn't execute
in that case, why not just put if (!def_elem) { foreach(cell3, ...) {
... } } instead of reiterating the ReleaseSysCache in two places?

Fixed in the attached.

I will respond to further comments in following email.

On Thu, Apr 13, 2017 at 12:48 AM, Robert Haas <robertmhaas@gmail.com> wrote:

Show quoted text

On Thu, Apr 6, 2017 at 7:30 AM, Rahila Syed <rahilasyed90@gmail.com>
wrote:

Thanks a lot for testing and reporting this. Please find attached an

updated

patch with the fix. The patch also contains a fix
regarding operator used at the time of creating expression as default
partition constraint. This was notified offlist by Amit Langote.

I think that the syntax for this patch should probably be revised.
Right now the proposal is for:

CREATE TABLE .. PARTITION OF ... FOR VALUES IN (DEFAULT);

But that's not a good idea for several reasons. For one thing, you
can also write FOR VALUES IN (DEFAULT, 5) or which isn't sensible.
For another thing, this kind of syntax won't generalize to range
partitioning, which we've talked about making this feature support.
Maybe something like:

CREATE TABLE .. PARTITION OF .. DEFAULT;

This patch makes the assumption throughout that any DefElem represents
the word DEFAULT, which is true in the patch as written but doesn't
seem very future-proof. I think the "def" in "DefElem" stands for
"definition" or "define" or something like that, so this is actually
pretty confusing. Maybe we should introduce a dedicated node type to
represent a default-specification in the parser grammar. If not, then
let's at least encapsulate the test a little better, e.g. by adding
isDefaultPartitionBound() which tests not only IsA(..., DefElem) but
also whether the name is DEFAULT as expected. BTW, we typically use
lower-case internally, so if we stick with this representation it
should really be "default" not "DEFAULT".

Useless hunk:

+    bool        has_def;        /* Is there a default partition?
Currently false
+                                 * for a range partitioned table */
+    int            def_index;        /* Index of the default list
partition. -1 for
+                                 * range partitioned tables */

Why abbreviate "default" to def here? Seems pointless.

+                    if (found_def)
+                    {
+                        if (mapping[def_index] == -1)
+                            mapping[def_index] = next_index++;
+                    }

Consider &&

@@ -717,7 +754,6 @@ check_new_partition_bound(char *relname, Relation
parent, Node *bound)
}
}
}
-
break;
}

+ * default partiton for rows satisfying the new partition

Spelling.

+ * constraint. If found dont allow addition of a new partition.

Missing apostrophe.

+        defrel = heap_open(defid, AccessShareLock);
+        tupdesc = CreateTupleDescCopy(RelationGetDescr(defrel));
+
+        /* Build expression execution states for partition check quals */
+        partqualstate = ExecPrepareCheck(partConstraint,
+                        estate);
+
+        econtext = GetPerTupleExprContext(estate);
+        snapshot = RegisterSnapshot(GetLatestSnapshot());

Definitely not safe against concurrency, since AccessShareLock won't
exclude somebody else's update. In fact, it won't even cover somebody
else's already-in-flight transaction.

+ errmsg("new default partition constraint is violated
by some row")));

Normally in such cases we try to give more detail using
ExecBuildSlotValueDescription.

+ bool is_def = true;

This variable starts out true and is never set to any value other than
true. Just get rid of it and, in the one place where it is currently
used, write "true". That's shorter and clearer.

+ inhoids = find_inheritance_children(RelationGetRelid(parent),
NoLock);

If it's actually safe to do this with no lock, there ought to be a
comment with a very compelling explanation of why it's safe.

+        boundspec = (Node *) stringToNode(TextDatumGetCString(datum));
+        bspec = (PartitionBoundSpec *)boundspec;

There's not really a reason to cast the result of stringToNode() to
Node * and then turn around and cast it to PartitionBoundSpec *. Just
cast it directly to whatever it needs to be. And use the new castNode
macro.

+        foreach(cell1, bspec->listdatums)
+        {
+            Node *value = lfirst(cell1);
+            if (IsA(value, DefElem))
+            {
+                def_elem = true;
+                *defid = inhrelid;
+            }
+        }
+        if (def_elem)
+        {
+            ReleaseSysCache(tuple);
+            continue;
+        }
+        foreach(cell3, bspec->listdatums)
+        {
+            Node *value = lfirst(cell3);
+            boundspecs = lappend(boundspecs, value);
+        }
+        ReleaseSysCache(tuple);
+    }
+    foreach(cell4, spec->listdatums)
+    {
+        Node *value = lfirst(cell4);
+        boundspecs = lappend(boundspecs, value);
+    }

cell1, cell2, cell3, and cell4 are not very clear variable names.
Between that and the lack of comments, this is not easy to understand.
It's sort of spaghetti logic, too. The if (def_elem) test continues
early, but if the point is that the loop using cell3 shouldn't execute
in that case, why not just put if (!def_elem) { foreach(cell3, ...) {
... } } instead of reiterating the ReleaseSysCache in two places?

+                /* Collect bound spec nodes in a list. This is done
if the partition is
+                 * a default partition. In case of default partition,
constraint is formed
+                 * by performing <> operation over the partition
constraints of the
+                 * existing partitions.
+                 */

I doubt that handles NULLs properly.

+ inhoids =
find_inheritance_children(RelationGetRelid(parent), NoLock);

Again, no lock? Really?

The logic which follows looks largely cut-and-pasted, which makes me
think you need to do some refactoring here to make it more clear
what's going on, so that you have the relevant logic in just one
place. It seems wrong anyway to shove all of this logic specific to
the default case into get_qual_from_partbound() when the logic for the
non-default case is inside get_qual_for_list. Where there were 2
lines of code before you've now got something like 30.

+        if(get_negator(operoid) == InvalidOid)
+            elog(ERROR, "no negator found for partition operator %u",
+                 operoid);

I really doubt that's OK. elog() shouldn't be reachable, but this
will be reachable if the partitioning operator does not have a
negator. And there's the NULL-handling issue I mentioned above, too.

+            if (partdesc->boundinfo->has_def && key->strategy
+                == PARTITION_STRATEGY_LIST)
+                result = parent->indexes[partdesc->boundinfo->def_index];

Testing for PARTITION_STRATEGY_LIST here seems unnecessary. If
has_def (or has_default, as it probably should be) isn't allowed for
range partitions, then it's redundant; if it is allowed, then that
case should be handled too. Also, at this point we've already set
*failed_at and *failed_slot; presumably you'd want to make this check
before you get to that point.

I suspect there are quite a few more problems here in addition to the
ones mentioned above, but I don't think it makes sense to spend too
much time searching for them until some of this basic stuff is cleaned
up.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

Attachments:

default_partition_v7.patchapplication/x-download; name=default_partition_v7.patchDownload
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index e0d2665..47b6c3d 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -34,6 +34,7 @@
 #include "nodes/nodeFuncs.h"
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
+#include "optimizer/prep.h"
 #include "optimizer/planmain.h"
 #include "optimizer/var.h"
 #include "rewrite/rewriteManip.h"
@@ -90,6 +91,10 @@ typedef struct PartitionBoundInfoData
 								 * for range partitioned tables */
 	int			null_index;		/* Index of the null-accepting partition; -1
 								 * for range partitioned tables */
+	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 */
 } PartitionBoundInfoData;
 
 /*
@@ -118,7 +123,8 @@ static int32 qsort_partition_list_value_cmp(const void *a, const void *b,
 static int32 qsort_partition_rbound_cmp(const void *a, const void *b,
 						   void *arg);
 
-static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec,
+								bool is_def, List *boundspecs);
 static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
 static Oid get_partition_operator(PartitionKey key, int col,
 					   StrategyNumber strategy, bool *need_relabel);
@@ -166,6 +172,8 @@ RelationBuildPartitionDesc(Relation rel)
 	/* List partitioning specific */
 	PartitionListValue **all_values = NULL;
 	bool		found_null = false;
+	bool		found_def = false;
+	int			def_index = -1;
 	int			null_index = -1;
 
 	/* Range partitioning specific */
@@ -249,9 +257,16 @@ RelationBuildPartitionDesc(Relation rel)
 
 				foreach(c, spec->listdatums)
 				{
+					Node *value = lfirst(c);
 					Const	   *val = lfirst(c);
 					PartitionListValue *list_value = NULL;
 
+					if (isDefaultPartitionBound(value))
+					{
+						found_def = true;
+						def_index = i;
+						continue;
+					}
 					if (!val->constisnull)
 					{
 						list_value = (PartitionListValue *)
@@ -459,6 +474,7 @@ RelationBuildPartitionDesc(Relation rel)
 			case PARTITION_STRATEGY_LIST:
 				{
 					boundinfo->has_null = found_null;
+					boundinfo->has_default = found_def;
 					boundinfo->indexes = (int *) palloc(ndatums * sizeof(int));
 
 					/*
@@ -496,6 +512,8 @@ RelationBuildPartitionDesc(Relation rel)
 						if (mapping[null_index] == -1)
 							mapping[null_index] = next_index++;
 					}
+					if (found_def && mapping[def_index] == -1)
+						mapping[def_index] = next_index++;
 
 					/* All partition must now have a valid mapping */
 					Assert(next_index == nparts);
@@ -504,6 +522,11 @@ RelationBuildPartitionDesc(Relation rel)
 						boundinfo->null_index = mapping[null_index];
 					else
 						boundinfo->null_index = -1;
+
+					if (found_def)
+						boundinfo->default_index = mapping[def_index];
+					else
+						boundinfo->default_index = -1;
 					break;
 				}
 
@@ -671,6 +694,7 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
 	PartitionKey key = RelationGetPartitionKey(parent);
 	PartitionDesc partdesc = RelationGetPartitionDesc(parent);
+	PartitionBoundInfo boundinfo;
 	ParseState *pstate = make_parsestate(NULL);
 	int			with = -1;
 	bool		overlap = false;
@@ -679,6 +703,8 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 	{
 		case PARTITION_STRATEGY_LIST:
 			{
+				boundinfo = partdesc->boundinfo;
+
 				Assert(spec->strategy == PARTITION_STRATEGY_LIST);
 
 				if (partdesc->nparts > 0)
@@ -688,13 +714,15 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 
 					Assert(boundinfo &&
 						   boundinfo->strategy == PARTITION_STRATEGY_LIST &&
-						   (boundinfo->ndatums > 0 || boundinfo->has_null));
+						   (boundinfo->ndatums > 0 || boundinfo->has_null
+							|| boundinfo->has_default));
 
 					foreach(cell, spec->listdatums)
 					{
-						Const	   *val = lfirst(cell);
+						Node	   *value = lfirst(cell);
+						Const	   *val = (Const *) value;
 
-						if (!val->constisnull)
+						if (!val->constisnull && !(isDefaultPartitionBound(value)))
 						{
 							int			offset;
 							bool		equal;
@@ -709,7 +737,14 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 								break;
 							}
 						}
-						else if (boundinfo->has_null)
+						else if (isDefaultPartitionBound(value) &&
+								 boundinfo->has_default)
+						{
+							overlap = true;
+							with = boundinfo->default_index;
+							break;
+						}
+						else if (val->constisnull && boundinfo->has_null)
 						{
 							overlap = true;
 							with = boundinfo->null_index;
@@ -836,6 +871,76 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 						relname, get_rel_name(partdesc->oids[with])),
 				 parser_errposition(pstate, spec->location)));
 	}
+
+	/*
+	 * 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.
+	 */
+	if (spec->strategy == PARTITION_STRATEGY_LIST && partdesc->nparts > 0
+		&& boundinfo->has_default)
+	{
+		List	       *partConstraint = NIL;
+		ExprContext    *econtext;
+		EState	       *estate;
+		Relation	    defrel;
+		HeapScanDesc    scan;
+		HeapTuple	    tuple;
+		ExprState	   *partqualstate = NULL;
+		Snapshot	    snapshot;
+		Oid			    defid;
+		MemoryContext   oldCxt;
+		TupleTableSlot *tupslot;
+		TupleDesc	    tupdesc;
+
+		partConstraint = generate_qual_for_defaultpart(parent, bound, &defid);
+		partConstraint = (List *) eval_const_expressions(NULL,
+													(Node *) partConstraint);
+		partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
+		partConstraint = list_make1(make_ands_explicit(partConstraint));
+
+		/*
+		 * Generate the constraint and default execution states
+		 */
+		estate = CreateExecutorState();
+
+		defrel = heap_open(defid, AccessExclusiveLock);
+		tupdesc = CreateTupleDescCopy(RelationGetDescr(defrel));
+
+		/* Build expression execution states for partition check quals */
+		partqualstate = ExecPrepareCheck(partConstraint,
+						estate);
+
+		econtext = GetPerTupleExprContext(estate);
+		snapshot = RegisterSnapshot(GetLatestSnapshot());
+		scan = heap_beginscan(defrel, snapshot, 0, NULL);
+		tupslot = MakeSingleTupleTableSlot(tupdesc);
+
+		/*
+		 * Switch to per-tuple memory context and reset it for each tuple
+		 * produced, so we don't leak memory.
+		 */
+		oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		{
+			ExecStoreTuple(tuple, tupslot, InvalidBuffer, false);
+			econtext->ecxt_scantuple = tupslot;
+			if (partqualstate && !ExecCheck(partqualstate, econtext))
+				ereport(ERROR,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+				errmsg("new default partition constraint is violated by some row")));
+			ResetExprContext(econtext);
+			CHECK_FOR_INTERRUPTS();
+		}
+		MemoryContextSwitchTo(oldCxt);
+		heap_endscan(scan);
+		UnregisterSnapshot(snapshot);
+		heap_close(defrel, AccessExclusiveLock);
+		ExecDropSingleTupleTableSlot(tupslot);
+	}
+
 }
 
 /*
@@ -884,6 +989,102 @@ get_partition_parent(Oid relid)
 }
 
 /*
+ * Returns true if the partition bound is default
+ */
+bool
+isDefaultPartitionBound(Node *value)
+{
+	if (IsA(value, DefElem))
+	{
+		DefElem *defvalue = (DefElem *) value;
+		if(!strcmp(defvalue->defname, "DEFAULT"))
+			return true;
+		return false;
+	}
+	return false;
+}
+
+/*
+ * Return a list of executable expressions as new partition constraint
+ * for default partition while adding a new partition after default
+ */
+List *
+generate_qual_for_defaultpart(Relation parent, Node *bound, Oid * defid)
+{
+	PartitionKey key = RelationGetPartitionKey(parent);
+	PartitionBoundSpec *spec;
+	List	   *partConstraint = NIL;
+	List	   *inhoids;
+	ListCell   *cell2;
+	ListCell   *cell4;
+	List       *boundspecs = NIL;
+
+	spec = (PartitionBoundSpec *) bound;
+
+	inhoids = find_inheritance_children(RelationGetRelid(parent), NoLock);
+
+	foreach(cell2, inhoids)
+	{
+		PartitionBoundSpec *bspec;
+		Oid			inhrelid = lfirst_oid(cell2);
+		HeapTuple	tuple;
+		Datum		datum;
+		bool		isnull;
+		bool		def_elem = false;
+		ListCell   *cell1;
+		ListCell   *cell3;
+
+		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));
+		foreach(cell1, bspec->listdatums)
+		{
+			Node *value = lfirst(cell1);
+			if (isDefaultPartitionBound(value))
+			{
+				def_elem = true;
+				*defid = inhrelid;
+			}
+		}
+		if (!def_elem)
+		{
+			foreach(cell3, bspec->listdatums)
+			{
+				Node *value = lfirst(cell3);
+				boundspecs = lappend(boundspecs, value);
+			}
+		}
+		ReleaseSysCache(tuple);
+	}
+	foreach(cell4, spec->listdatums)
+	{
+		Node *value = lfirst(cell4);
+		boundspecs = lappend(boundspecs, value);
+	}
+	partConstraint = get_qual_for_list(key, spec, true, boundspecs);
+	return partConstraint;
+}
+
+/*
  * get_qual_from_partbound
  *		Given a parser node for partition bound, return the list of executable
  *		expressions as partition constraint
@@ -894,6 +1095,11 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
 	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
 	PartitionKey key = RelationGetPartitionKey(parent);
 	List	   *my_qual = NIL;
+	List	   *inhoids;
+	List	   *boundspecs = NIL;
+	bool		is_def = false;
+	ListCell   *cell;
+	ListCell   *cell2;
 
 	Assert(key != NULL);
 
@@ -901,9 +1107,72 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
 	{
 		case PARTITION_STRATEGY_LIST:
 			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
-			my_qual = get_qual_for_list(key, spec);
-			break;
+			foreach(cell, spec->listdatums)
+			{
+				Node *value = lfirst(cell);
+				if (isDefaultPartitionBound(value))
+					is_def = true;
+			}
+			if (is_def)
+			{
+				/* Collect bound spec nodes in a list. This is done if the partition is
+				 * a default partition. In case of default partition, constraint is formed
+				 * by performing <> operation over the partition constraints of the
+				 * existing partitions.
+				 */
+				inhoids = find_inheritance_children(RelationGetRelid(parent), NoLock);
+				foreach(cell2, inhoids)
+				{
+					Oid			inhrelid = lfirst_oid(cell2);
+					HeapTuple	tuple;
+					Datum		datum;
+					bool		isnull;
+					bool		def_elem = false;
+					PartitionBoundSpec *bspec;
+					ListCell *cell1;
+					ListCell *cell3;
+
+					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));
+					foreach(cell1, bspec->listdatums)
+					{
+						Node *value = lfirst(cell1);
+						if (isDefaultPartitionBound(value))
+							def_elem = true;
+					}
+					if (!def_elem)
+					{
+						foreach(cell3, bspec->listdatums)
+						{
+							Node *value = lfirst(cell3);
+							boundspecs = lappend(boundspecs, value);
+						}
+					}
+					ReleaseSysCache(tuple);
+				}
+			}
+			my_qual = get_qual_for_list(key, spec, is_def, boundspecs);
+			break;
 		case PARTITION_STRATEGY_RANGE:
 			Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
 			my_qual = get_qual_for_range(key, spec);
@@ -1151,7 +1420,8 @@ RelationGetPartitionDispatchInfo(Relation rel, int lockmode,
  * Returns a list of expressions to use as a list partition's constraint.
  */
 static List *
-get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
+get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec, bool is_def,
+					List *boundspecs)
 {
 	List	   *result;
 	ArrayExpr  *arr;
@@ -1229,7 +1499,10 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 		: key->parttypid[0];
 	arr->array_collid = key->parttypcoll[0];
 	arr->element_typeid = key->parttypid[0];
-	arr->elements = spec->listdatums;
+	if (is_def)
+		arr->elements = boundspecs;
+	else
+		arr->elements = spec->listdatums;
 	arr->multidims = false;
 	arr->location = -1;
 
@@ -1243,15 +1516,33 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 										  key->partcollation[0],
 										  COERCE_EXPLICIT_CAST);
 
+	/* Build leftop <> ALL(rightop). This is for default partition */
+	if (is_def)
+	{
+		/* Find the negator for the partition operator above */
+		if(get_negator(operoid) == InvalidOid)
+			elog(ERROR, "no negator found for partition operator %u",
+				 operoid);
+		operoid = get_negator(operoid);
+		opexpr = makeNode(ScalarArrayOpExpr);
+		opexpr->opno = operoid;
+		opexpr->opfuncid = get_opcode(operoid);
+		opexpr->useOr = false;
+		opexpr->inputcollid = key->partcollation[0];
+		opexpr->args = list_make2(keyCol, arr);
+		opexpr->location = -1;
+	}
 	/* Build leftop = ANY (rightop) */
-	opexpr = makeNode(ScalarArrayOpExpr);
-	opexpr->opno = operoid;
-	opexpr->opfuncid = get_opcode(operoid);
-	opexpr->useOr = true;
-	opexpr->inputcollid = key->partcollation[0];
-	opexpr->args = list_make2(keyCol, arr);
-	opexpr->location = -1;
-
+	else
+	{
+		opexpr = makeNode(ScalarArrayOpExpr);
+		opexpr->opno = operoid;
+		opexpr->opfuncid = get_opcode(operoid);
+		opexpr->useOr = true;
+		opexpr->inputcollid = key->partcollation[0];
+		opexpr->args = list_make2(keyCol, arr);
+		opexpr->location = -1;
+	}
 	if (nulltest1)
 		result = list_make2(nulltest1, opexpr);
 	else if (nulltest2)
@@ -1778,6 +2069,13 @@ get_partition_for_tuple(PartitionDispatch *pd,
 			result = -1;
 			*failed_at = parent;
 			*failed_slot = slot;
+			/*
+			 * If partitioned table has a default partition, return
+			 * its sequence number
+			 */
+			if (partdesc->boundinfo->has_default && key->strategy
+				== PARTITION_STRATEGY_LIST)
+				result = parent->indexes[partdesc->boundinfo->default_index];
 			break;
 		}
 		else if (parent->indexes[cur_index] >= 0)
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index a02904c..7277f6f 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -759,6 +759,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		ParseState *pstate;
 		Oid			parentId = linitial_oid(inheritOids);
 		Relation	parent;
+		PartitionDesc partdesc;
 
 		/* Already have strong enough lock on the parent */
 		parent = heap_open(parentId, NoLock);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 89d2836..e0b27cf 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2677,6 +2677,7 @@ partbound_datum:
 			Sconst			{ $$ = makeStringConst($1, @1); }
 			| NumericOnly	{ $$ = makeAConst($1, @1); }
 			| NULL_P		{ $$ = makeNullAConst(@1); }
+			| DEFAULT  { $$ = (Node *)makeDefElem("DEFAULT", NULL, @1); }
 		;
 
 partbound_datum_list:
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e546194..c73f963 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3304,47 +3304,50 @@ transformPartitionBound(ParseState *pstate, Relation parent, Node *bound)
 		result_spec->listdatums = NIL;
 		foreach(cell, spec->listdatums)
 		{
-			A_Const    *con = (A_Const *) lfirst(cell);
-			Node	   *value;
-			ListCell   *cell2;
-			bool		duplicate;
-
-			value = (Node *) make_const(pstate, &con->val, con->location);
-			value = coerce_to_target_type(pstate,
-										  value, exprType(value),
-										  get_partition_col_typid(key, 0),
-										  get_partition_col_typmod(key, 0),
-										  COERCION_ASSIGNMENT,
-										  COERCE_IMPLICIT_CAST,
-										  -1);
-
-			if (value == NULL)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
-							 format_type_be(get_partition_col_typid(key, 0)),
-								colname),
-						 parser_errposition(pstate,
-											exprLocation((Node *) con))));
-
-			/* Simplify the expression */
-			value = (Node *) expression_planner((Expr *) value);
-
-			/* Don't add to the result if the value is a duplicate */
-			duplicate = false;
-			foreach(cell2, result_spec->listdatums)
+			Node *value = lfirst(cell);
+			/* Perform the transformation only for non default partition */
+			if (!(isDefaultPartitionBound(value)))
 			{
-				Const	   *value2 = (Const *) lfirst(cell2);
+				A_Const    *con = (A_Const *) lfirst(cell);
+				ListCell   *cell2;
+				bool		duplicate;
+
+				value = (Node *) make_const(pstate, &con->val, con->location);
+				value = coerce_to_target_type(pstate,
+											value, exprType(value),
+											get_partition_col_typid(key, 0),
+											get_partition_col_typmod(key, 0),
+											COERCION_ASSIGNMENT,
+											COERCE_IMPLICIT_CAST,
+											-1);
+
+				if (value == NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+								format_type_be(get_partition_col_typid(key, 0)),
+									colname),
+							parser_errposition(pstate,
+												exprLocation((Node *) con))));
 
-				if (equal(value, value2))
+				/* Simplify the expression */
+				value = (Node *) expression_planner((Expr *) value);
+
+				/* Don't add to the result if the value is a duplicate */
+				duplicate = false;
+				foreach(cell2, result_spec->listdatums)
 				{
-					duplicate = true;
-					break;
+					Const	   *value2 = (Const *) lfirst(cell2);
+
+					if (equal(value, value2))
+					{
+						duplicate = true;
+						break;
+					}
 				}
+				if (duplicate)
+					continue;
 			}
-			if (duplicate)
-				continue;
-
 			result_spec->listdatums = lappend(result_spec->listdatums,
 											  value);
 		}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 184e5da..65d8821 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -24,6 +24,7 @@
 #include "access/sysattr.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
+#include "catalog/partition.h"
 #include "catalog/pg_aggregate.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_authid.h"
@@ -8619,8 +8620,14 @@ get_rule_expr(Node *node, deparse_context *context,
 						sep = "";
 						foreach(cell, spec->listdatums)
 						{
+							Node *value = lfirst(cell);
 							Const	   *val = lfirst(cell);
 
+							if (isDefaultPartitionBound(value))
+							{
+								appendStringInfoString(buf, "DEFAULT");
+								continue;
+							}
 							appendStringInfoString(buf, sep);
 							get_const_expr(val, context, -1);
 							sep = ", ";
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 421644c..40da35e 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -73,10 +73,11 @@ typedef struct PartitionDispatchData *PartitionDispatch;
 extern void RelationBuildPartitionDesc(Relation relation);
 extern bool partition_bounds_equal(PartitionKey key,
 					   PartitionBoundInfo p1, PartitionBoundInfo p2);
-
+extern bool isDefaultPartitionBound(Node *value);
 extern void check_new_partition_bound(char *relname, Relation parent, Node *bound);
 extern Oid	get_partition_parent(Oid relid);
 extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
+extern List *generate_qual_for_defaultpart(Relation parent, Node *bound ,Oid *defid);
 extern List *map_partition_varattnos(List *expr, int target_varno,
 						Relation partrel, Relation parent);
 extern List *RelationGetPartitionQual(Relation rel);
#42Robert Haas
robertmhaas@gmail.com
In reply to: Rahila Syed (#41)
Re: Adding support for Default partition in partitioning

On Mon, Apr 24, 2017 at 5:10 AM, Rahila Syed <rahilasyed90@gmail.com> wrote:

Following can also be considered as it specifies more clearly that the
partition holds default values.

CREATE TABLE ...PARTITION OF...FOR VALUES DEFAULT;

Yes, that could be done. But I don't think it's correct to say that
the partition holds default values. Let's back up and ask what the
word "default" means. The relevant definition (according to Google or
whoever they stole it from) is:

a preselected option adopted by a computer program or other mechanism
when no alternative is specified by the user or programmer.

So, a default *value* is the value that is used when no alternative is
specified by the user or programmer. We have that concept, but it's
not what we're talking about here: that's configured by applying the
DEFAULT property to a column. Here, we're talking about the default
*partition*, or in other words the *partition* that is used when no
alternative is specified by the user or programmer. So, that's why I
proposed the syntax I did. The partition doesn't contain default
values; it is itself a default.

--
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

#43Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Robert Haas (#42)
Re: Adding support for Default partition in partitioning

On Mon, Apr 24, 2017 at 4:24 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Mon, Apr 24, 2017 at 5:10 AM, Rahila Syed <rahilasyed90@gmail.com> wrote:

Following can also be considered as it specifies more clearly that the
partition holds default values.

CREATE TABLE ...PARTITION OF...FOR VALUES DEFAULT;

Yes, that could be done. But I don't think it's correct to say that
the partition holds default values. Let's back up and ask what the
word "default" means. The relevant definition (according to Google or
whoever they stole it from) is:

a preselected option adopted by a computer program or other mechanism
when no alternative is specified by the user or programmer.

So, a default *value* is the value that is used when no alternative is
specified by the user or programmer. We have that concept, but it's
not what we're talking about here: that's configured by applying the
DEFAULT property to a column. Here, we're talking about the default
*partition*, or in other words the *partition* that is used when no
alternative is specified by the user or programmer. So, that's why I
proposed the syntax I did. The partition doesn't contain default
values; it is itself a default.

Is CREATE TABLE ... DEFAULT PARTITION OF ... feasible? That sounds more natural.

--
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

#44Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Ashutosh Bapat (#43)
Re: Adding support for Default partition in partitioning

Hi Rahila,

I tried to go through your v7 patch, and following are my comments:

1.
With -Werrors I see following compilation failure:

parse_utilcmd.c: In function ‘transformPartitionBound’:
parse_utilcmd.c:3309:4: error: implicit declaration of function
‘isDefaultPartitionBound’ [-Werror=implicit-function-declaration]
if (!(isDefaultPartitionBound(value)))
^
cc1: all warnings being treated as errors

You need to include, "catalog/partitions.h".

2.
Once I made above change pass, I see following error:
tablecmds.c: In function ‘DefineRelation’:
tablecmds.c:762:17: error: unused variable ‘partdesc’
[-Werror=unused-variable]
PartitionDesc partdesc;
^
cc1: all warnings being treated as errors

3.
Please remove the extra line at the end of the function
check_new_partition_bound:
+ MemoryContextSwitchTo(oldCxt);
+ heap_endscan(scan);
+ UnregisterSnapshot(snapshot);
+ heap_close(defrel, AccessExclusiveLock);
+ ExecDropSingleTupleTableSlot(tupslot);
+ }
+
 }

4.
In generate_qual_for_defaultpart() you do not need 2 pointers for looping
over
bound specs:
+ ListCell *cell1;
+ ListCell *cell3;
You can iterate twice using one pointer itself.

Same is for:
+ ListCell   *cell2;
+ ListCell   *cell4;
Similarly, in get_qual_from_partbound(), you can use one pointer below,
instead of cell1 and cell3:
+ PartitionBoundSpec *bspec;
+ ListCell *cell1;
+ ListCell *cell3;
5.
Should this have a break in if block?
+ foreach(cell1, bspec->listdatums)
+ {
+ Node *value = lfirst(cell1);
+ if (isDefaultPartitionBound(value))
+ {
+ def_elem = true;
+ *defid = inhrelid;
+ }
+ }

6.
I am wondering, isn't it possible to retrieve the has_default and
default_index
here to find out if default partition exists and if exist then find it's oid
using rd_partdesc, that would avoid above(7) loop to check if partition
bound is
default.

7.
The output of describe needs to be improved.
Consider following case:
postgres=# CREATE TABLE part_1 PARTITION OF list_partitioned FOR VALUES IN
(4,5,4,4,4,6,2);
ERROR: relation "list_partitioned" does not exist
postgres=# CREATE TABLE list_partitioned (
a int
) PARTITION BY LIST (a);
CREATE TABLE
postgres=# CREATE TABLE part_1 PARTITION OF list_partitioned FOR VALUES IN
(4,5,4,4,4,6,2);
CREATE TABLE
postgres=# CREATE TABLE part_default PARTITION OF list_partitioned FOR
VALUES IN (DEFAULT, 3, DEFAULT, 3, DEFAULT);
CREATE TABLE
postgres=# \d+ part_1;
Table "public.part_1"
Column | Type | Collation | Nullable | Default | Storage | Stats target
| Description
--------+---------+-----------+----------+---------+---------+--------------+-------------
a | integer | | | | plain |
|
Partition of: list_partitioned FOR VALUES IN (4, 5, 6, 2)

postgres=# \d+ part_default;
Table "public.part_default"
Column | Type | Collation | Nullable | Default | Storage | Stats target
| Description
--------+---------+-----------+----------+---------+---------+--------------+-------------
a | integer | | | | plain |
|
Partition of: list_partitioned FOR VALUES IN (DEFAULT3DEFAULTDEFAULT)

As you can see in above example, part_1 has multiple entries for 4 while
creating the partition, but describe shows only one entry for 4 in values
set.
Similarly, part_default has multiple entries for 3 and DEFAULT while
creating
the partition, but the describe shows a weired output. Instead, we should
have
just one entry saying "VALUES IN (DEFAULT, 3)":

postgres=# \d+ part_default;
Table "public.part_default"
Column | Type | Collation | Nullable | Default | Storage | Stats target
| Description
--------+---------+-----------+----------+---------+---------+--------------+-------------
a | integer | | | | plain |
|
Partition of: list_partitioned FOR VALUES IN (DEFAULT, 3)

8.
Following call to find_inheritance_children() in
generate_qual_for_defaultpart()
is an overhead, instead we can simply use an array of oids in rd_partdesc.

+ spec = (PartitionBoundSpec *) bound;
+
+ inhoids = find_inheritance_children(RelationGetRelid(parent), NoLock);
+
+ foreach(cell2, inhoids)

Same is for the call in get_qual_from_partbound:

+ /* Collect bound spec nodes in a list. This is done if the partition is
+ * a default partition. In case of default partition, constraint is formed
+ * by performing <> operation over the partition constraints of the
+ * existing partitions.
+ */
+ inhoids = find_inheritance_children(RelationGetRelid(parent), NoLock);
+ foreach(cell2, inhoids)

9.
How about rephrasing following error message:
postgres=# CREATE TABLE part_2 PARTITION OF list_partitioned FOR VALUES IN
(14);
ERROR: new default partition constraint is violated by some row

To,
"ERROR: some existing row in default partition violates new default
partition constraint"

10.
Additionally, I did test your given sample test in first post and the one
mentioned by Keith; both of them are passing without errors.
Also, I did a pg_dump test and it is dumping the partitions and data
correctly.
But as mentioned earlier, it would be good if you have them in your patch.

I will do further review and let you know comments if any.

Regards,
Jeevan Ladhe

On Mon, Apr 24, 2017 at 5:44 PM, Ashutosh Bapat <
ashutosh.bapat@enterprisedb.com> wrote:

Show quoted text

On Mon, Apr 24, 2017 at 4:24 PM, Robert Haas <robertmhaas@gmail.com>
wrote:

On Mon, Apr 24, 2017 at 5:10 AM, Rahila Syed <rahilasyed90@gmail.com>

wrote:

Following can also be considered as it specifies more clearly that the
partition holds default values.

CREATE TABLE ...PARTITION OF...FOR VALUES DEFAULT;

Yes, that could be done. But I don't think it's correct to say that
the partition holds default values. Let's back up and ask what the
word "default" means. The relevant definition (according to Google or
whoever they stole it from) is:

a preselected option adopted by a computer program or other mechanism
when no alternative is specified by the user or programmer.

So, a default *value* is the value that is used when no alternative is
specified by the user or programmer. We have that concept, but it's
not what we're talking about here: that's configured by applying the
DEFAULT property to a column. Here, we're talking about the default
*partition*, or in other words the *partition* that is used when no
alternative is specified by the user or programmer. So, that's why I
proposed the syntax I did. The partition doesn't contain default
values; it is itself a default.

Is CREATE TABLE ... DEFAULT PARTITION OF ... feasible? That sounds more
natural.

--
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

#45Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Ashutosh Bapat (#43)
Re: Adding support for Default partition in partitioning

On Mon, Apr 24, 2017 at 5:44 PM, Ashutosh Bapat <
ashutosh.bapat@enterprisedb.com> wrote:

On Mon, Apr 24, 2017 at 4:24 PM, Robert Haas <robertmhaas@gmail.com>
wrote:

On Mon, Apr 24, 2017 at 5:10 AM, Rahila Syed <rahilasyed90@gmail.com>

wrote:

Following can also be considered as it specifies more clearly that the
partition holds default values.

CREATE TABLE ...PARTITION OF...FOR VALUES DEFAULT;

Yes, that could be done. But I don't think it's correct to say that
the partition holds default values. Let's back up and ask what the
word "default" means. The relevant definition (according to Google or
whoever they stole it from) is:

a preselected option adopted by a computer program or other mechanism
when no alternative is specified by the user or programmer.

So, a default *value* is the value that is used when no alternative is
specified by the user or programmer. We have that concept, but it's
not what we're talking about here: that's configured by applying the
DEFAULT property to a column. Here, we're talking about the default
*partition*, or in other words the *partition* that is used when no
alternative is specified by the user or programmer. So, that's why I
proposed the syntax I did. The partition doesn't contain default
values; it is itself a default.

Is CREATE TABLE ... DEFAULT PARTITION OF ... feasible? That sounds more
natural.

+1

#46Robert Haas
robertmhaas@gmail.com
In reply to: Ashutosh Bapat (#43)
Re: Adding support for Default partition in partitioning

On Mon, Apr 24, 2017 at 8:14 AM, Ashutosh Bapat
<ashutosh.bapat@enterprisedb.com> wrote:

On Mon, Apr 24, 2017 at 4:24 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Mon, Apr 24, 2017 at 5:10 AM, Rahila Syed <rahilasyed90@gmail.com> wrote:

Following can also be considered as it specifies more clearly that the
partition holds default values.

CREATE TABLE ...PARTITION OF...FOR VALUES DEFAULT;

The partition doesn't contain default values; it is itself a default.

Is CREATE TABLE ... DEFAULT PARTITION OF ... feasible? That sounds more natural.

I suspect it could be done as of now, but I'm a little worried that it
might create grammar conflicts in the future as we extend the syntax
further. If we use CREATE TABLE ... PARTITION OF .. DEFAULT, then the
word DEFAULT appears in the same position where we'd normally have FOR
VALUES, and so the parser will definitely be able to figure out what's
going on. When it gets to that position, it will see FOR or it will
see DEFAULT, and all is clear. OTOH, if we use CREATE TABLE ...
DEFAULT PARTITION OF ..., then we have action at a distance: whether
or not the word DEFAULT is present before PARTITION affects which
tokens are legal after the parent table name. bison isn't always very
smart about that kind of thing. No particular dangers come to mind at
the moment, but it makes me nervous anyway.

--
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

#47Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#46)
Re: Adding support for Default partition in partitioning

On 2017/04/25 5:16, Robert Haas wrote:

On Mon, Apr 24, 2017 at 8:14 AM, Ashutosh Bapat
<ashutosh.bapat@enterprisedb.com> wrote:

On Mon, Apr 24, 2017 at 4:24 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Mon, Apr 24, 2017 at 5:10 AM, Rahila Syed <rahilasyed90@gmail.com> wrote:

Following can also be considered as it specifies more clearly that the
partition holds default values.

CREATE TABLE ...PARTITION OF...FOR VALUES DEFAULT;

The partition doesn't contain default values; it is itself a default.

Is CREATE TABLE ... DEFAULT PARTITION OF ... feasible? That sounds more natural.

I suspect it could be done as of now, but I'm a little worried that it
might create grammar conflicts in the future as we extend the syntax
further. If we use CREATE TABLE ... PARTITION OF .. DEFAULT, then the
word DEFAULT appears in the same position where we'd normally have FOR
VALUES, and so the parser will definitely be able to figure out what's
going on. When it gets to that position, it will see FOR or it will
see DEFAULT, and all is clear. OTOH, if we use CREATE TABLE ...
DEFAULT PARTITION OF ..., then we have action at a distance: whether
or not the word DEFAULT is present before PARTITION affects which
tokens are legal after the parent table name. bison isn't always very
smart about that kind of thing. No particular dangers come to mind at
the moment, but it makes me nervous anyway.

+1 to CREATE TABLE .. PARTITION OF .. DEFAULT

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#48Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Robert Haas (#46)
Re: Adding support for Default partition in partitioning

On Tue, Apr 25, 2017 at 1:46 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Mon, Apr 24, 2017 at 8:14 AM, Ashutosh Bapat
<ashutosh.bapat@enterprisedb.com> wrote:

On Mon, Apr 24, 2017 at 4:24 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Mon, Apr 24, 2017 at 5:10 AM, Rahila Syed <rahilasyed90@gmail.com> wrote:

Following can also be considered as it specifies more clearly that the
partition holds default values.

CREATE TABLE ...PARTITION OF...FOR VALUES DEFAULT;

The partition doesn't contain default values; it is itself a default.

Is CREATE TABLE ... DEFAULT PARTITION OF ... feasible? That sounds more natural.

I suspect it could be done as of now, but I'm a little worried that it
might create grammar conflicts in the future as we extend the syntax
further. If we use CREATE TABLE ... PARTITION OF .. DEFAULT, then the
word DEFAULT appears in the same position where we'd normally have FOR
VALUES, and so the parser will definitely be able to figure out what's
going on. When it gets to that position, it will see FOR or it will
see DEFAULT, and all is clear. OTOH, if we use CREATE TABLE ...
DEFAULT PARTITION OF ..., then we have action at a distance: whether
or not the word DEFAULT is present before PARTITION affects which
tokens are legal after the parent table name.

As long as we handle this at the transformation stage, it shouldn't be
a problem. The grammar would be something like
CREATE TABLE ... optDefault PARTITION OF ...

If user specifies DEFAULT PARTITION OF t1 FOR VALUES ..., parser will
allow that but in transformation stage, we will detect it and throw an
error "DEFAULT partitions can not contains partition bound clause" or
something like that. Also, documentation would say that DEFAULT and
partition bound specification are not allowed together.

--
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

#49Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Ashutosh Bapat (#48)
Re: Adding support for Default partition in partitioning

On 2017/04/25 14:20, Ashutosh Bapat wrote:

On Tue, Apr 25, 2017 at 1:46 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Mon, Apr 24, 2017 at 8:14 AM, Ashutosh Bapat
<ashutosh.bapat@enterprisedb.com> wrote:

On Mon, Apr 24, 2017 at 4:24 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Mon, Apr 24, 2017 at 5:10 AM, Rahila Syed <rahilasyed90@gmail.com> wrote:

Following can also be considered as it specifies more clearly that the
partition holds default values.

CREATE TABLE ...PARTITION OF...FOR VALUES DEFAULT;

The partition doesn't contain default values; it is itself a default.

Is CREATE TABLE ... DEFAULT PARTITION OF ... feasible? That sounds more natural.

I suspect it could be done as of now, but I'm a little worried that it
might create grammar conflicts in the future as we extend the syntax
further. If we use CREATE TABLE ... PARTITION OF .. DEFAULT, then the
word DEFAULT appears in the same position where we'd normally have FOR
VALUES, and so the parser will definitely be able to figure out what's
going on. When it gets to that position, it will see FOR or it will
see DEFAULT, and all is clear. OTOH, if we use CREATE TABLE ...
DEFAULT PARTITION OF ..., then we have action at a distance: whether
or not the word DEFAULT is present before PARTITION affects which
tokens are legal after the parent table name.

As long as we handle this at the transformation stage, it shouldn't be
a problem. The grammar would be something like
CREATE TABLE ... optDefault PARTITION OF ...

If user specifies DEFAULT PARTITION OF t1 FOR VALUES ..., parser will
allow that but in transformation stage, we will detect it and throw an
error "DEFAULT partitions can not contains partition bound clause" or
something like that. Also, documentation would say that DEFAULT and
partition bound specification are not allowed together.

FWIW, one point to like about PARTITION OF .. DEFAULT is that it wouldn't
need us to do things you mention we could do. A point to not like it may
be that it might read backwards to some users, but then the DEFAULT
PARTITION OF have all those possibilities of error-causing user input.

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#50Robert Haas
robertmhaas@gmail.com
In reply to: Ashutosh Bapat (#48)
Re: Adding support for Default partition in partitioning

On Tue, Apr 25, 2017 at 1:20 AM, Ashutosh Bapat
<ashutosh.bapat@enterprisedb.com> wrote:

I suspect it could be done as of now, but I'm a little worried that it
might create grammar conflicts in the future as we extend the syntax
further. If we use CREATE TABLE ... PARTITION OF .. DEFAULT, then the
word DEFAULT appears in the same position where we'd normally have FOR
VALUES, and so the parser will definitely be able to figure out what's
going on. When it gets to that position, it will see FOR or it will
see DEFAULT, and all is clear. OTOH, if we use CREATE TABLE ...
DEFAULT PARTITION OF ..., then we have action at a distance: whether
or not the word DEFAULT is present before PARTITION affects which
tokens are legal after the parent table name.

As long as we handle this at the transformation stage, it shouldn't be
a problem. The grammar would be something like
CREATE TABLE ... optDefault PARTITION OF ...

If user specifies DEFAULT PARTITION OF t1 FOR VALUES ..., parser will
allow that but in transformation stage, we will detect it and throw an
error "DEFAULT partitions can not contains partition bound clause" or
something like that. Also, documentation would say that DEFAULT and
partition bound specification are not allowed together.

That's not what I'm concerned about. I'm concerned about future
syntax additions resulting in difficult-to-resolve grammar conflicts.
For an example what of what I mean, consider this example:

/messages/by-id/9253.1295031520@sss.pgh.pa.us

The whole thread is worth a read. In brief, I wanted to add syntax
like LOCK VIEW xyz, and it wasn't possible to do that without breaking
backward compatibility. In a nutshell, the problem with making that
syntax work was that LOCK VIEW NOWAIT would then potentially mean
either lock a table called VIEW with the NOWAIT option, or else it
might mean lock a view called NOWAIT. If the NOWAIT key word were not
allowed at the end or if the TABLE keyword were mandatory, then it
would be possible to make it work, but because we already decided both
to make the TABLE keyword optional and allow an optional NOWAIT
keyword at the end, the syntax couldn't be further extended in the way
that I wanted to extend it without confusing the parser. The problem
was basically unfixable without breaking backward compatibility, and
we gave up. I don't want to make the same mistake with the default
partition syntax that we made with the LOCK TABLE syntax.

Aside from unfixable grammar conflicts, there's another way that this
kind of syntax can become problematic, which is when you end up with
multiple optional keywords in the same part of the syntax. For an
example of that, see
/messages/by-id/603c8f070905231747j2e099c23hef8eafbf26682e5f@mail.gmail.com
- that discusses the problems with EXPLAIN; we later ran into the same
problem with VACUUM. Users can't remember whether they are supposed
to type VACUUM FULL VERBOSE or VACUUM VERBOSE FULL and trying to
support both creates parser problems and tends to involve adding too
many keywords, so we switched to a new and more extensible syntax for
future options.

Now, you may think that that's never going to happen in this case.
What optional keyword other than DEFAULT could we possibly want to add
just before PARTITION OF? TBH, I don't know. I can't think of
anything else we might want to put in that position right now. But
considering that it's been less than six months since the original
syntax was committed and we've already thought of ONE thing we might
want to put there, it seems hard to rule out the possibility that we
might eventually think of more, and then we will have exactly the same
kind of problem that we've had in the past with other commands. Let's
head the problem off at the pass and pick a syntax which isn't
vulnerable to that sort of issue.

--
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

#51Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Robert Haas (#50)
Re: Adding support for Default partition in partitioning

On Tue, Apr 25, 2017 at 11:23 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Tue, Apr 25, 2017 at 1:20 AM, Ashutosh Bapat
<ashutosh.bapat@enterprisedb.com> wrote:

I suspect it could be done as of now, but I'm a little worried that it
might create grammar conflicts in the future as we extend the syntax
further. If we use CREATE TABLE ... PARTITION OF .. DEFAULT, then the
word DEFAULT appears in the same position where we'd normally have FOR
VALUES, and so the parser will definitely be able to figure out what's
going on. When it gets to that position, it will see FOR or it will
see DEFAULT, and all is clear. OTOH, if we use CREATE TABLE ...
DEFAULT PARTITION OF ..., then we have action at a distance: whether
or not the word DEFAULT is present before PARTITION affects which
tokens are legal after the parent table name.

As long as we handle this at the transformation stage, it shouldn't be
a problem. The grammar would be something like
CREATE TABLE ... optDefault PARTITION OF ...

If user specifies DEFAULT PARTITION OF t1 FOR VALUES ..., parser will
allow that but in transformation stage, we will detect it and throw an
error "DEFAULT partitions can not contains partition bound clause" or
something like that. Also, documentation would say that DEFAULT and
partition bound specification are not allowed together.

That's not what I'm concerned about. I'm concerned about future
syntax additions resulting in difficult-to-resolve grammar conflicts.
For an example what of what I mean, consider this example:

/messages/by-id/9253.1295031520@sss.pgh.pa.us

The whole thread is worth a read. In brief, I wanted to add syntax
like LOCK VIEW xyz, and it wasn't possible to do that without breaking
backward compatibility. In a nutshell, the problem with making that
syntax work was that LOCK VIEW NOWAIT would then potentially mean
either lock a table called VIEW with the NOWAIT option, or else it
might mean lock a view called NOWAIT. If the NOWAIT key word were not
allowed at the end or if the TABLE keyword were mandatory, then it
would be possible to make it work, but because we already decided both
to make the TABLE keyword optional and allow an optional NOWAIT
keyword at the end, the syntax couldn't be further extended in the way
that I wanted to extend it without confusing the parser. The problem
was basically unfixable without breaking backward compatibility, and
we gave up. I don't want to make the same mistake with the default
partition syntax that we made with the LOCK TABLE syntax.

Aside from unfixable grammar conflicts, there's another way that this
kind of syntax can become problematic, which is when you end up with
multiple optional keywords in the same part of the syntax. For an
example of that, see
/messages/by-id/603c8f070905231747j2e099c23hef8eafbf26682e5f@mail.gmail.com
- that discusses the problems with EXPLAIN; we later ran into the same
problem with VACUUM. Users can't remember whether they are supposed
to type VACUUM FULL VERBOSE or VACUUM VERBOSE FULL and trying to
support both creates parser problems and tends to involve adding too
many keywords, so we switched to a new and more extensible syntax for
future options.

Thanks for taking out time for detailed explanation.

Now, you may think that that's never going to happen in this case.
What optional keyword other than DEFAULT could we possibly want to add
just before PARTITION OF?

Since the grammar before PARTITION OF is shared with CREATE TABLE ()
there is high chance that we will have an optional keyword unrelated
to partitioning there. I take back my proposal for that syntax.

--
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

#52Rahila Syed
rahilasyed90@gmail.com
In reply to: Jeevan Ladhe (#44)
Re: Adding support for Default partition in partitioning

Hello Jeevan,

Thank you for comments.

I will include your comments in the updated patch.

7.The output of describe needs to be improved.

The syntax for DEFAULT partitioning is still under discussion. This comment
wont be
applicable if the syntax is changed.

6.
I am wondering, isn't it possible to retrieve the has_default and

default_index

here to find out if default partition exists and if exist then find it's

oid

using rd_partdesc, that would avoid above(7) loop to check if partition

bound is

default

The checks are used to find the default partition bound and
exclude it from the list of partition bounds to form the partition
constraint.
This cant be accomplished by using has_default flag.
isDefaultPartitionBound() is written to accomplish that.

8.
Following call to find_inheritance_children() in

generate_qual_for_defaultpart()

is an overhead, instead we can simply use an array of oids in rd_partdesc.

I think using find_inheritance_children() will take into consideration
concurrent
drop of a partition which the value in rd_partdesc will not.

Thank you,
Rahila Syed

#53Rahila Syed
rahilasyed90@gmail.com
In reply to: Robert Haas (#46)
Re: Adding support for Default partition in partitioning

I suspect it could be done as of now, but I'm a little worried that it
might create grammar conflicts in the future as we extend the syntax
further. If we use CREATE TABLE ... PARTITION OF .. DEFAULT, then the
word DEFAULT appears in the same position where we'd normally have FOR
VALUES, and so the parser will definitely be able to figure out what's
going on. When it gets to that position, it will see FOR or it will
see DEFAULT, and all is clear. OTOH, if we use CREATE TABLE ...
DEFAULT PARTITION OF ..., then we have action at a distance: whether
or not the word DEFAULT is present before PARTITION affects which
tokens are legal after the parent table name. bison isn't always very
smart about that kind of thing. No particular dangers come to mind at
the moment, but it makes me nervous anyway.

+1 for CREATE TABLE..PARTITION OF...DEFAULT syntax.
I think substituting DEFAULT for FOR VALUES is appropriate as
both cases are mutually exclusive.

One more thing that needs consideration is should default partitions be
partitioned further? Other databases allow default partitions to be
partitioned further. I think, its normal for users to expect the data in
default partitions to also be divided into sub partitions. So
it should be supported.
My colleague Rajkumar Raghuwanshi brought to my notice the current patch
does not handle this correctly.
I will include this in the updated patch if there is no objection.

On the other hand if sub partitions of a default partition is to be
prohibited,
an error should be thrown if PARTITION BY is specified after DEFAULT.

Thank you,
Rahila Syed

On Tue, Apr 25, 2017 at 1:46 AM, Robert Haas <robertmhaas@gmail.com> wrote:

Show quoted text

On Mon, Apr 24, 2017 at 8:14 AM, Ashutosh Bapat
<ashutosh.bapat@enterprisedb.com> wrote:

On Mon, Apr 24, 2017 at 4:24 PM, Robert Haas <robertmhaas@gmail.com>

wrote:

On Mon, Apr 24, 2017 at 5:10 AM, Rahila Syed <rahilasyed90@gmail.com>

wrote:

Following can also be considered as it specifies more clearly that the
partition holds default values.

CREATE TABLE ...PARTITION OF...FOR VALUES DEFAULT;

The partition doesn't contain default values; it is itself a default.

Is CREATE TABLE ... DEFAULT PARTITION OF ... feasible? That sounds more

natural.

I suspect it could be done as of now, but I'm a little worried that it
might create grammar conflicts in the future as we extend the syntax
further. If we use CREATE TABLE ... PARTITION OF .. DEFAULT, then the
word DEFAULT appears in the same position where we'd normally have FOR
VALUES, and so the parser will definitely be able to figure out what's
going on. When it gets to that position, it will see FOR or it will
see DEFAULT, and all is clear. OTOH, if we use CREATE TABLE ...
DEFAULT PARTITION OF ..., then we have action at a distance: whether
or not the word DEFAULT is present before PARTITION affects which
tokens are legal after the parent table name. bison isn't always very
smart about that kind of thing. No particular dangers come to mind at
the moment, but it makes me nervous anyway.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#54Robert Haas
robertmhaas@gmail.com
In reply to: Rahila Syed (#53)
Re: Adding support for Default partition in partitioning

On Thu, Apr 27, 2017 at 8:49 AM, Rahila Syed <rahilasyed90@gmail.com> wrote:

I suspect it could be done as of now, but I'm a little worried that it
might create grammar conflicts in the future as we extend the syntax
further. If we use CREATE TABLE ... PARTITION OF .. DEFAULT, then the
word DEFAULT appears in the same position where we'd normally have FOR
VALUES, and so the parser will definitely be able to figure out what's
going on. When it gets to that position, it will see FOR or it will
see DEFAULT, and all is clear. OTOH, if we use CREATE TABLE ...
DEFAULT PARTITION OF ..., then we have action at a distance: whether
or not the word DEFAULT is present before PARTITION affects which
tokens are legal after the parent table name. bison isn't always very
smart about that kind of thing. No particular dangers come to mind at
the moment, but it makes me nervous anyway.

+1 for CREATE TABLE..PARTITION OF...DEFAULT syntax.
I think substituting DEFAULT for FOR VALUES is appropriate as
both cases are mutually exclusive.

One more thing that needs consideration is should default partitions be
partitioned further? Other databases allow default partitions to be
partitioned further. I think, its normal for users to expect the data in
default partitions to also be divided into sub partitions. So
it should be supported.
My colleague Rajkumar Raghuwanshi brought to my notice the current patch
does not handle this correctly.
I will include this in the updated patch if there is no objection.

On the other hand if sub partitions of a default partition is to be
prohibited,
an error should be thrown if PARTITION BY is specified after DEFAULT.

I see no reason to prohibit it. You can further partition any other
kind of partition, so there seems to be no reason to disallow it in
this one case.

Are you also working on extending this to work with range
partitioning? Because I think that would be good to do.

--
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

#55Sven R. Kunze
srkunze@mail.de
In reply to: Robert Haas (#54)
Re: Adding support for Default partition in partitioning

On 27.04.2017 15:07, Robert Haas wrote:

On Thu, Apr 27, 2017 at 8:49 AM, Rahila Syed <rahilasyed90@gmail.com> wrote:

+1 for CREATE TABLE..PARTITION OF...DEFAULT syntax.
I think substituting DEFAULT for FOR VALUES is appropriate as
both cases are mutually exclusive.

Just to make sound a little rounder:

CREATE TABLE ... PARTITION OF ... AS DEFAULT
CREATE TABLE ... PARTITION OF ... AS FALLBACK

or

CREATE TABLE ... PARTITION OF ... AS DEFAULT PARTITION
CREATE TABLE ... PARTITION OF ... AS FALLBACK PARTITION

Could any of these be feasible?

Sven

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#56Rahila Syed
rahilasyed90@gmail.com
In reply to: Robert Haas (#54)
Re: Adding support for Default partition in partitioning

Hi,

On Apr 27, 2017 18:37, "Robert Haas" <robertmhaas@gmail.com> wrote:

Are you also working on extending this to work with range
partitioning? Because I think that would be good to do.

Currently I am working on review comments and bug fixes for the

default list partitioning patch. After that I can start with default
partition for range partitioning.

Thank you,
Rahila Syed

#57Robert Haas
robertmhaas@gmail.com
In reply to: Sven R. Kunze (#55)
Re: Adding support for Default partition in partitioning

On Thu, Apr 27, 2017 at 3:15 PM, Sven R. Kunze <srkunze@mail.de> wrote:

On 27.04.2017 15:07, Robert Haas wrote:

On Thu, Apr 27, 2017 at 8:49 AM, Rahila Syed <rahilasyed90@gmail.com>
wrote:

+1 for CREATE TABLE..PARTITION OF...DEFAULT syntax.
I think substituting DEFAULT for FOR VALUES is appropriate as
both cases are mutually exclusive.

Just to make sound a little rounder:

CREATE TABLE ... PARTITION OF ... AS DEFAULT
CREATE TABLE ... PARTITION OF ... AS FALLBACK

or

CREATE TABLE ... PARTITION OF ... AS DEFAULT PARTITION
CREATE TABLE ... PARTITION OF ... AS FALLBACK PARTITION

Could any of these be feasible?

FALLBACK wouldn't be a good choice because it's not an existing parser
keyword. We could probably insert AS before DEFAULT and/or PARTITION
afterwards, but they sort of seem like noise words. SQL seems to have
been invented by people who didn't have any trouble remembering really
long command strings, but brevity is not without some merit.

--
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

#58Sven R. Kunze
srkunze@mail.de
In reply to: Robert Haas (#57)
Re: Adding support for Default partition in partitioning

On 27.04.2017 22:21, Robert Haas wrote:

On Thu, Apr 27, 2017 at 3:15 PM, Sven R. Kunze <srkunze@mail.de> wrote:

Just to make sound a little rounder:

CREATE TABLE ... PARTITION OF ... AS DEFAULT
CREATE TABLE ... PARTITION OF ... AS FALLBACK

or

CREATE TABLE ... PARTITION OF ... AS DEFAULT PARTITION
CREATE TABLE ... PARTITION OF ... AS FALLBACK PARTITION

Could any of these be feasible?

FALLBACK wouldn't be a good choice because it's not an existing parser
keyword. We could probably insert AS before DEFAULT and/or PARTITION
afterwards, but they sort of seem like noise words.

You are right. I just thought it would make this variant more acceptable
as people expressed concerns about understandability of the command.

SQL seems to have
been invented by people who didn't have any trouble remembering really
long command strings, but brevity is not without some merit.

For me, it's exactly the thing I like about SQL. It makes for an easy
learning curve.

Sven

#59Rahila Syed
rahilasyed90@gmail.com
In reply to: Rahila Syed (#41)
1 attachment(s)
Re: Adding support for Default partition in partitioning

Please find attached updated patch with review comments by Robert and
Jeevan implemented.

The newly proposed syntax
CREATE TABLE .. PARTITION OF .. DEFAULT has got most votes on this thread.

If there is no more objection, I will go ahead and include that in the
patch.

Thank you,
Rahila Syed

On Mon, Apr 24, 2017 at 2:40 PM, Rahila Syed <rahilasyed90@gmail.com> wrote:

Show quoted text

Hello,

Thank you for reviewing.

But that's not a good idea for several reasons. For one thing, you
can also write FOR VALUES IN (DEFAULT, 5) or which isn't sensible.
For another thing, this kind of syntax won't generalize to range
partitioning, which we've talked about making this feature support.
Maybe something like:

CREATE TABLE .. PARTITION OF .. DEFAULT;

I agree that the syntax should be changed to also support range
partitioning.

Following can also be considered as it specifies more clearly that the
partition holds default values.

CREATE TABLE ...PARTITION OF...FOR VALUES DEFAULT;

Maybe we should introduce a dedicated node type to
represent a default-specification in the parser grammar. If not, then
let's at least encapsulate the test a little better, e.g. by adding
isDefaultPartitionBound() which tests not only IsA(..., DefElem) but
also whether the name is DEFAULT as expected. BTW, we typically use
lower-case internally, so if we stick with this representation it
should really be "default" not "DEFAULT".

isDefaultPartitionBound() function is created in the attached patch which
checks for both node type and name.

Why abbreviate "default" to def here? Seems pointless.

Corrected in the attached.

Consider &&

Fixed.

+ * default partiton for rows satisfying the new partition
Spelling.

Fixed.

Missing apostrophe

Fixed.

Definitely not safe against concurrency, since AccessShareLock won't
exclude somebody else's update. In fact, it won't even cover somebody
else's already-in-flight transaction

Changed it to AccessExclusiveLock

Normally in such cases we try to give more detail using
ExecBuildSlotValueDescription.

This function is used in execMain.c and the error is being
reported in partition.c.
Do you mean the error reporting should be moved into execMain.c
to use ExecBuildSlotValueDescription?

This variable starts out true and is never set to any value other than
true. Just get rid of it and, in the one place where it is currently
used, write "true". That's shorter and clearer.

Fixed.

There's not really a reason to cast the result of stringToNode() to
Node * and then turn around and cast it to PartitionBoundSpec *. Just
cast it directly to whatever it needs to be. And use the new castNode
macro

Fixed. castNode macro takes as input Node * whereas stringToNode() takes
string.
IIUC, castNode cant be used here.

The if (def_elem) test continues
early, but if the point is that the loop using cell3 shouldn't execute
in that case, why not just put if (!def_elem) { foreach(cell3, ...) {
... } } instead of reiterating the ReleaseSysCache in two places?

Fixed in the attached.

I will respond to further comments in following email.

On Thu, Apr 13, 2017 at 12:48 AM, Robert Haas <robertmhaas@gmail.com>
wrote:

On Thu, Apr 6, 2017 at 7:30 AM, Rahila Syed <rahilasyed90@gmail.com>
wrote:

Thanks a lot for testing and reporting this. Please find attached an

updated

patch with the fix. The patch also contains a fix
regarding operator used at the time of creating expression as default
partition constraint. This was notified offlist by Amit Langote.

I think that the syntax for this patch should probably be revised.
Right now the proposal is for:

CREATE TABLE .. PARTITION OF ... FOR VALUES IN (DEFAULT);

But that's not a good idea for several reasons. For one thing, you
can also write FOR VALUES IN (DEFAULT, 5) or which isn't sensible.
For another thing, this kind of syntax won't generalize to range
partitioning, which we've talked about making this feature support.
Maybe something like:

CREATE TABLE .. PARTITION OF .. DEFAULT;

This patch makes the assumption throughout that any DefElem represents
the word DEFAULT, which is true in the patch as written but doesn't
seem very future-proof. I think the "def" in "DefElem" stands for
"definition" or "define" or something like that, so this is actually
pretty confusing. Maybe we should introduce a dedicated node type to
represent a default-specification in the parser grammar. If not, then
let's at least encapsulate the test a little better, e.g. by adding
isDefaultPartitionBound() which tests not only IsA(..., DefElem) but
also whether the name is DEFAULT as expected. BTW, we typically use
lower-case internally, so if we stick with this representation it
should really be "default" not "DEFAULT".

Useless hunk:

+    bool        has_def;        /* Is there a default partition?
Currently false
+                                 * for a range partitioned table */
+    int            def_index;        /* Index of the default list
partition. -1 for
+                                 * range partitioned tables */

Why abbreviate "default" to def here? Seems pointless.

+                    if (found_def)
+                    {
+                        if (mapping[def_index] == -1)
+                            mapping[def_index] = next_index++;
+                    }

Consider &&

@@ -717,7 +754,6 @@ check_new_partition_bound(char *relname, Relation
parent, Node *bound)
}
}
}
-
break;
}

+ * default partiton for rows satisfying the new partition

Spelling.

+ * constraint. If found dont allow addition of a new partition.

Missing apostrophe.

+        defrel = heap_open(defid, AccessShareLock);
+        tupdesc = CreateTupleDescCopy(RelationGetDescr(defrel));
+
+        /* Build expression execution states for partition check quals */
+        partqualstate = ExecPrepareCheck(partConstraint,
+                        estate);
+
+        econtext = GetPerTupleExprContext(estate);
+        snapshot = RegisterSnapshot(GetLatestSnapshot());

Definitely not safe against concurrency, since AccessShareLock won't
exclude somebody else's update. In fact, it won't even cover somebody
else's already-in-flight transaction.

+ errmsg("new default partition constraint is violated
by some row")));

Normally in such cases we try to give more detail using
ExecBuildSlotValueDescription.

+ bool is_def = true;

This variable starts out true and is never set to any value other than
true. Just get rid of it and, in the one place where it is currently
used, write "true". That's shorter and clearer.

+ inhoids = find_inheritance_children(RelationGetRelid(parent),
NoLock);

If it's actually safe to do this with no lock, there ought to be a
comment with a very compelling explanation of why it's safe.

+        boundspec = (Node *) stringToNode(TextDatumGetCString(datum));
+        bspec = (PartitionBoundSpec *)boundspec;

There's not really a reason to cast the result of stringToNode() to
Node * and then turn around and cast it to PartitionBoundSpec *. Just
cast it directly to whatever it needs to be. And use the new castNode
macro.

+        foreach(cell1, bspec->listdatums)
+        {
+            Node *value = lfirst(cell1);
+            if (IsA(value, DefElem))
+            {
+                def_elem = true;
+                *defid = inhrelid;
+            }
+        }
+        if (def_elem)
+        {
+            ReleaseSysCache(tuple);
+            continue;
+        }
+        foreach(cell3, bspec->listdatums)
+        {
+            Node *value = lfirst(cell3);
+            boundspecs = lappend(boundspecs, value);
+        }
+        ReleaseSysCache(tuple);
+    }
+    foreach(cell4, spec->listdatums)
+    {
+        Node *value = lfirst(cell4);
+        boundspecs = lappend(boundspecs, value);
+    }

cell1, cell2, cell3, and cell4 are not very clear variable names.
Between that and the lack of comments, this is not easy to understand.
It's sort of spaghetti logic, too. The if (def_elem) test continues
early, but if the point is that the loop using cell3 shouldn't execute
in that case, why not just put if (!def_elem) { foreach(cell3, ...) {
... } } instead of reiterating the ReleaseSysCache in two places?

+                /* Collect bound spec nodes in a list. This is done
if the partition is
+                 * a default partition. In case of default partition,
constraint is formed
+                 * by performing <> operation over the partition
constraints of the
+                 * existing partitions.
+                 */

I doubt that handles NULLs properly.

+ inhoids =
find_inheritance_children(RelationGetRelid(parent), NoLock);

Again, no lock? Really?

The logic which follows looks largely cut-and-pasted, which makes me
think you need to do some refactoring here to make it more clear
what's going on, so that you have the relevant logic in just one
place. It seems wrong anyway to shove all of this logic specific to
the default case into get_qual_from_partbound() when the logic for the
non-default case is inside get_qual_for_list. Where there were 2
lines of code before you've now got something like 30.

+        if(get_negator(operoid) == InvalidOid)
+            elog(ERROR, "no negator found for partition operator %u",
+                 operoid);

I really doubt that's OK. elog() shouldn't be reachable, but this
will be reachable if the partitioning operator does not have a
negator. And there's the NULL-handling issue I mentioned above, too.

+            if (partdesc->boundinfo->has_def && key->strategy
+                == PARTITION_STRATEGY_LIST)
+                result = parent->indexes[partdesc->boun
dinfo->def_index];

Testing for PARTITION_STRATEGY_LIST here seems unnecessary. If
has_def (or has_default, as it probably should be) isn't allowed for
range partitions, then it's redundant; if it is allowed, then that
case should be handled too. Also, at this point we've already set
*failed_at and *failed_slot; presumably you'd want to make this check
before you get to that point.

I suspect there are quite a few more problems here in addition to the
ones mentioned above, but I don't think it makes sense to spend too
much time searching for them until some of this basic stuff is cleaned
up.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

Attachments:

default_partition_v8.patchapplication/x-download; name=default_partition_v8.patchDownload
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index e0d2665..38312f2 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -34,6 +34,7 @@
 #include "nodes/nodeFuncs.h"
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
+#include "optimizer/prep.h"
 #include "optimizer/planmain.h"
 #include "optimizer/var.h"
 #include "rewrite/rewriteManip.h"
@@ -90,6 +91,10 @@ typedef struct PartitionBoundInfoData
 								 * for range partitioned tables */
 	int			null_index;		/* Index of the null-accepting partition; -1
 								 * for range partitioned tables */
+	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 */
 } PartitionBoundInfoData;
 
 /*
@@ -118,7 +123,8 @@ static int32 qsort_partition_list_value_cmp(const void *a, const void *b,
 static int32 qsort_partition_rbound_cmp(const void *a, const void *b,
 						   void *arg);
 
-static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec,
+								bool is_def, List *boundspecs);
 static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
 static Oid get_partition_operator(PartitionKey key, int col,
 					   StrategyNumber strategy, bool *need_relabel);
@@ -166,6 +172,8 @@ RelationBuildPartitionDesc(Relation rel)
 	/* List partitioning specific */
 	PartitionListValue **all_values = NULL;
 	bool		found_null = false;
+	bool		found_def = false;
+	int			def_index = -1;
 	int			null_index = -1;
 
 	/* Range partitioning specific */
@@ -249,9 +257,16 @@ RelationBuildPartitionDesc(Relation rel)
 
 				foreach(c, spec->listdatums)
 				{
+					Node *value = lfirst(c);
 					Const	   *val = lfirst(c);
 					PartitionListValue *list_value = NULL;
 
+					if (isDefaultPartitionBound(value))
+					{
+						found_def = true;
+						def_index = i;
+						continue;
+					}
 					if (!val->constisnull)
 					{
 						list_value = (PartitionListValue *)
@@ -459,6 +474,7 @@ RelationBuildPartitionDesc(Relation rel)
 			case PARTITION_STRATEGY_LIST:
 				{
 					boundinfo->has_null = found_null;
+					boundinfo->has_default = found_def;
 					boundinfo->indexes = (int *) palloc(ndatums * sizeof(int));
 
 					/*
@@ -496,6 +512,8 @@ RelationBuildPartitionDesc(Relation rel)
 						if (mapping[null_index] == -1)
 							mapping[null_index] = next_index++;
 					}
+					if (found_def && mapping[def_index] == -1)
+						mapping[def_index] = next_index++;
 
 					/* All partition must now have a valid mapping */
 					Assert(next_index == nparts);
@@ -504,6 +522,11 @@ RelationBuildPartitionDesc(Relation rel)
 						boundinfo->null_index = mapping[null_index];
 					else
 						boundinfo->null_index = -1;
+
+					if (found_def)
+						boundinfo->default_index = mapping[def_index];
+					else
+						boundinfo->default_index = -1;
 					break;
 				}
 
@@ -513,6 +536,7 @@ RelationBuildPartitionDesc(Relation rel)
 												sizeof(RangeDatumContent *));
 					boundinfo->indexes = (int *) palloc((ndatums + 1) *
 														sizeof(int));
+					boundinfo->has_default = found_def;
 
 					for (i = 0; i < ndatums; i++)
 					{
@@ -671,6 +695,7 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
 	PartitionKey key = RelationGetPartitionKey(parent);
 	PartitionDesc partdesc = RelationGetPartitionDesc(parent);
+	PartitionBoundInfo boundinfo;
 	ParseState *pstate = make_parsestate(NULL);
 	int			with = -1;
 	bool		overlap = false;
@@ -679,6 +704,8 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 	{
 		case PARTITION_STRATEGY_LIST:
 			{
+				boundinfo = partdesc->boundinfo;
+
 				Assert(spec->strategy == PARTITION_STRATEGY_LIST);
 
 				if (partdesc->nparts > 0)
@@ -688,13 +715,15 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 
 					Assert(boundinfo &&
 						   boundinfo->strategy == PARTITION_STRATEGY_LIST &&
-						   (boundinfo->ndatums > 0 || boundinfo->has_null));
+						   (boundinfo->ndatums > 0 || boundinfo->has_null
+							|| boundinfo->has_default));
 
 					foreach(cell, spec->listdatums)
 					{
-						Const	   *val = lfirst(cell);
+						Node	   *value = lfirst(cell);
+						Const	   *val = (Const *) value;
 
-						if (!val->constisnull)
+						if (!val->constisnull && !(isDefaultPartitionBound(value)))
 						{
 							int			offset;
 							bool		equal;
@@ -709,7 +738,14 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 								break;
 							}
 						}
-						else if (boundinfo->has_null)
+						else if (isDefaultPartitionBound(value) &&
+								 boundinfo->has_default)
+						{
+							overlap = true;
+							with = boundinfo->default_index;
+							break;
+						}
+						else if (val->constisnull && boundinfo->has_null)
 						{
 							overlap = true;
 							with = boundinfo->null_index;
@@ -836,6 +872,77 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 						relname, get_rel_name(partdesc->oids[with])),
 				 parser_errposition(pstate, spec->location)));
 	}
+
+	/*
+	 * 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.
+	 */
+	if (spec->strategy == PARTITION_STRATEGY_LIST && partdesc->nparts > 0
+		&& boundinfo->has_default)
+	{
+		List	       *partConstraint = NIL;
+		ExprContext    *econtext;
+		EState	       *estate;
+		Relation	    defrel;
+		HeapScanDesc    scan;
+		HeapTuple	    tuple;
+		ExprState	   *partqualstate = NULL;
+		Snapshot	    snapshot;
+		Oid			    defid;
+		MemoryContext   oldCxt;
+		TupleTableSlot *tupslot;
+		TupleDesc	    tupdesc;
+
+
+		partConstraint = generate_qual_for_defaultpart(parent, bound, &defid);
+		partConstraint = (List *) eval_const_expressions(NULL,
+													(Node *) partConstraint);
+		partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
+		partConstraint = list_make1(make_ands_explicit(partConstraint));
+
+		/*
+		 * Generate the constraint and default execution states
+		 */
+		estate = CreateExecutorState();
+
+		defrel = heap_open(defid, AccessExclusiveLock);
+		tupdesc = CreateTupleDescCopy(RelationGetDescr(defrel));
+
+		/* Build expression execution states for partition check quals */
+		partqualstate = ExecPrepareCheck(partConstraint,
+						estate);
+
+		econtext = GetPerTupleExprContext(estate);
+		snapshot = RegisterSnapshot(GetLatestSnapshot());
+		scan = heap_beginscan(defrel, snapshot, 0, NULL);
+		tupslot = MakeSingleTupleTableSlot(tupdesc);
+
+		/*
+		 * Switch to per-tuple memory context and reset it for each tuple
+		 * produced, so we don't leak memory.
+		 */
+		oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		{
+			ExecStoreTuple(tuple, tupslot, InvalidBuffer, false);
+			econtext->ecxt_scantuple = tupslot;
+			if (partqualstate && !ExecCheck(partqualstate, econtext))
+				ereport(ERROR,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+				errmsg("new default partition constraint is violated by some row")));
+			ResetExprContext(econtext);
+			CHECK_FOR_INTERRUPTS();
+		}
+		MemoryContextSwitchTo(oldCxt);
+		heap_endscan(scan);
+		UnregisterSnapshot(snapshot);
+		ExecDropSingleTupleTableSlot(tupslot);
+		FreeExecutorState(estate);
+		heap_close(defrel, AccessExclusiveLock);
+	}
 }
 
 /*
@@ -884,6 +991,116 @@ get_partition_parent(Oid relid)
 }
 
 /*
+ * Returns true if the partition bound is default
+ */
+bool
+isDefaultPartitionBound(Node *value)
+{
+	if (IsA(value, DefElem))
+	{
+		DefElem *defvalue = (DefElem *) value;
+		if(!strcmp(defvalue->defname, "DEFAULT"))
+			return true;
+		return false;
+	}
+	return false;
+}
+
+/*
+ * Return the bound spec list to be used
+ * in partition constraint of default partition
+ */
+List *
+get_qual_for_default(Relation parent, Oid *defid)
+{
+	List	   *inhoids;
+	ListCell   *cell;
+	List	   *boundspecs = 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 *cell1;
+
+		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));
+		foreach(cell1, bspec->listdatums)
+		{
+			Node *value = lfirst(cell1);
+			if (isDefaultPartitionBound(value))
+			{
+				def_elem = true;
+				*defid  = inhrelid;
+			}
+		}
+		if (!def_elem)
+		{
+			foreach(cell1, bspec->listdatums)
+			{
+				Node *value = lfirst(cell1);
+				boundspecs = lappend(boundspecs, value);
+			}
+		}
+		ReleaseSysCache(tuple);
+	}
+	return boundspecs;
+}
+
+/*
+ * Return a list of executable expressions as new partition constraint
+ * for default partition while adding a new partition after default
+ */
+List *
+generate_qual_for_defaultpart(Relation parent, Node *bound, Oid * defid)
+{
+	PartitionKey key = RelationGetPartitionKey(parent);
+	PartitionBoundSpec *spec;
+	List	   *partConstraint = NIL;
+	ListCell   *cell;
+	List       *boundspecs = NIL;
+	List       *bound_datums;
+
+	spec = (PartitionBoundSpec *) bound;
+	bound_datums = list_copy(spec->listdatums);
+
+	boundspecs = get_qual_for_default(parent, defid);
+
+	foreach(cell, bound_datums)
+	{
+		Node *value = lfirst(cell);
+		boundspecs = lappend(boundspecs, value);
+	}
+	partConstraint = get_qual_for_list(key, spec, true, boundspecs);
+	return partConstraint;
+}
+
+/*
  * get_qual_from_partbound
  *		Given a parser node for partition bound, return the list of executable
  *		expressions as partition constraint
@@ -894,6 +1111,10 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
 	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
 	PartitionKey key = RelationGetPartitionKey(parent);
 	List	   *my_qual = NIL;
+	bool		is_def = false;
+	Oid        defid;
+	ListCell   *cell;
+	List	   *boundspecs = NIL;
 
 	Assert(key != NULL);
 
@@ -901,9 +1122,16 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
 	{
 		case PARTITION_STRATEGY_LIST:
 			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
-			my_qual = get_qual_for_list(key, spec);
+			foreach(cell, spec->listdatums)
+			{
+				Node *value = lfirst(cell);
+				if (isDefaultPartitionBound(value))
+					is_def = true;
+			}
+			if (is_def)
+				boundspecs = get_qual_for_default(parent, &defid);
+			my_qual = get_qual_for_list(key, spec, is_def, boundspecs);
 			break;
-
 		case PARTITION_STRATEGY_RANGE:
 			Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
 			my_qual = get_qual_for_range(key, spec);
@@ -1151,7 +1379,8 @@ RelationGetPartitionDispatchInfo(Relation rel, int lockmode,
  * Returns a list of expressions to use as a list partition's constraint.
  */
 static List *
-get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
+get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec, bool is_def,
+					List *boundspecs)
 {
 	List	   *result;
 	ArrayExpr  *arr;
@@ -1177,12 +1406,14 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 	else
 		keyCol = (Expr *) copyObject(linitial(key->partexprs));
 
+	if (!is_def)
+		boundspecs = spec->listdatums;
 	/*
 	 * We must remove any NULL value in the list; we handle it separately
 	 * below.
 	 */
 	prev = NULL;
-	for (cell = list_head(spec->listdatums); cell; cell = next)
+	for (cell = list_head(boundspecs); cell; cell = next)
 	{
 		Const	   *val = (Const *) lfirst(cell);
 
@@ -1191,14 +1422,14 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 		if (val->constisnull)
 		{
 			list_has_null = true;
-			spec->listdatums = list_delete_cell(spec->listdatums,
+			boundspecs = list_delete_cell(boundspecs,
 												cell, prev);
 		}
 		else
 			prev = cell;
 	}
 
-	if (!list_has_null)
+	if ((is_def && list_has_null) || (!is_def && !list_has_null))
 	{
 		/*
 		 * Gin up a col IS NOT NULL test that will be AND'd with other
@@ -1210,7 +1441,7 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 		nulltest1->argisrow = false;
 		nulltest1->location = -1;
 	}
-	else
+	else if(!is_def && list_has_null)
 	{
 		/*
 		 * Gin up a col IS NULL test that will be OR'd with other expressions
@@ -1229,7 +1460,7 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 		: key->parttypid[0];
 	arr->array_collid = key->parttypcoll[0];
 	arr->element_typeid = key->parttypid[0];
-	arr->elements = spec->listdatums;
+	arr->elements = boundspecs;
 	arr->multidims = false;
 	arr->location = -1;
 
@@ -1243,15 +1474,34 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 										  key->partcollation[0],
 										  COERCE_EXPLICIT_CAST);
 
+	/* Build leftop <> ALL(rightop). This is for default partition */
+	if (is_def)
+	{
+		/* Find the negator for the partition operator above */
+		if(get_negator(operoid) == InvalidOid)
+			ereport(ERROR, (errcode(ERRCODE_CHECK_VIOLATION),
+			errmsg("DEFAULT partition cannot be used without negator of operator  %s",
+						get_opname(operoid))));
+		operoid = get_negator(operoid);
+		opexpr = makeNode(ScalarArrayOpExpr);
+		opexpr->opno = operoid;
+		opexpr->opfuncid = get_opcode(operoid);
+		opexpr->useOr = false;
+		opexpr->inputcollid = key->partcollation[0];
+		opexpr->args = list_make2(keyCol, arr);
+		opexpr->location = -1;
+	}
 	/* Build leftop = ANY (rightop) */
-	opexpr = makeNode(ScalarArrayOpExpr);
-	opexpr->opno = operoid;
-	opexpr->opfuncid = get_opcode(operoid);
-	opexpr->useOr = true;
-	opexpr->inputcollid = key->partcollation[0];
-	opexpr->args = list_make2(keyCol, arr);
-	opexpr->location = -1;
-
+	else
+	{
+		opexpr = makeNode(ScalarArrayOpExpr);
+		opexpr->opno = operoid;
+		opexpr->opfuncid = get_opcode(operoid);
+		opexpr->useOr = true;
+		opexpr->inputcollid = key->partcollation[0];
+		opexpr->args = list_make2(keyCol, arr);
+		opexpr->location = -1;
+	}
 	if (nulltest1)
 		result = list_make2(nulltest1, opexpr);
 	else if (nulltest2)
@@ -1775,9 +2025,18 @@ get_partition_for_tuple(PartitionDispatch *pd,
 		 */
 		if (cur_index < 0)
 		{
-			result = -1;
-			*failed_at = parent;
-			*failed_slot = slot;
+			/*
+			 * If partitioned table has a default partition, return
+			 * its sequence number
+			 */
+			if (partdesc->boundinfo->has_default)
+				result = parent->indexes[partdesc->boundinfo->default_index];
+			else
+			{
+				result = -1;
+				*failed_at = parent;
+				*failed_slot = slot;
+			}
 			break;
 		}
 		else if (parent->indexes[cur_index] >= 0)
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 89d2836..e0b27cf 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2677,6 +2677,7 @@ partbound_datum:
 			Sconst			{ $$ = makeStringConst($1, @1); }
 			| NumericOnly	{ $$ = makeAConst($1, @1); }
 			| NULL_P		{ $$ = makeNullAConst(@1); }
+			| DEFAULT  { $$ = (Node *)makeDefElem("DEFAULT", NULL, @1); }
 		;
 
 partbound_datum_list:
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e546194..5e11873 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -3304,47 +3305,50 @@ transformPartitionBound(ParseState *pstate, Relation parent, Node *bound)
 		result_spec->listdatums = NIL;
 		foreach(cell, spec->listdatums)
 		{
-			A_Const    *con = (A_Const *) lfirst(cell);
-			Node	   *value;
-			ListCell   *cell2;
-			bool		duplicate;
-
-			value = (Node *) make_const(pstate, &con->val, con->location);
-			value = coerce_to_target_type(pstate,
-										  value, exprType(value),
-										  get_partition_col_typid(key, 0),
-										  get_partition_col_typmod(key, 0),
-										  COERCION_ASSIGNMENT,
-										  COERCE_IMPLICIT_CAST,
-										  -1);
-
-			if (value == NULL)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
-							 format_type_be(get_partition_col_typid(key, 0)),
-								colname),
-						 parser_errposition(pstate,
-											exprLocation((Node *) con))));
-
-			/* Simplify the expression */
-			value = (Node *) expression_planner((Expr *) value);
-
-			/* Don't add to the result if the value is a duplicate */
-			duplicate = false;
-			foreach(cell2, result_spec->listdatums)
+			Node *value = lfirst(cell);
+			/* Perform the transformation only for non default partition */
+			if (!(isDefaultPartitionBound(value)))
 			{
-				Const	   *value2 = (Const *) lfirst(cell2);
+				A_Const    *con = (A_Const *) lfirst(cell);
+				ListCell   *cell2;
+				bool		duplicate;
+
+				value = (Node *) make_const(pstate, &con->val, con->location);
+				value = coerce_to_target_type(pstate,
+											value, exprType(value),
+											get_partition_col_typid(key, 0),
+											get_partition_col_typmod(key, 0),
+											COERCION_ASSIGNMENT,
+											COERCE_IMPLICIT_CAST,
+											-1);
+
+				if (value == NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+								format_type_be(get_partition_col_typid(key, 0)),
+									colname),
+							parser_errposition(pstate,
+												exprLocation((Node *) con))));
 
-				if (equal(value, value2))
+				/* Simplify the expression */
+				value = (Node *) expression_planner((Expr *) value);
+
+				/* Don't add to the result if the value is a duplicate */
+				duplicate = false;
+				foreach(cell2, result_spec->listdatums)
 				{
-					duplicate = true;
-					break;
+					Const	   *value2 = (Const *) lfirst(cell2);
+
+					if (equal(value, value2))
+					{
+						duplicate = true;
+						break;
+					}
 				}
+				if (duplicate)
+					continue;
 			}
-			if (duplicate)
-				continue;
-
 			result_spec->listdatums = lappend(result_spec->listdatums,
 											  value);
 		}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 184e5da..65d8821 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -24,6 +24,7 @@
 #include "access/sysattr.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
+#include "catalog/partition.h"
 #include "catalog/pg_aggregate.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_authid.h"
@@ -8619,8 +8620,14 @@ get_rule_expr(Node *node, deparse_context *context,
 						sep = "";
 						foreach(cell, spec->listdatums)
 						{
+							Node *value = lfirst(cell);
 							Const	   *val = lfirst(cell);
 
+							if (isDefaultPartitionBound(value))
+							{
+								appendStringInfoString(buf, "DEFAULT");
+								continue;
+							}
 							appendStringInfoString(buf, sep);
 							get_const_expr(val, context, -1);
 							sep = ", ";
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 421644c..7cb4f0a 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -74,9 +74,12 @@ extern void RelationBuildPartitionDesc(Relation relation);
 extern bool partition_bounds_equal(PartitionKey key,
 					   PartitionBoundInfo p1, PartitionBoundInfo p2);
 
+extern bool isDefaultPartitionBound(Node *value);
 extern void check_new_partition_bound(char *relname, Relation parent, Node *bound);
 extern Oid	get_partition_parent(Oid relid);
 extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
+extern List *generate_qual_for_defaultpart(Relation parent, Node *bound, Oid *defid);
+extern List *get_qual_for_default(Relation parent, Oid *defid);
 extern List *map_partition_varattnos(List *expr, int target_varno,
 						Relation partrel, Relation parent);
 extern List *RelationGetPartitionQual(Relation rel);
#60amul sul
sulamul@gmail.com
In reply to: Rahila Syed (#59)
Re: Adding support for Default partition in partitioning

On Tue, May 2, 2017 at 9:33 PM, Rahila Syed <rahilasyed90@gmail.com> wrote:

Please find attached updated patch with review comments by Robert and Jeevan
implemented.

Patch v8 got clean apply on latest head but server got crash at data
insert in the following test:

-- Create test table
CREATE TABLE test ( a int, b date) PARTITION BY LIST (a);
CREATE TABLE p1 PARTITION OF test FOR VALUES IN (DEFAULT) PARTITION BY LIST(b);
CREATE TABLE p11 PARTITION OF p1 FOR VALUES IN (DEFAULT);

-- crash
INSERT INTO test VALUES (210,'1/1/2002');

Regards,
Amul

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#61Rahila Syed
rahilasyed90@gmail.com
In reply to: amul sul (#60)
1 attachment(s)
Re: Adding support for Default partition in partitioning

Hello Amul,

Thanks for reporting. Please find attached an updated patch which fixes the
above.
Also, the attached patch includes changes in syntax proposed upthread.

The syntax implemented in this patch is as follows,

CREATE TABLE p11 PARTITION OF p1 DEFAULT;

Thank you,
Rahila Syed

On Thu, May 4, 2017 at 4:02 PM, amul sul <sulamul@gmail.com> wrote:

Show quoted text

On Tue, May 2, 2017 at 9:33 PM, Rahila Syed <rahilasyed90@gmail.com>
wrote:

Please find attached updated patch with review comments by Robert and

Jeevan

implemented.

Patch v8 got clean apply on latest head but server got crash at data
insert in the following test:

-- Create test table
CREATE TABLE test ( a int, b date) PARTITION BY LIST (a);
CREATE TABLE p1 PARTITION OF test FOR VALUES IN (DEFAULT) PARTITION BY
LIST(b);
CREATE TABLE p11 PARTITION OF p1 FOR VALUES IN (DEFAULT);

-- crash
INSERT INTO test VALUES (210,'1/1/2002');

Regards,
Amul

Attachments:

default_partition_v9.patchapplication/x-download; name=default_partition_v9.patchDownload
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index e0d2665..193c7fc 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -34,6 +34,7 @@
 #include "nodes/nodeFuncs.h"
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
+#include "optimizer/prep.h"
 #include "optimizer/planmain.h"
 #include "optimizer/var.h"
 #include "rewrite/rewriteManip.h"
@@ -90,6 +91,10 @@ typedef struct PartitionBoundInfoData
 								 * for range partitioned tables */
 	int			null_index;		/* Index of the null-accepting partition; -1
 								 * for range partitioned tables */
+	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 */
 } PartitionBoundInfoData;
 
 /*
@@ -118,7 +123,8 @@ static int32 qsort_partition_list_value_cmp(const void *a, const void *b,
 static int32 qsort_partition_rbound_cmp(const void *a, const void *b,
 						   void *arg);
 
-static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec,
+								bool is_def, List *boundspecs);
 static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
 static Oid get_partition_operator(PartitionKey key, int col,
 					   StrategyNumber strategy, bool *need_relabel);
@@ -166,6 +172,8 @@ RelationBuildPartitionDesc(Relation rel)
 	/* List partitioning specific */
 	PartitionListValue **all_values = NULL;
 	bool		found_null = false;
+	bool		found_def = false;
+	int			def_index = -1;
 	int			null_index = -1;
 
 	/* Range partitioning specific */
@@ -249,9 +257,16 @@ RelationBuildPartitionDesc(Relation rel)
 
 				foreach(c, spec->listdatums)
 				{
+					Node *value = lfirst(c);
 					Const	   *val = lfirst(c);
 					PartitionListValue *list_value = NULL;
 
+					if (isDefaultPartitionBound(value))
+					{
+						found_def = true;
+						def_index = i;
+						continue;
+					}
 					if (!val->constisnull)
 					{
 						list_value = (PartitionListValue *)
@@ -459,6 +474,7 @@ RelationBuildPartitionDesc(Relation rel)
 			case PARTITION_STRATEGY_LIST:
 				{
 					boundinfo->has_null = found_null;
+					boundinfo->has_default = found_def;
 					boundinfo->indexes = (int *) palloc(ndatums * sizeof(int));
 
 					/*
@@ -496,6 +512,8 @@ RelationBuildPartitionDesc(Relation rel)
 						if (mapping[null_index] == -1)
 							mapping[null_index] = next_index++;
 					}
+					if (found_def && mapping[def_index] == -1)
+						mapping[def_index] = next_index++;
 
 					/* All partition must now have a valid mapping */
 					Assert(next_index == nparts);
@@ -504,6 +522,11 @@ RelationBuildPartitionDesc(Relation rel)
 						boundinfo->null_index = mapping[null_index];
 					else
 						boundinfo->null_index = -1;
+
+					if (found_def)
+						boundinfo->default_index = mapping[def_index];
+					else
+						boundinfo->default_index = -1;
 					break;
 				}
 
@@ -513,6 +536,7 @@ RelationBuildPartitionDesc(Relation rel)
 												sizeof(RangeDatumContent *));
 					boundinfo->indexes = (int *) palloc((ndatums + 1) *
 														sizeof(int));
+					boundinfo->has_default = found_def;
 
 					for (i = 0; i < ndatums; i++)
 					{
@@ -671,6 +695,7 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
 	PartitionKey key = RelationGetPartitionKey(parent);
 	PartitionDesc partdesc = RelationGetPartitionDesc(parent);
+	PartitionBoundInfo boundinfo;
 	ParseState *pstate = make_parsestate(NULL);
 	int			with = -1;
 	bool		overlap = false;
@@ -679,6 +704,8 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 	{
 		case PARTITION_STRATEGY_LIST:
 			{
+				boundinfo = partdesc->boundinfo;
+
 				Assert(spec->strategy == PARTITION_STRATEGY_LIST);
 
 				if (partdesc->nparts > 0)
@@ -688,13 +715,15 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 
 					Assert(boundinfo &&
 						   boundinfo->strategy == PARTITION_STRATEGY_LIST &&
-						   (boundinfo->ndatums > 0 || boundinfo->has_null));
+						   (boundinfo->ndatums > 0 || boundinfo->has_null
+							|| boundinfo->has_default));
 
 					foreach(cell, spec->listdatums)
 					{
-						Const	   *val = lfirst(cell);
+						Node	   *value = lfirst(cell);
+						Const	   *val = (Const *) value;
 
-						if (!val->constisnull)
+						if (!val->constisnull && !(isDefaultPartitionBound(value)))
 						{
 							int			offset;
 							bool		equal;
@@ -709,7 +738,14 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 								break;
 							}
 						}
-						else if (boundinfo->has_null)
+						else if (isDefaultPartitionBound(value) &&
+								 boundinfo->has_default)
+						{
+							overlap = true;
+							with = boundinfo->default_index;
+							break;
+						}
+						else if (val->constisnull && boundinfo->has_null)
 						{
 							overlap = true;
 							with = boundinfo->null_index;
@@ -836,6 +872,78 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 						relname, get_rel_name(partdesc->oids[with])),
 				 parser_errposition(pstate, spec->location)));
 	}
+
+	/*
+	 * 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.
+	 */
+	if (spec->strategy == PARTITION_STRATEGY_LIST && partdesc->nparts > 0
+		&& boundinfo->has_default)
+	{
+		List	       *partConstraint = NIL;
+		ExprContext    *econtext;
+		EState	       *estate;
+		Relation	    defrel;
+		HeapScanDesc    scan;
+		HeapTuple	    tuple;
+		ExprState	   *partqualstate = NULL;
+		Snapshot	    snapshot;
+		Oid			    defid;
+		MemoryContext   oldCxt;
+		TupleTableSlot *tupslot;
+		TupleDesc	    tupdesc;
+
+
+		partConstraint = generate_qual_for_defaultpart(parent, bound, &defid);
+		partConstraint = (List *) eval_const_expressions(NULL,
+													(Node *) partConstraint);
+		partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
+		partConstraint = list_make1(make_ands_explicit(partConstraint));
+
+		/*
+		 * Generate the constraint and default execution states
+		 */
+		estate = CreateExecutorState();
+
+		defrel = heap_open(defid, AccessExclusiveLock);
+		tupdesc = CreateTupleDescCopy(RelationGetDescr(defrel));
+
+		/* Build expression execution states for partition check quals */
+		partqualstate = ExecPrepareCheck(partConstraint,
+						estate);
+
+		econtext = GetPerTupleExprContext(estate);
+		snapshot = RegisterSnapshot(GetLatestSnapshot());
+		scan = heap_beginscan(defrel, snapshot, 0, NULL);
+		tupslot = MakeSingleTupleTableSlot(tupdesc);
+
+		/*
+		 * Switch to per-tuple memory context and reset it for each tuple
+		 * produced, so we don't leak memory.
+		 */
+		oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		{
+			ExecStoreTuple(tuple, tupslot, InvalidBuffer, false);
+			econtext->ecxt_scantuple = tupslot;
+			if (partqualstate && !ExecCheck(partqualstate, econtext))
+				ereport(ERROR,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+				errmsg("new default partition constraint is violated by some row")));
+			ResetExprContext(econtext);
+			CHECK_FOR_INTERRUPTS();
+		}
+		CacheInvalidateRelcache(defrel);
+		MemoryContextSwitchTo(oldCxt);
+		heap_endscan(scan);
+		UnregisterSnapshot(snapshot);
+		ExecDropSingleTupleTableSlot(tupslot);
+		FreeExecutorState(estate);
+		heap_close(defrel, AccessExclusiveLock);
+	}
 }
 
 /*
@@ -884,6 +992,116 @@ get_partition_parent(Oid relid)
 }
 
 /*
+ * Returns true if the partition bound is default
+ */
+bool
+isDefaultPartitionBound(Node *value)
+{
+	if (IsA(value, DefElem))
+	{
+		DefElem *defvalue = (DefElem *) value;
+		if(!strcmp(defvalue->defname, "DEFAULT"))
+			return true;
+		return false;
+	}
+	return false;
+}
+
+/*
+ * Return the bound spec list to be used
+ * in partition constraint of default partition
+ */
+List *
+get_qual_for_default(Relation parent, Oid *defid)
+{
+	List	   *inhoids;
+	ListCell   *cell;
+	List	   *boundspecs = 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 *cell1;
+
+		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));
+		foreach(cell1, bspec->listdatums)
+		{
+			Node *value = lfirst(cell1);
+			if (isDefaultPartitionBound(value))
+			{
+				def_elem = true;
+				*defid  = inhrelid;
+			}
+		}
+		if (!def_elem)
+		{
+			foreach(cell1, bspec->listdatums)
+			{
+				Node *value = lfirst(cell1);
+				boundspecs = lappend(boundspecs, value);
+			}
+		}
+		ReleaseSysCache(tuple);
+	}
+	return boundspecs;
+}
+
+/*
+ * Return a list of executable expressions as new partition constraint
+ * for default partition while adding a new partition after default
+ */
+List *
+generate_qual_for_defaultpart(Relation parent, Node *bound, Oid * defid)
+{
+	PartitionKey key = RelationGetPartitionKey(parent);
+	PartitionBoundSpec *spec;
+	List	   *partConstraint = NIL;
+	ListCell   *cell;
+	List       *boundspecs = NIL;
+	List       *bound_datums;
+
+	spec = (PartitionBoundSpec *) bound;
+	bound_datums = list_copy(spec->listdatums);
+
+	boundspecs = get_qual_for_default(parent, defid);
+
+	foreach(cell, bound_datums)
+	{
+		Node *value = lfirst(cell);
+		boundspecs = lappend(boundspecs, value);
+	}
+	partConstraint = get_qual_for_list(key, spec, true, boundspecs);
+	return partConstraint;
+}
+
+/*
  * get_qual_from_partbound
  *		Given a parser node for partition bound, return the list of executable
  *		expressions as partition constraint
@@ -894,6 +1112,10 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
 	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
 	PartitionKey key = RelationGetPartitionKey(parent);
 	List	   *my_qual = NIL;
+	bool		is_def = false;
+	Oid        defid;
+	ListCell   *cell;
+	List	   *boundspecs = NIL;
 
 	Assert(key != NULL);
 
@@ -901,9 +1123,16 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
 	{
 		case PARTITION_STRATEGY_LIST:
 			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
-			my_qual = get_qual_for_list(key, spec);
+			foreach(cell, spec->listdatums)
+			{
+				Node *value = lfirst(cell);
+				if (isDefaultPartitionBound(value))
+					is_def = true;
+			}
+			if (is_def)
+				boundspecs = get_qual_for_default(parent, &defid);
+			my_qual = get_qual_for_list(key, spec, is_def, boundspecs);
 			break;
-
 		case PARTITION_STRATEGY_RANGE:
 			Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
 			my_qual = get_qual_for_range(key, spec);
@@ -1151,7 +1380,8 @@ RelationGetPartitionDispatchInfo(Relation rel, int lockmode,
  * Returns a list of expressions to use as a list partition's constraint.
  */
 static List *
-get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
+get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec, bool is_def,
+					List *boundspecs)
 {
 	List	   *result;
 	ArrayExpr  *arr;
@@ -1177,12 +1407,14 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 	else
 		keyCol = (Expr *) copyObject(linitial(key->partexprs));
 
+	if (!is_def)
+		boundspecs = spec->listdatums;
 	/*
 	 * We must remove any NULL value in the list; we handle it separately
 	 * below.
 	 */
 	prev = NULL;
-	for (cell = list_head(spec->listdatums); cell; cell = next)
+	for (cell = list_head(boundspecs); cell; cell = next)
 	{
 		Const	   *val = (Const *) lfirst(cell);
 
@@ -1191,14 +1423,14 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 		if (val->constisnull)
 		{
 			list_has_null = true;
-			spec->listdatums = list_delete_cell(spec->listdatums,
+			boundspecs = list_delete_cell(boundspecs,
 												cell, prev);
 		}
 		else
 			prev = cell;
 	}
 
-	if (!list_has_null)
+	if ((is_def && list_has_null) || (!is_def && !list_has_null))
 	{
 		/*
 		 * Gin up a col IS NOT NULL test that will be AND'd with other
@@ -1210,7 +1442,7 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 		nulltest1->argisrow = false;
 		nulltest1->location = -1;
 	}
-	else
+	else if(!is_def && list_has_null)
 	{
 		/*
 		 * Gin up a col IS NULL test that will be OR'd with other expressions
@@ -1229,7 +1461,7 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 		: key->parttypid[0];
 	arr->array_collid = key->parttypcoll[0];
 	arr->element_typeid = key->parttypid[0];
-	arr->elements = spec->listdatums;
+	arr->elements = boundspecs;
 	arr->multidims = false;
 	arr->location = -1;
 
@@ -1243,15 +1475,34 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 										  key->partcollation[0],
 										  COERCE_EXPLICIT_CAST);
 
+	/* Build leftop <> ALL(rightop). This is for default partition */
+	if (is_def)
+	{
+		/* Find the negator for the partition operator above */
+		if(get_negator(operoid) == InvalidOid)
+			ereport(ERROR, (errcode(ERRCODE_CHECK_VIOLATION),
+			errmsg("DEFAULT partition cannot be used without negator of operator  %s",
+						get_opname(operoid))));
+		operoid = get_negator(operoid);
+		opexpr = makeNode(ScalarArrayOpExpr);
+		opexpr->opno = operoid;
+		opexpr->opfuncid = get_opcode(operoid);
+		opexpr->useOr = false;
+		opexpr->inputcollid = key->partcollation[0];
+		opexpr->args = list_make2(keyCol, arr);
+		opexpr->location = -1;
+	}
 	/* Build leftop = ANY (rightop) */
-	opexpr = makeNode(ScalarArrayOpExpr);
-	opexpr->opno = operoid;
-	opexpr->opfuncid = get_opcode(operoid);
-	opexpr->useOr = true;
-	opexpr->inputcollid = key->partcollation[0];
-	opexpr->args = list_make2(keyCol, arr);
-	opexpr->location = -1;
-
+	else
+	{
+		opexpr = makeNode(ScalarArrayOpExpr);
+		opexpr->opno = operoid;
+		opexpr->opfuncid = get_opcode(operoid);
+		opexpr->useOr = true;
+		opexpr->inputcollid = key->partcollation[0];
+		opexpr->args = list_make2(keyCol, arr);
+		opexpr->location = -1;
+	}
 	if (nulltest1)
 		result = list_make2(nulltest1, opexpr);
 	else if (nulltest2)
@@ -1775,10 +2026,25 @@ get_partition_for_tuple(PartitionDispatch *pd,
 		 */
 		if (cur_index < 0)
 		{
-			result = -1;
-			*failed_at = parent;
-			*failed_slot = slot;
-			break;
+			/*
+			 * If partitioned table has a default partition, return
+			 * its sequence number
+			 */
+			if (partdesc->boundinfo->has_default)
+			{
+				result = parent->indexes[partdesc->boundinfo->default_index];
+				if (result >= 0)
+					break;
+				else
+					parent = pd[-parent->indexes[partdesc->boundinfo->default_index]];
+			}
+			else
+			{
+				result = -1;
+				*failed_at = parent;
+				*failed_slot = slot;
+				break;
+			}
 		}
 		else if (parent->indexes[cur_index] >= 0)
 		{
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 818d2c2..dffebc8 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -579,6 +579,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <node>		ForValues
 %type <node>		partbound_datum
 %type <list>		partbound_datum_list
+%type <node>		default_partition
+%type <list>		default_part_list
 %type <partrange_datum>	PartitionRangeDatum
 %type <list>		range_datum_list
 
@@ -2669,6 +2671,17 @@ ForValues:
 
 					$$ = (Node *) n;
 				}
+			| default_part_list
+				{
+					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+					n->strategy = PARTITION_DEFAULT;
+					n->listdatums = $1;
+					n->location = @1;
+
+					$$ = (Node *) n;
+				}
+
 		;
 
 partbound_datum:
@@ -2683,6 +2696,13 @@ partbound_datum_list:
 												{ $$ = lappend($1, $3); }
 		;
 
+default_partition:
+			DEFAULT  { $$ = (Node *)makeDefElem("DEFAULT", NULL, @1); }
+
+default_part_list:
+			default_partition						{ $$ = list_make1($1); }
+		;
+
 range_datum_list:
 			PartitionRangeDatum					{ $$ = list_make1($1); }
 			| range_datum_list ',' PartitionRangeDatum
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e187409..13295a3 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -3298,55 +3299,67 @@ transformPartitionBound(ParseState *pstate, Relation parent, Node *bound)
 										 false, false);
 
 		if (spec->strategy != PARTITION_STRATEGY_LIST)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
-				  errmsg("invalid bound specification for a list partition"),
+		{
+			/*
+			 * If the partition is the default partition switch
+			 * back to PARTITION_STRATEGY_LIST
+			 */
+			if (spec->strategy == PARTITION_DEFAULT)
+				result_spec->strategy = PARTITION_STRATEGY_LIST;
+			else
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("invalid bound specification for a list partition"),
 					 parser_errposition(pstate, exprLocation(bound))));
+		}
 
 		result_spec->listdatums = NIL;
 		foreach(cell, spec->listdatums)
 		{
-			A_Const    *con = (A_Const *) lfirst(cell);
-			Node	   *value;
-			ListCell   *cell2;
-			bool		duplicate;
-
-			value = (Node *) make_const(pstate, &con->val, con->location);
-			value = coerce_to_target_type(pstate,
-										  value, exprType(value),
-										  get_partition_col_typid(key, 0),
-										  get_partition_col_typmod(key, 0),
-										  COERCION_ASSIGNMENT,
-										  COERCE_IMPLICIT_CAST,
-										  -1);
-
-			if (value == NULL)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
-							 format_type_be(get_partition_col_typid(key, 0)),
-								colname),
-						 parser_errposition(pstate,
-											exprLocation((Node *) con))));
-
-			/* Simplify the expression */
-			value = (Node *) expression_planner((Expr *) value);
-
-			/* Don't add to the result if the value is a duplicate */
-			duplicate = false;
-			foreach(cell2, result_spec->listdatums)
+			Node *value = lfirst(cell);
+			/* Perform the transformation only for non default partition */
+			if (!(isDefaultPartitionBound(value)))
 			{
-				Const	   *value2 = (Const *) lfirst(cell2);
+				A_Const    *con = (A_Const *) lfirst(cell);
+				ListCell   *cell2;
+				bool		duplicate;
 
-				if (equal(value, value2))
+				value = (Node *) make_const(pstate, &con->val, con->location);
+				value = coerce_to_target_type(pstate,
+											value, exprType(value),
+											get_partition_col_typid(key, 0),
+											get_partition_col_typmod(key, 0),
+											COERCION_ASSIGNMENT,
+											COERCE_IMPLICIT_CAST,
+											-1);
+
+				if (value == NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+								format_type_be(get_partition_col_typid(key, 0)),
+									colname),
+							parser_errposition(pstate,
+												exprLocation((Node *) con))));
+
+				/* Simplify the expression */
+				value = (Node *) expression_planner((Expr *) value);
+
+				/* Don't add to the result if the value is a duplicate */
+				duplicate = false;
+				foreach(cell2, result_spec->listdatums)
 				{
-					duplicate = true;
-					break;
+					Const	   *value2 = (Const *) lfirst(cell2);
+
+					if (equal(value, value2))
+					{
+						duplicate = true;
+						break;
+					}
 				}
+				if (duplicate)
+					continue;
 			}
-			if (duplicate)
-				continue;
-
 			result_spec->listdatums = lappend(result_spec->listdatums,
 											  value);
 		}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index cbde1ff..2764acc 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -24,6 +24,7 @@
 #include "access/sysattr.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
+#include "catalog/partition.h"
 #include "catalog/pg_aggregate.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_authid.h"
@@ -8627,8 +8628,14 @@ get_rule_expr(Node *node, deparse_context *context,
 						sep = "";
 						foreach(cell, spec->listdatums)
 						{
+							Node *value = lfirst(cell);
 							Const	   *val = lfirst(cell);
 
+							if (isDefaultPartitionBound(value))
+							{
+								appendStringInfoString(buf, "DEFAULT");
+								continue;
+							}
 							appendStringInfoString(buf, sep);
 							get_const_expr(val, context, -1);
 							sep = ", ";
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 421644c..7cb4f0a 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -74,9 +74,12 @@ extern void RelationBuildPartitionDesc(Relation relation);
 extern bool partition_bounds_equal(PartitionKey key,
 					   PartitionBoundInfo p1, PartitionBoundInfo p2);
 
+extern bool isDefaultPartitionBound(Node *value);
 extern void check_new_partition_bound(char *relname, Relation parent, Node *bound);
 extern Oid	get_partition_parent(Oid relid);
 extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
+extern List *generate_qual_for_defaultpart(Relation parent, Node *bound, Oid *defid);
+extern List *get_qual_for_default(Relation parent, Oid *defid);
 extern List *map_partition_varattnos(List *expr, int target_varno,
 						Relation partrel, Relation parent);
 extern List *RelationGetPartitionQual(Relation rel);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index e1d454a..5853c70 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -780,6 +780,7 @@ typedef struct PartitionSpec
 
 #define PARTITION_STRATEGY_LIST		'l'
 #define PARTITION_STRATEGY_RANGE	'r'
+#define PARTITION_DEFAULT	'd'
 
 /*
  * PartitionBoundSpec - a partition bound specification
#62Rajkumar Raghuwanshi
rajkumar.raghuwanshi@enterprisedb.com
In reply to: Rahila Syed (#61)
Re: Adding support for Default partition in partitioning

On Thu, May 4, 2017 at 5:14 PM, Rahila Syed <rahilasyed90@gmail.com> wrote:

The syntax implemented in this patch is as follows,

CREATE TABLE p11 PARTITION OF p1 DEFAULT;

Applied v9 patches, table description still showing old pattern of default

partition. Is it expected?

create table lpd (a int, b int, c varchar) partition by list(a);
create table lpd_d partition of lpd DEFAULT;

\d+ lpd
Table "public.lpd"
Column | Type | Collation | Nullable | Default | Storage |
Stats target | Description
--------+-------------------+-----------+----------+---------+----------+--------------+-------------
a | integer | | | | plain
| |
b | integer | | | | plain
| |
c | character varying | | | | extended
| |
Partition key: LIST (a)
Partitions: lpd_d FOR VALUES IN (DEFAULT)

#63Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Rajkumar Raghuwanshi (#62)
Re: Adding support for Default partition in partitioning

Hi Rahila,

I have started reviewing your latest patch, and here are my initial
comments:

1.
In following block, we can just do with def_index, and we do not need
found_def
flag. We can check if def_index is -1 or not to decide if default partition
is
present.

@@ -166,6 +172,8 @@ RelationBuildPartitionDesc(Relation rel)
/* List partitioning specific */
PartitionListValue **all_values = NULL;
bool found_null = false;
+ bool found_def = false;
+ int def_index = -1;
int null_index = -1;

2.
In check_new_partition_bound, in case of PARTITION_STRATEGY_LIST, remove
following duplicate declaration of boundinfo, because it is confusing and
after
your changes it is not needed as its not getting overridden in the if block
locally.
if (partdesc->nparts > 0)
{
PartitionBoundInfo boundinfo = partdesc->boundinfo;
ListCell *cell;

3.
In following function isDefaultPartitionBound, first statement "return
false"
is not needed.

+ * Returns true if the partition bound is default
+ */
+bool
+isDefaultPartitionBound(Node *value)
+{
+ if (IsA(value, DefElem))
+ {
+ DefElem *defvalue = (DefElem *) value;
+ if(!strcmp(defvalue->defname, "DEFAULT"))
+ return true;
+ return false;
+ }
+ return false;
+}

4.
As mentioned in my previous set of comments, following if block inside a
loop
in get_qual_for_default needs a break:

+ foreach(cell1, bspec->listdatums)
+ {
+ Node *value = lfirst(cell1);
+ if (isDefaultPartitionBound(value))
+ {
+ def_elem = true;
+ *defid  = inhrelid;
+ }
+ }

5.
In the grammar the rule default_part_list is not needed:

+default_partition:
+ DEFAULT  { $$ = (Node *)makeDefElem("DEFAULT", NULL, @1); }
+
+default_part_list:
+ default_partition { $$ = list_make1($1); }
+ ;
+

Instead you can simply declare default_partition as a list and write it as:

default_partition:
DEFAULT
{
Node *def = (Node *)makeDefElem("DEFAULT", NULL, @1);
$$ = list_make1(def);
}

6.
You need to change the output of the describe command, which is currently
as below: postgres=# \d+ test; Table "public.test" Column | Type |
Collation | Nullable | Default | Storage | Stats target | Description
--------+---------+-----------+----------+---------+---------+--------------+-------------
a | integer | | | | plain | | b | date | | | | plain | | Partition key:
LIST (a) Partitions: pd FOR VALUES IN (DEFAULT), test_p1 FOR VALUES IN (4,
5) What about changing the Paritions output as below: Partitions: *pd
DEFAULT,* test_p1 FOR VALUES IN (4, 5)

7.
You need to handle tab completion for DEFAULT.
e.g.
If I partially type following command:
CREATE TABLE pd PARTITION OF test DEFA
and then press tab, I get following completion:
CREATE TABLE pd PARTITION OF test FOR VALUES

I did some primary testing and did not find any problem so far.

I will review and test further and let you know my comments.

Regards,
Jeevan Ladhe

On Thu, May 4, 2017 at 6:09 PM, Rajkumar Raghuwanshi <
rajkumar.raghuwanshi@enterprisedb.com> wrote:

Show quoted text

On Thu, May 4, 2017 at 5:14 PM, Rahila Syed <rahilasyed90@gmail.com>
wrote:

The syntax implemented in this patch is as follows,

CREATE TABLE p11 PARTITION OF p1 DEFAULT;

Applied v9 patches, table description still showing old pattern of

default partition. Is it expected?

create table lpd (a int, b int, c varchar) partition by list(a);
create table lpd_d partition of lpd DEFAULT;

\d+ lpd
Table "public.lpd"
Column | Type | Collation | Nullable | Default | Storage |
Stats target | Description
--------+-------------------+-----------+----------+--------
-+----------+--------------+-------------
a | integer | | | | plain
| |
b | integer | | | | plain
| |
c | character varying | | | | extended
| |
Partition key: LIST (a)
Partitions: lpd_d FOR VALUES IN (DEFAULT)

#64Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Jeevan Ladhe (#63)
Re: Adding support for Default partition in partitioning

While reviewing the code I was trying to explore more cases, and I here
comes an
open question to my mind:
should we allow the default partition table to be partitioned further?

If we allow it(as in the current case) then observe following case, where I
have defined a default partitioned which is further partitioned on a
different
column.

postgres=# CREATE TABLE test ( a int, b int, c int) PARTITION BY LIST (a);
CREATE TABLE
postgres=# CREATE TABLE test_p1 PARTITION OF test FOR VALUES IN(4, 5, 6, 7,
8);
CREATE TABLE
postgres=# CREATE TABLE test_pd PARTITION OF test DEFAULT PARTITION BY
LIST(b);
CREATE TABLE
postgres=# INSERT INTO test VALUES (20, 24, 12);
ERROR: no partition of relation "test_pd" found for row
DETAIL: Partition key of the failing row contains (b) = (24).

Note, that it does not allow inserting the tuple(20, 24, 12) because though
a=20
would fall in default partition i.e. test_pd, table test_pd itself is
further
partitioned and does not have any partition satisfying b=24.
Further if I define a default partition for table test_pd, the the tuple
gets inserted.

Doesn't this sound like the whole purpose of having DEFAULT partition on
test
table is defeated?

Any views?

Regards,
Jeevan Ladhe

#65Sven R. Kunze
srkunze@mail.de
In reply to: Rahila Syed (#61)
Re: Adding support for Default partition in partitioning

Hi Rahila,

still thinking about the syntax (sorry):

On 04.05.2017 13:44, Rahila Syed wrote:

[...] The syntax implemented in this patch is as follows,

CREATE TABLE p11 PARTITION OF p1 DEFAULT;

Rewriting the following:

On Thu, May 4, 2017 at 4:02 PM, amul sul <sulamul@gmail.com
<mailto:sulamul@gmail.com>> wrote:

[...] CREATE TABLE p1 PARTITION OF test FOR VALUES IN (DEFAULT)
PARTITION BY LIST(b); [...]

It yields

CREATE TABLE p1 PARTITION OF test DEFAULT PARTITION BY LIST(b);

This reads to me like "DEFAULT PARTITION".

I can imagine a lot of confusion when those queries are encountered in
the wild. I know this thread is about creating a default partition but I
were to propose a minor change in the following direction, I think
confusion would be greatly avoided:

CREATE TABLE p1 PARTITION OF test*AS *DEFAULT PARTITION*ED* BY LIST(b);

I know it's a bit longer but I think those 4 characters might serve
readability in the long term. It was especially confusing to see
PARTITION in two positions serving two different functions.

Sven

#66Rajkumar Raghuwanshi
rajkumar.raghuwanshi@enterprisedb.com
In reply to: Sven R. Kunze (#65)
Re: Adding support for Default partition in partitioning

Hi Rahila,

pg_restore is failing for default partition, dump file still storing old
syntax of default partition.

create table lpd (a int, b int, c varchar) partition by list(a);
create table lpd_d partition of lpd DEFAULT;

create database bkp owner 'edb';
grant all on DATABASE bkp to edb;

--take plain dump of existing database
\! ./pg_dump -f lpd_test.sql -Fp -d postgres

--restore plain backup to new database bkp
\! ./psql -f lpd_test.sql -d bkp

psql:lpd_test.sql:63: ERROR: syntax error at or near "DEFAULT"
LINE 2: FOR VALUES IN (DEFAULT);
^

vi lpd_test.sql

--
-- Name: lpd; Type: TABLE; Schema: public; Owner: edb
--

CREATE TABLE lpd (
a integer,
b integer,
c character varying
)
PARTITION BY LIST (a);

ALTER TABLE lpd OWNER TO edb;

--
-- Name: lpd_d; Type: TABLE; Schema: public; Owner: edb
--

CREATE TABLE lpd_d PARTITION OF lpd
FOR VALUES IN (DEFAULT);

ALTER TABLE lpd_d OWNER TO edb;

Thanks,
Rajkumar

#67Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Rajkumar Raghuwanshi (#66)
Re: Adding support for Default partition in partitioning

Hi Rahila,

I am not able add a new partition if default partition is further
partitioned
with default partition.

Consider example below:

postgres=# CREATE TABLE test ( a int, b int, c int) PARTITION BY LIST (a);
CREATE TABLE
postgres=# CREATE TABLE test_p1 PARTITION OF test FOR VALUES IN(4, 5, 6, 7,
8);
CREATE TABLE
postgres=# CREATE TABLE test_pd PARTITION OF test DEFAULT PARTITION BY
LIST(b);
CREATE TABLE
postgres=# CREATE TABLE test_pd_pd PARTITION OF test_pd DEFAULT;
CREATE TABLE
postgres=# INSERT INTO test VALUES (20, 24, 12);
INSERT 0 1
*postgres=# CREATE TABLE test_p2 PARTITION OF test FOR VALUES IN(15);*
*ERROR: could not open file "base/12335/16420": No such file or directory*

Thanks,
Jeevan Ladhe

On Fri, May 5, 2017 at 11:55 AM, Rajkumar Raghuwanshi <
rajkumar.raghuwanshi@enterprisedb.com> wrote:

Show quoted text

Hi Rahila,

pg_restore is failing for default partition, dump file still storing old
syntax of default partition.

create table lpd (a int, b int, c varchar) partition by list(a);
create table lpd_d partition of lpd DEFAULT;

create database bkp owner 'edb';
grant all on DATABASE bkp to edb;

--take plain dump of existing database
\! ./pg_dump -f lpd_test.sql -Fp -d postgres

--restore plain backup to new database bkp
\! ./psql -f lpd_test.sql -d bkp

psql:lpd_test.sql:63: ERROR: syntax error at or near "DEFAULT"
LINE 2: FOR VALUES IN (DEFAULT);
^

vi lpd_test.sql

--
-- Name: lpd; Type: TABLE; Schema: public; Owner: edb
--

CREATE TABLE lpd (
a integer,
b integer,
c character varying
)
PARTITION BY LIST (a);

ALTER TABLE lpd OWNER TO edb;

--
-- Name: lpd_d; Type: TABLE; Schema: public; Owner: edb
--

CREATE TABLE lpd_d PARTITION OF lpd
FOR VALUES IN (DEFAULT);

ALTER TABLE lpd_d OWNER TO edb;

Thanks,
Rajkumar

#68Rahila Syed
rahilasyed90@gmail.com
In reply to: Jeevan Ladhe (#67)
Re: Adding support for Default partition in partitioning

I am not able add a new partition if default partition is further

partitioned

with default partition.

Thanks for reporting. I will fix this.

pg_restore is failing for default partition, dump file still storing old

syntax of default partition.
Thanks for reporting . I will fix this once the syntax is finalized.

On Fri, May 5, 2017 at 12:46 PM, Jeevan Ladhe <jeevan.ladhe@enterprisedb.com

Show quoted text

wrote:

Hi Rahila,

I am not able add a new partition if default partition is further
partitioned
with default partition.

Consider example below:

postgres=# CREATE TABLE test ( a int, b int, c int) PARTITION BY LIST (a);
CREATE TABLE
postgres=# CREATE TABLE test_p1 PARTITION OF test FOR VALUES IN(4, 5, 6,
7, 8);
CREATE TABLE
postgres=# CREATE TABLE test_pd PARTITION OF test DEFAULT PARTITION BY
LIST(b);
CREATE TABLE
postgres=# CREATE TABLE test_pd_pd PARTITION OF test_pd DEFAULT;
CREATE TABLE
postgres=# INSERT INTO test VALUES (20, 24, 12);
INSERT 0 1
*postgres=# CREATE TABLE test_p2 PARTITION OF test FOR VALUES IN(15);*
*ERROR: could not open file "base/12335/16420": No such file or directory*

Thanks,
Jeevan Ladhe

On Fri, May 5, 2017 at 11:55 AM, Rajkumar Raghuwanshi <
rajkumar.raghuwanshi@enterprisedb.com> wrote:

Hi Rahila,

pg_restore is failing for default partition, dump file still storing old
syntax of default partition.

create table lpd (a int, b int, c varchar) partition by list(a);
create table lpd_d partition of lpd DEFAULT;

create database bkp owner 'edb';
grant all on DATABASE bkp to edb;

--take plain dump of existing database
\! ./pg_dump -f lpd_test.sql -Fp -d postgres

--restore plain backup to new database bkp
\! ./psql -f lpd_test.sql -d bkp

psql:lpd_test.sql:63: ERROR: syntax error at or near "DEFAULT"
LINE 2: FOR VALUES IN (DEFAULT);
^

vi lpd_test.sql

--
-- Name: lpd; Type: TABLE; Schema: public; Owner: edb
--

CREATE TABLE lpd (
a integer,
b integer,
c character varying
)
PARTITION BY LIST (a);

ALTER TABLE lpd OWNER TO edb;

--
-- Name: lpd_d; Type: TABLE; Schema: public; Owner: edb
--

CREATE TABLE lpd_d PARTITION OF lpd
FOR VALUES IN (DEFAULT);

ALTER TABLE lpd_d OWNER TO edb;

Thanks,
Rajkumar

#69Robert Haas
robertmhaas@gmail.com
In reply to: Jeevan Ladhe (#64)
Re: Adding support for Default partition in partitioning

On Thu, May 4, 2017 at 4:28 PM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

While reviewing the code I was trying to explore more cases, and I here
comes an
open question to my mind:
should we allow the default partition table to be partitioned further?

I think yes. In general, you are allowed to partition a partition,
and I can't see any justification for restricting that for default
partitions when we allow it everywhere else.

If we allow it(as in the current case) then observe following case, where I
have defined a default partitioned which is further partitioned on a
different
column.

postgres=# CREATE TABLE test ( a int, b int, c int) PARTITION BY LIST (a);
CREATE TABLE
postgres=# CREATE TABLE test_p1 PARTITION OF test FOR VALUES IN(4, 5, 6, 7,
8);
CREATE TABLE
postgres=# CREATE TABLE test_pd PARTITION OF test DEFAULT PARTITION BY
LIST(b);
CREATE TABLE
postgres=# INSERT INTO test VALUES (20, 24, 12);
ERROR: no partition of relation "test_pd" found for row
DETAIL: Partition key of the failing row contains (b) = (24).

Note, that it does not allow inserting the tuple(20, 24, 12) because though
a=20
would fall in default partition i.e. test_pd, table test_pd itself is
further
partitioned and does not have any partition satisfying b=24.

Right, that looks like correct behavior. You would have gotten the
same result if you had tried to insert into test_pd directly.

Further if I define a default partition for table test_pd, the the tuple
gets inserted.

That also sounds correct.

Doesn't this sound like the whole purpose of having DEFAULT partition on
test
table is defeated?

Not to me. It's possible to do lots of silly things with partitioned
tables. For example, one case that we talked about before is that you
can define a range partition for, say, VALUES (0) TO (100), and then
subpartition it and give the subpartitions bounds which are outside
the range 0-100. That's obviously silly and will lead to failures
inserting tuples, but we chose not to try to prohibit it because it's
not really broken, just useless. There are lots of similar cases
involving other features. For example, you can apply an inherited
CHECK (false) constraint to a table, which makes it impossible for
that table or any of its children to ever contain any rows; that is
probably a dumb configuration. You can create two unique indexes with
exactly the same definition; unless you're creating a new one with the
intent of dropping the old one, that doesn't make sense. You can
define a trigger that always throws an ERROR and then another trigger
which runs later that modifies the tuple; the second will never be run
because the first one will always kill the transaction before we get
there. Those things are all legal, but often unuseful. Similarly
here. Defining a default list partition and then subpartitioning it
by list is not likely to be a good schema design, but it doesn't mean
we should try to disallow it. It is important to distinguish between
things that are actually *broken* (like a partitioning configuration
where the tuples that can be inserted into a partition manually differ
from the ones that are routed to it automatically) and things that are
merely *lame* (like creating a multi-level partitioning hierarchy when
a single level would have done the job just as well). The former
should be prevented by the code, while the latter is at most a
documentation issue.

--
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

#70Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Robert Haas (#69)
Re: Adding support for Default partition in partitioning

Hi Robert,

Thanks for your explnation.

On Mon, May 8, 2017 at 9:56 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Thu, May 4, 2017 at 4:28 PM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

While reviewing the code I was trying to explore more cases, and I here
comes an
open question to my mind:
should we allow the default partition table to be partitioned further?

I think yes. In general, you are allowed to partition a partition,
and I can't see any justification for restricting that for default
partitions when we allow it everywhere else.

If we allow it(as in the current case) then observe following case,

where I

have defined a default partitioned which is further partitioned on a
different
column.

postgres=# CREATE TABLE test ( a int, b int, c int) PARTITION BY LIST

(a);

CREATE TABLE
postgres=# CREATE TABLE test_p1 PARTITION OF test FOR VALUES IN(4, 5, 6,

7,

8);
CREATE TABLE
postgres=# CREATE TABLE test_pd PARTITION OF test DEFAULT PARTITION BY
LIST(b);
CREATE TABLE
postgres=# INSERT INTO test VALUES (20, 24, 12);
ERROR: no partition of relation "test_pd" found for row
DETAIL: Partition key of the failing row contains (b) = (24).

Note, that it does not allow inserting the tuple(20, 24, 12) because

though

a=20
would fall in default partition i.e. test_pd, table test_pd itself is
further
partitioned and does not have any partition satisfying b=24.

Right, that looks like correct behavior. You would have gotten the
same result if you had tried to insert into test_pd directly.

Further if I define a default partition for table test_pd, the the tuple
gets inserted.

That also sounds correct.

Doesn't this sound like the whole purpose of having DEFAULT partition on
test
table is defeated?

Not to me. It's possible to do lots of silly things with partitioned
tables. For example, one case that we talked about before is that you
can define a range partition for, say, VALUES (0) TO (100), and then
subpartition it and give the subpartitions bounds which are outside
the range 0-100. That's obviously silly and will lead to failures
inserting tuples, but we chose not to try to prohibit it because it's
not really broken, just useless. There are lots of similar cases
involving other features. For example, you can apply an inherited
CHECK (false) constraint to a table, which makes it impossible for
that table or any of its children to ever contain any rows; that is
probably a dumb configuration. You can create two unique indexes with
exactly the same definition; unless you're creating a new one with the
intent of dropping the old one, that doesn't make sense. You can
define a trigger that always throws an ERROR and then another trigger
which runs later that modifies the tuple; the second will never be run
because the first one will always kill the transaction before we get
there. Those things are all legal, but often unuseful. Similarly
here. Defining a default list partition and then subpartitioning it
by list is not likely to be a good schema design, but it doesn't mean
we should try to disallow it. It is important to distinguish between
things that are actually *broken* (like a partitioning configuration
where the tuples that can be inserted into a partition manually differ
from the ones that are routed to it automatically) and things that are
merely *lame* (like creating a multi-level partitioning hierarchy when
a single level would have done the job just as well). The former
should be prevented by the code, while the latter is at most a
documentation issue.

I agree with you that it is a user perspective on how he decides to do
partitions of already partitioned table, and also we should have a
demarcation between things to be handled by code and things to be
left as common-sense or ability to define a good schema.

I am ok with current behavior, provided we have atleast one-lineer in
documentation alerting the user that partitioning the default partition will
limit the ability of routing the tuples that do not fit in any other
partitions.

Regards,
Jeevan Ladhe

#71Robert Haas
robertmhaas@gmail.com
In reply to: Sven R. Kunze (#65)
Re: Adding support for Default partition in partitioning

On Thu, May 4, 2017 at 4:40 PM, Sven R. Kunze <srkunze@mail.de> wrote:

It yields

CREATE TABLE p1 PARTITION OF test DEFAULT PARTITION BY LIST(b);

This reads to me like "DEFAULT PARTITION".

I can imagine a lot of confusion when those queries are encountered in the
wild. I know this thread is about creating a default partition but I were to
propose a minor change in the following direction, I think confusion would
be greatly avoided:

CREATE TABLE p1 PARTITION OF test AS DEFAULT PARTITIONED BY LIST(b);

I know it's a bit longer but I think those 4 characters might serve
readability in the long term. It was especially confusing to see PARTITION
in two positions serving two different functions.

Well, we certainly can't make that change just for default partitions.
I mean, that would be non-orthogonal, right? You can't say that the
way to subpartition is to write "PARTITION BY strategy" when the table
unpartitioned or is a non-default partition but "PARTITIONED BY
strategy" when it is a default partition. That would certainly not be
a good way of confusing users less, and would probably result in a
variety of special cases in places like ruleutils.c or pg_dump, plus
some weasel-wording in the documentation. We COULD do a general
change from "CREATE TABLE table_name PARTITION BY strategy" to "CREATE
TABLE table_name PARTITIONED BY strategy". I don't have any
particular arguments against that except that the current syntax is
more like Oracle, which might count for something, and maybe the fact
that we're a month after feature freeze. Still, if we want to change
that, now would be the time; but I favor leaving it alone.

I don't have a big objection to adding AS. If that's the majority
vote, fine; if not, that's OK, too. I can see it might be a bit more
clear in the case you mention, but it might also just be a noise word
that we don't really need. There don't seem to be many uses of AS
that would pose a risk of actual grammar conflicts here. I can
imagine someone wanting to use CREATE TABLE ... PARTITION BY ... AS
SELECT ... to create and populate a partition in one command, but that
wouldn't be a conflict because it'd have to go AFTER the partition
specification. In the DEFAULT case, you'd end up with something like

CREATE TABLE p1 PARTITION OF test AS DEFAULT AS <query>

...which is neither great nor horrible syntax-wise and maybe not such
a good thing to support anyway since it would have to lock the parent
to add the partition and then keep the lock on the parent while
populating the new child (ouch).

So I guess I'm still in favor of the CREATE TABLE p1 PARTITION OF test
DEFAULT syntax, but if it ends up being AS DEFAULT instead, I can live
with that.

Other opinions?

--
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

#72Rahila Syed
rahilasyed90@gmail.com
In reply to: Robert Haas (#71)
Re: Adding support for Default partition in partitioning

+1 for AS DEFAULT syntax if it helps in improving readability specially in
following case

CREATE TABLE p1 PARTITION OF test AS DEFAULT PARTITION BY LIST(a);

Thank you,
Rahila Syed

On Tue, May 9, 2017 at 1:13 AM, Robert Haas <robertmhaas@gmail.com> wrote:

Show quoted text

On Thu, May 4, 2017 at 4:40 PM, Sven R. Kunze <srkunze@mail.de> wrote:

It yields

CREATE TABLE p1 PARTITION OF test DEFAULT PARTITION BY LIST(b);

This reads to me like "DEFAULT PARTITION".

I can imagine a lot of confusion when those queries are encountered in

the

wild. I know this thread is about creating a default partition but I

were to

propose a minor change in the following direction, I think confusion

would

be greatly avoided:

CREATE TABLE p1 PARTITION OF test AS DEFAULT PARTITIONED BY LIST(b);

I know it's a bit longer but I think those 4 characters might serve
readability in the long term. It was especially confusing to see

PARTITION

in two positions serving two different functions.

Well, we certainly can't make that change just for default partitions.
I mean, that would be non-orthogonal, right? You can't say that the
way to subpartition is to write "PARTITION BY strategy" when the table
unpartitioned or is a non-default partition but "PARTITIONED BY
strategy" when it is a default partition. That would certainly not be
a good way of confusing users less, and would probably result in a
variety of special cases in places like ruleutils.c or pg_dump, plus
some weasel-wording in the documentation. We COULD do a general
change from "CREATE TABLE table_name PARTITION BY strategy" to "CREATE
TABLE table_name PARTITIONED BY strategy". I don't have any
particular arguments against that except that the current syntax is
more like Oracle, which might count for something, and maybe the fact
that we're a month after feature freeze. Still, if we want to change
that, now would be the time; but I favor leaving it alone.

I don't have a big objection to adding AS. If that's the majority
vote, fine; if not, that's OK, too. I can see it might be a bit more
clear in the case you mention, but it might also just be a noise word
that we don't really need. There don't seem to be many uses of AS
that would pose a risk of actual grammar conflicts here. I can
imagine someone wanting to use CREATE TABLE ... PARTITION BY ... AS
SELECT ... to create and populate a partition in one command, but that
wouldn't be a conflict because it'd have to go AFTER the partition
specification. In the DEFAULT case, you'd end up with something like

CREATE TABLE p1 PARTITION OF test AS DEFAULT AS <query>

...which is neither great nor horrible syntax-wise and maybe not such
a good thing to support anyway since it would have to lock the parent
to add the partition and then keep the lock on the parent while
populating the new child (ouch).

So I guess I'm still in favor of the CREATE TABLE p1 PARTITION OF test
DEFAULT syntax, but if it ends up being AS DEFAULT instead, I can live
with that.

Other opinions?

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#73Rahila Syed
rahilasyed90@gmail.com
In reply to: Jeevan Ladhe (#67)
Re: Adding support for Default partition in partitioning

Hi Rahila,

I am not able add a new partition if default partition is further

partitioned

with default partition.

Consider example below:

postgres=# CREATE TABLE test ( a int, b int, c int) PARTITION BY LIST (a);
CREATE TABLE
postgres=# CREATE TABLE test_p1 PARTITION OF test FOR VALUES IN(4, 5, 6,

7, 8);

CREATE TABLE
postgres=# CREATE TABLE test_pd PARTITION OF test DEFAULT PARTITION BY

LIST(b);

CREATE TABLE
postgres=# CREATE TABLE test_pd_pd PARTITION OF test_pd DEFAULT;
CREATE TABLE
postgres=# INSERT INTO test VALUES (20, 24, 12);
INSERT 0 1

*>postgres=# CREATE TABLE test_p2 PARTITION OF test FOR VALUES IN(15);*

*ERROR: could not open file "base/12335/16420": No such file or directory*
Regarding fix for this I think we need to prohibit this case. That is
prohibit creation
of new partition after a default partition which is further partitioned.
Currently before adding a new partition after default partition all the
rows of default
partition are scanned and if a row which matches the new partitions
constraint exists
the new partition is not added.

If we allow this for default partition which is partitioned further, we
will have to scan
all the partitions of default partition for matching rows which can slow
down execution.

So to not hamper the performance, an error should be thrown in this case
and user should
be expected to change his schema to avoid partitioning default partitions.

Kindly give your opinions.

On Fri, May 5, 2017 at 12:46 PM, Jeevan Ladhe <jeevan.ladhe@enterprisedb.com

Show quoted text

wrote:

Hi Rahila,

I am not able add a new partition if default partition is further
partitioned
with default partition.

Consider example below:

postgres=# CREATE TABLE test ( a int, b int, c int) PARTITION BY LIST (a);
CREATE TABLE
postgres=# CREATE TABLE test_p1 PARTITION OF test FOR VALUES IN(4, 5, 6,
7, 8);
CREATE TABLE
postgres=# CREATE TABLE test_pd PARTITION OF test DEFAULT PARTITION BY
LIST(b);
CREATE TABLE
postgres=# CREATE TABLE test_pd_pd PARTITION OF test_pd DEFAULT;
CREATE TABLE
postgres=# INSERT INTO test VALUES (20, 24, 12);
INSERT 0 1
*postgres=# CREATE TABLE test_p2 PARTITION OF test FOR VALUES IN(15);*
*ERROR: could not open file "base/12335/16420": No such file or directory*

Thanks,
Jeevan Ladhe

On Fri, May 5, 2017 at 11:55 AM, Rajkumar Raghuwanshi <
rajkumar.raghuwanshi@enterprisedb.com> wrote:

Hi Rahila,

pg_restore is failing for default partition, dump file still storing old
syntax of default partition.

create table lpd (a int, b int, c varchar) partition by list(a);
create table lpd_d partition of lpd DEFAULT;

create database bkp owner 'edb';
grant all on DATABASE bkp to edb;

--take plain dump of existing database
\! ./pg_dump -f lpd_test.sql -Fp -d postgres

--restore plain backup to new database bkp
\! ./psql -f lpd_test.sql -d bkp

psql:lpd_test.sql:63: ERROR: syntax error at or near "DEFAULT"
LINE 2: FOR VALUES IN (DEFAULT);
^

vi lpd_test.sql

--
-- Name: lpd; Type: TABLE; Schema: public; Owner: edb
--

CREATE TABLE lpd (
a integer,
b integer,
c character varying
)
PARTITION BY LIST (a);

ALTER TABLE lpd OWNER TO edb;

--
-- Name: lpd_d; Type: TABLE; Schema: public; Owner: edb
--

CREATE TABLE lpd_d PARTITION OF lpd
FOR VALUES IN (DEFAULT);

ALTER TABLE lpd_d OWNER TO edb;

Thanks,
Rajkumar

#74Robert Haas
robertmhaas@gmail.com
In reply to: Rahila Syed (#73)
Re: Adding support for Default partition in partitioning

On Tue, May 9, 2017 at 9:26 AM, Rahila Syed <rahilasyed90@gmail.com> wrote:

Hi Rahila,

I am not able add a new partition if default partition is further
partitioned
with default partition.

Consider example below:

postgres=# CREATE TABLE test ( a int, b int, c int) PARTITION BY LIST (a);
CREATE TABLE
postgres=# CREATE TABLE test_p1 PARTITION OF test FOR VALUES IN(4, 5, 6, 7,
8);
CREATE TABLE
postgres=# CREATE TABLE test_pd PARTITION OF test DEFAULT PARTITION BY
LIST(b);
CREATE TABLE
postgres=# CREATE TABLE test_pd_pd PARTITION OF test_pd DEFAULT;
CREATE TABLE
postgres=# INSERT INTO test VALUES (20, 24, 12);
INSERT 0 1
postgres=# CREATE TABLE test_p2 PARTITION OF test FOR VALUES IN(15);

ERROR: could not open file "base/12335/16420": No such file or directory

Regarding fix for this I think we need to prohibit this case. That is
prohibit creation
of new partition after a default partition which is further partitioned.
Currently before adding a new partition after default partition all the rows
of default
partition are scanned and if a row which matches the new partitions
constraint exists
the new partition is not added.

If we allow this for default partition which is partitioned further, we will
have to scan
all the partitions of default partition for matching rows which can slow
down execution.

I think this case should be allowed and I don't think it should
require scanning all the partitions of the default partition. This is
no different than any other case where multiple levels of partitioning
are used. First, you route the tuple at the root level; then, you
route it at the next level; and so on. It shouldn't matter whether
the routing at the top level is to that level's default partition or
not.

--
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

#75Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#74)
Re: Adding support for Default partition in partitioning

On 2017/05/10 2:09, Robert Haas wrote:

On Tue, May 9, 2017 at 9:26 AM, Rahila Syed <rahilasyed90@gmail.com> wrote:

Hi Rahila,

I am not able add a new partition if default partition is further
partitioned
with default partition.

Consider example below:

postgres=# CREATE TABLE test ( a int, b int, c int) PARTITION BY LIST (a);
CREATE TABLE
postgres=# CREATE TABLE test_p1 PARTITION OF test FOR VALUES IN(4, 5, 6, 7,
8);
CREATE TABLE
postgres=# CREATE TABLE test_pd PARTITION OF test DEFAULT PARTITION BY
LIST(b);
CREATE TABLE
postgres=# CREATE TABLE test_pd_pd PARTITION OF test_pd DEFAULT;
CREATE TABLE
postgres=# INSERT INTO test VALUES (20, 24, 12);
INSERT 0 1
postgres=# CREATE TABLE test_p2 PARTITION OF test FOR VALUES IN(15);

ERROR: could not open file "base/12335/16420": No such file or directory

Regarding fix for this I think we need to prohibit this case. That is
prohibit creation
of new partition after a default partition which is further partitioned.
Currently before adding a new partition after default partition all the rows
of default
partition are scanned and if a row which matches the new partitions
constraint exists
the new partition is not added.

If we allow this for default partition which is partitioned further, we will
have to scan
all the partitions of default partition for matching rows which can slow
down execution.

I think this case should be allowed

+1

and I don't think it should
require scanning all the partitions of the default partition. This is
no different than any other case where multiple levels of partitioning
are used. First, you route the tuple at the root level; then, you
route it at the next level; and so on. It shouldn't matter whether
the routing at the top level is to that level's default partition or
not.

It seems that adding a new partition at the same level as the default
partition will require scanning it or its (leaf) partitions if
partitioned. Consider that p1, pd are partitions of a list-partitioned
table p accepting 1 and everything else, respectively, and pd is further
partitioned. When adding p2 of p for 2, we need to scan the partitions of
pd to check if there are any (2, ...) rows.

As for fixing the reported issue whereby the partitioned default
partition's non-existent file is being accessed, it would help to take a
look at the code in ATExecAttachPartition() starting at the following:

/*
* Set up to have the table be scanned to validate the partition
* constraint (see partConstraint above). If it's a partitioned table, we
* instead schedule its leaf partitions to be scanned.
*/
if (!skip_validate)
{

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#76Rahila Syed
rahilasyed90@gmail.com
In reply to: Amit Langote (#75)
Re: Adding support for Default partition in partitioning

It seems that adding a new partition at the same level as the default
partition will require scanning it or its (leaf) partitions if
partitioned. Consider that p1, pd are partitions of a list-partitioned
table p accepting 1 and everything else, respectively, and pd is further
partitioned. When adding p2 of p for 2, we need to scan the partitions of
pd to check if there are any (2, ...) rows.

This is a better explanation. May be following sentence was confusing,
"That is prohibit creation of new partition after a default partition which
is further partitioned"
Here, what I meant was default partition is partitioned further.

As for fixing the reported issue whereby the partitioned default
partition's non-existent file is being accessed, it would help to take a
look at the code in ATExecAttachPartition() starting at the following:

OK. I get it now. If attach partition already supports scanning all the
partitions before attach,
similar support should be provided in the case of adding a partition after
default partition as well.

Thank you,
Rahila Syed

On Wed, May 10, 2017 at 6:42 AM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp

Show quoted text

wrote:

On 2017/05/10 2:09, Robert Haas wrote:

On Tue, May 9, 2017 at 9:26 AM, Rahila Syed <rahilasyed90@gmail.com>

wrote:

Hi Rahila,

I am not able add a new partition if default partition is further
partitioned
with default partition.

Consider example below:

postgres=# CREATE TABLE test ( a int, b int, c int) PARTITION BY LIST

(a);

CREATE TABLE
postgres=# CREATE TABLE test_p1 PARTITION OF test FOR VALUES IN(4, 5,

6, 7,

8);
CREATE TABLE
postgres=# CREATE TABLE test_pd PARTITION OF test DEFAULT PARTITION BY
LIST(b);
CREATE TABLE
postgres=# CREATE TABLE test_pd_pd PARTITION OF test_pd DEFAULT;
CREATE TABLE
postgres=# INSERT INTO test VALUES (20, 24, 12);
INSERT 0 1
postgres=# CREATE TABLE test_p2 PARTITION OF test FOR VALUES IN(15);

ERROR: could not open file "base/12335/16420": No such file or

directory

Regarding fix for this I think we need to prohibit this case. That is
prohibit creation
of new partition after a default partition which is further partitioned.
Currently before adding a new partition after default partition all the

rows

of default
partition are scanned and if a row which matches the new partitions
constraint exists
the new partition is not added.

If we allow this for default partition which is partitioned further, we

will

have to scan
all the partitions of default partition for matching rows which can slow
down execution.

I think this case should be allowed

+1

and I don't think it should
require scanning all the partitions of the default partition. This is
no different than any other case where multiple levels of partitioning
are used. First, you route the tuple at the root level; then, you
route it at the next level; and so on. It shouldn't matter whether
the routing at the top level is to that level's default partition or
not.

It seems that adding a new partition at the same level as the default
partition will require scanning it or its (leaf) partitions if
partitioned. Consider that p1, pd are partitions of a list-partitioned
table p accepting 1 and everything else, respectively, and pd is further
partitioned. When adding p2 of p for 2, we need to scan the partitions of
pd to check if there are any (2, ...) rows.

As for fixing the reported issue whereby the partitioned default
partition's non-existent file is being accessed, it would help to take a
look at the code in ATExecAttachPartition() starting at the following:

/*
* Set up to have the table be scanned to validate the partition
* constraint (see partConstraint above). If it's a partitioned
table, we
* instead schedule its leaf partitions to be scanned.
*/
if (!skip_validate)
{

Thanks,
Amit

#77Sven R. Kunze
srkunze@mail.de
In reply to: Rahila Syed (#72)
Re: Adding support for Default partition in partitioning

On 09.05.2017 09:19, Rahila Syed wrote:

+1 for AS DEFAULT syntax if it helps in improving readability
specially in following case

CREATE TABLE p1 PARTITION OF test AS DEFAULT PARTITION BY LIST(a);

Thank you,
Rahila Syed

On Tue, May 9, 2017 at 1:13 AM, Robert Haas <robertmhaas@gmail.com
<mailto:robertmhaas@gmail.com>> wrote:

On Thu, May 4, 2017 at 4:40 PM, Sven R. Kunze <srkunze@mail.de
<mailto:srkunze@mail.de>> wrote:

It yields

CREATE TABLE p1 PARTITION OF test DEFAULT PARTITION BY LIST(b);

This reads to me like "DEFAULT PARTITION".

I can imagine a lot of confusion when those queries are

encountered in the

wild. I know this thread is about creating a default partition

but I were to

propose a minor change in the following direction, I think

confusion would

be greatly avoided:

CREATE TABLE p1 PARTITION OF test AS DEFAULT PARTITIONED BY LIST(b);

I know it's a bit longer but I think those 4 characters might serve
readability in the long term. It was especially confusing to see

PARTITION

in two positions serving two different functions.

Well, we certainly can't make that change just for default partitions.
I mean, that would be non-orthogonal, right? You can't say that the
way to subpartition is to write "PARTITION BY strategy" when the table
unpartitioned or is a non-default partition but "PARTITIONED BY
strategy" when it is a default partition. That would certainly not be
a good way of confusing users less, and would probably result in a
variety of special cases in places like ruleutils.c or pg_dump, plus
some weasel-wording in the documentation. We COULD do a general
change from "CREATE TABLE table_name PARTITION BY strategy" to "CREATE
TABLE table_name PARTITIONED BY strategy". I don't have any
particular arguments against that except that the current syntax is
more like Oracle, which might count for something, and maybe the fact
that we're a month after feature freeze. Still, if we want to change
that, now would be the time; but I favor leaving it alone.

You are definitely right. Changing it here would require to change it
everywhere AND thus to loose syntax parity with Oracle.

I am not in a position to judge this properly whether this would be a
huge problem. Personally, I don't have an issue with that. But don't
count me as most important opion on this.

So I guess I'm still in favor of the CREATE TABLE p1 PARTITION OF test
DEFAULT syntax, but if it ends up being AS DEFAULT instead, I can live
with that.

Is to make it optional an option?

Sven

#78Robert Haas
robertmhaas@gmail.com
In reply to: Sven R. Kunze (#77)
Re: Adding support for Default partition in partitioning

On Wed, May 10, 2017 at 10:59 AM, Sven R. Kunze <srkunze@mail.de> wrote:

You are definitely right. Changing it here would require to change it
everywhere AND thus to loose syntax parity with Oracle.

Right.

I am not in a position to judge this properly whether this would be a huge
problem. Personally, I don't have an issue with that. But don't count me as
most important opion on this.

Well, I don't think it would be a HUGE problem, but I think the fact
that Amit chose to implement this with syntax similar to that of
Oracle is probably not a coincidence, but rather a goal, and I think
the readability problem that you're worrying about is really pretty
minor. I think most people aren't going to subpartition their default
partition, and I think those who do will probably find the syntax
clear enough anyway. So I don't favor changing it. Now, if there's
an outcry of support for your position then I'll stand aside but I
don't anticipate that.

So I guess I'm still in favor of the CREATE TABLE p1 PARTITION OF test
DEFAULT syntax, but if it ends up being AS DEFAULT instead, I can live
with that.

Is to make it optional an option?

Optional keywords may not be the root of ALL evil, but they're pretty
evil. See my posting earlier on this same thread on this topic:

/messages/by-id/CA+TgmoZGHgd3vKZvyQ1Qx3e0L3n=voxY57mz9TTncVET-aLK2A@mail.gmail.com

The issues here are more or less the same.

--
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

#79Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Robert Haas (#78)
Re: Adding support for Default partition in partitioning

I'm surprised that there is so much activity in this thread. Is this
patch being considered for pg10?

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#80Robert Haas
robertmhaas@gmail.com
In reply to: Alvaro Herrera (#79)
Re: Adding support for Default partition in partitioning

On Wed, May 10, 2017 at 12:12 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

I'm surprised that there is so much activity in this thread. Is this
patch being considered for pg10?

Of course not. Feature freeze was a month ago.

--
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

#81Sven R. Kunze
srkunze@mail.de
In reply to: Robert Haas (#78)
Re: Adding support for Default partition in partitioning

On 10.05.2017 17:59, Robert Haas wrote:

Well, I don't think it would be a HUGE problem, but I think the fact
that Amit chose to implement this with syntax similar to that of
Oracle is probably not a coincidence, but rather a goal, and I think
the readability problem that you're worrying about is really pretty
minor. I think most people aren't going to subpartition their default
partition, and I think those who do will probably find the syntax
clear enough anyway.

I agree here.

Optional keywords may not be the root of ALL evil, but they're pretty
evil. See my posting earlier on this same thread on this topic:

/messages/by-id/CA+TgmoZGHgd3vKZvyQ1Qx3e0L3n=voxY57mz9TTncVET-aLK2A@mail.gmail.com

The issues here are more or less the same.

Ah, I see. I didn't draw the conclusion from the optionality of a
keyword the other day but after re-reading your post, it's exactly the
same issue.
Let's avoid optional keywords!

Sven

#82Rahila Syed
rahilasyed90@gmail.com
In reply to: Jeevan Ladhe (#63)
1 attachment(s)
Re: Adding support for Default partition in partitioning

Hello,

Please find attached an updated patch with review comments and bugs
reported till date implemented.

1.
In following block, we can just do with def_index, and we do not need

found_def

flag. We can check if def_index is -1 or not to decide if default partition

is

present.

found_def is used to set boundinfo->has_default which is used at couple
of other places to check if default partition exists. The implementation is
similar
to has_null.

3.
In following function isDefaultPartitionBound, first statement "return

false"

is not needed.

It is needed to return false if the node is not DefElem.

Todo:
Add regression tests
Documentation

Thank you,
Rahila Syed

On Fri, May 5, 2017 at 1:30 AM, Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
wrote:

Show quoted text

Hi Rahila,

I have started reviewing your latest patch, and here are my initial
comments:

1.
In following block, we can just do with def_index, and we do not need
found_def
flag. We can check if def_index is -1 or not to decide if default
partition is
present.

@@ -166,6 +172,8 @@ RelationBuildPartitionDesc(Relation rel)
/* List partitioning specific */
PartitionListValue **all_values = NULL;
bool found_null = false;
+ bool found_def = false;
+ int def_index = -1;
int null_index = -1;

2.
In check_new_partition_bound, in case of PARTITION_STRATEGY_LIST, remove
following duplicate declaration of boundinfo, because it is confusing and
after
your changes it is not needed as its not getting overridden in the if block
locally.
if (partdesc->nparts > 0)
{
PartitionBoundInfo boundinfo = partdesc->boundinfo;
ListCell *cell;

3.
In following function isDefaultPartitionBound, first statement "return
false"
is not needed.

+ * Returns true if the partition bound is default
+ */
+bool
+isDefaultPartitionBound(Node *value)
+{
+ if (IsA(value, DefElem))
+ {
+ DefElem *defvalue = (DefElem *) value;
+ if(!strcmp(defvalue->defname, "DEFAULT"))
+ return true;
+ return false;
+ }
+ return false;
+}

4.
As mentioned in my previous set of comments, following if block inside a
loop
in get_qual_for_default needs a break:

+ foreach(cell1, bspec->listdatums)
+ {
+ Node *value = lfirst(cell1);
+ if (isDefaultPartitionBound(value))
+ {
+ def_elem = true;
+ *defid  = inhrelid;
+ }
+ }

5.
In the grammar the rule default_part_list is not needed:

+default_partition:
+ DEFAULT  { $$ = (Node *)makeDefElem("DEFAULT", NULL, @1); }
+
+default_part_list:
+ default_partition { $$ = list_make1($1); }
+ ;
+

Instead you can simply declare default_partition as a list and write it as:

default_partition:
DEFAULT
{
Node *def = (Node *)makeDefElem("DEFAULT", NULL, @1);
$$ = list_make1(def);
}

6.
You need to change the output of the describe command, which is currently
as below: postgres=# \d+ test; Table "public.test" Column | Type |
Collation | Nullable | Default | Storage | Stats target | Description
--------+---------+-----------+----------+---------+---------+--------------+-------------
a | integer | | | | plain | | b | date | | | | plain | | Partition key:
LIST (a) Partitions: pd FOR VALUES IN (DEFAULT), test_p1 FOR VALUES IN (4,
5) What about changing the Paritions output as below: Partitions: *pd
DEFAULT,* test_p1 FOR VALUES IN (4, 5)

7.
You need to handle tab completion for DEFAULT.
e.g.
If I partially type following command:
CREATE TABLE pd PARTITION OF test DEFA
and then press tab, I get following completion:
CREATE TABLE pd PARTITION OF test FOR VALUES

I did some primary testing and did not find any problem so far.

I will review and test further and let you know my comments.

Regards,
Jeevan Ladhe

On Thu, May 4, 2017 at 6:09 PM, Rajkumar Raghuwanshi <
rajkumar.raghuwanshi@enterprisedb.com> wrote:

On Thu, May 4, 2017 at 5:14 PM, Rahila Syed <rahilasyed90@gmail.com>
wrote:

The syntax implemented in this patch is as follows,

CREATE TABLE p11 PARTITION OF p1 DEFAULT;

Applied v9 patches, table description still showing old pattern of

default partition. Is it expected?

create table lpd (a int, b int, c varchar) partition by list(a);
create table lpd_d partition of lpd DEFAULT;

\d+ lpd
Table "public.lpd"
Column | Type | Collation | Nullable | Default | Storage |
Stats target | Description
--------+-------------------+-----------+----------+--------
-+----------+--------------+-------------
a | integer | | | | plain
| |
b | integer | | | | plain
| |
c | character varying | | | | extended
| |
Partition key: LIST (a)
Partitions: lpd_d FOR VALUES IN (DEFAULT)

Attachments:

default_partition_v10.patchapplication/x-download; name=default_partition_v10.patchDownload
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index e0d2665..7d5a49b 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -34,6 +34,7 @@
 #include "nodes/nodeFuncs.h"
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
+#include "optimizer/prep.h"
 #include "optimizer/planmain.h"
 #include "optimizer/var.h"
 #include "rewrite/rewriteManip.h"
@@ -90,6 +91,10 @@ typedef struct PartitionBoundInfoData
 								 * for range partitioned tables */
 	int			null_index;		/* Index of the null-accepting partition; -1
 								 * for range partitioned tables */
+	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 */
 } PartitionBoundInfoData;
 
 /*
@@ -118,7 +123,8 @@ static int32 qsort_partition_list_value_cmp(const void *a, const void *b,
 static int32 qsort_partition_rbound_cmp(const void *a, const void *b,
 						   void *arg);
 
-static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec,
+								bool is_def, List *boundspecs);
 static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
 static Oid get_partition_operator(PartitionKey key, int col,
 					   StrategyNumber strategy, bool *need_relabel);
@@ -166,6 +172,8 @@ RelationBuildPartitionDesc(Relation rel)
 	/* List partitioning specific */
 	PartitionListValue **all_values = NULL;
 	bool		found_null = false;
+	bool		found_def = false;
+	int			def_index = -1;
 	int			null_index = -1;
 
 	/* Range partitioning specific */
@@ -249,9 +257,16 @@ RelationBuildPartitionDesc(Relation rel)
 
 				foreach(c, spec->listdatums)
 				{
+					Node *value = lfirst(c);
 					Const	   *val = lfirst(c);
 					PartitionListValue *list_value = NULL;
 
+					if (isDefaultPartitionBound(value))
+					{
+						found_def = true;
+						def_index = i;
+						continue;
+					}
 					if (!val->constisnull)
 					{
 						list_value = (PartitionListValue *)
@@ -459,6 +474,7 @@ RelationBuildPartitionDesc(Relation rel)
 			case PARTITION_STRATEGY_LIST:
 				{
 					boundinfo->has_null = found_null;
+					boundinfo->has_default = found_def;
 					boundinfo->indexes = (int *) palloc(ndatums * sizeof(int));
 
 					/*
@@ -496,6 +512,8 @@ RelationBuildPartitionDesc(Relation rel)
 						if (mapping[null_index] == -1)
 							mapping[null_index] = next_index++;
 					}
+					if (found_def && mapping[def_index] == -1)
+						mapping[def_index] = next_index++;
 
 					/* All partition must now have a valid mapping */
 					Assert(next_index == nparts);
@@ -504,6 +522,11 @@ RelationBuildPartitionDesc(Relation rel)
 						boundinfo->null_index = mapping[null_index];
 					else
 						boundinfo->null_index = -1;
+
+					if (found_def)
+						boundinfo->default_index = mapping[def_index];
+					else
+						boundinfo->default_index = -1;
 					break;
 				}
 
@@ -513,6 +536,7 @@ RelationBuildPartitionDesc(Relation rel)
 												sizeof(RangeDatumContent *));
 					boundinfo->indexes = (int *) palloc((ndatums + 1) *
 														sizeof(int));
+					boundinfo->has_default = found_def;
 
 					for (i = 0; i < ndatums; i++)
 					{
@@ -671,6 +695,7 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
 	PartitionKey key = RelationGetPartitionKey(parent);
 	PartitionDesc partdesc = RelationGetPartitionDesc(parent);
+	PartitionBoundInfo boundinfo;
 	ParseState *pstate = make_parsestate(NULL);
 	int			with = -1;
 	bool		overlap = false;
@@ -679,22 +704,25 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 	{
 		case PARTITION_STRATEGY_LIST:
 			{
+				boundinfo = partdesc->boundinfo;
+
 				Assert(spec->strategy == PARTITION_STRATEGY_LIST);
 
 				if (partdesc->nparts > 0)
 				{
-					PartitionBoundInfo boundinfo = partdesc->boundinfo;
 					ListCell   *cell;
 
 					Assert(boundinfo &&
 						   boundinfo->strategy == PARTITION_STRATEGY_LIST &&
-						   (boundinfo->ndatums > 0 || boundinfo->has_null));
+						   (boundinfo->ndatums > 0 || boundinfo->has_null
+							|| boundinfo->has_default));
 
 					foreach(cell, spec->listdatums)
 					{
-						Const	   *val = lfirst(cell);
+						Node	   *value = lfirst(cell);
+						Const	   *val = (Const *) value;
 
-						if (!val->constisnull)
+						if (!val->constisnull && !(isDefaultPartitionBound(value)))
 						{
 							int			offset;
 							bool		equal;
@@ -709,7 +737,14 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 								break;
 							}
 						}
-						else if (boundinfo->has_null)
+						else if (isDefaultPartitionBound(value) &&
+								 boundinfo->has_default)
+						{
+							overlap = true;
+							with = boundinfo->default_index;
+							break;
+						}
+						else if (val->constisnull && boundinfo->has_null)
 						{
 							overlap = true;
 							with = boundinfo->null_index;
@@ -836,6 +871,114 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 						relname, get_rel_name(partdesc->oids[with])),
 				 parser_errposition(pstate, spec->location)));
 	}
+
+	/*
+	 * 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.
+	 */
+	if (spec->strategy == PARTITION_STRATEGY_LIST && partdesc->nparts > 0
+		&& boundinfo->has_default)
+	{
+		List	       *partConstraint = NIL;
+		ExprContext    *econtext;
+		EState	       *estate;
+		Relation	    defrel;
+		HeapScanDesc    scan;
+		HeapTuple	    tuple;
+		ExprState	   *partqualstate = NULL;
+		Snapshot	    snapshot;
+		Oid			    defid;
+		MemoryContext   oldCxt;
+		TupleTableSlot *tupslot;
+		TupleDesc	    tupdesc;
+		List           *all_parts;
+		ListCell       *lc;
+
+
+		partConstraint = generate_qual_for_defaultpart(parent, bound, &defid);
+		partConstraint = (List *) eval_const_expressions(NULL,
+													(Node *) partConstraint);
+		partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
+		partConstraint = list_make1(make_ands_explicit(partConstraint));
+
+		/*
+		 * Generate the constraint and default execution states
+		 */
+		defrel = heap_open(defid, AccessExclusiveLock);
+
+		if (defrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			all_parts = find_all_inheritors(RelationGetRelid(defrel),
+											AccessExclusiveLock, NULL);
+		else
+			all_parts = list_make1_oid(RelationGetRelid(defrel));
+
+		foreach(lc, all_parts)
+		{
+			Oid			part_relid = lfirst_oid(lc);
+			Relation	part_rel;
+			Expr	   *constr;
+			Expr	   *partition_constraint;
+
+			if (part_relid != RelationGetRelid(defrel))
+				part_rel = heap_open(part_relid, NoLock);
+			else
+				continue;
+
+			/*
+			 * Skip if it's a partitioned table.  Only RELKIND_RELATION
+			 * relations (ie, leaf partitions) need to be scanned.
+			 */
+			if (part_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			{
+				heap_close(part_rel, NoLock);
+				continue;
+			}
+			tupdesc = CreateTupleDescCopy(RelationGetDescr(part_rel));
+			constr = linitial(partConstraint);
+			partition_constraint = (Expr *)
+				map_partition_varattnos((List *) constr, 1,
+										part_rel, parent);
+			estate = CreateExecutorState();
+
+			/* Build expression execution states for partition check quals */
+			partqualstate = ExecPrepareExpr(partition_constraint,
+							estate);
+
+			econtext = GetPerTupleExprContext(estate);
+			snapshot = RegisterSnapshot(GetLatestSnapshot());
+			scan = heap_beginscan(part_rel, snapshot, 0, NULL);
+			tupslot = MakeSingleTupleTableSlot(tupdesc);
+
+			/*
+			 * Switch to per-tuple memory context and reset it for each tuple
+			 * produced, so we don't leak memory.
+			 */
+			oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+			while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+			{
+				ExecStoreTuple(tuple, tupslot, InvalidBuffer, false);
+				econtext->ecxt_scantuple = tupslot;
+				if (partqualstate && !ExecCheck(partqualstate, econtext))
+					ereport(ERROR,
+							(errcode(ERRCODE_CHECK_VIOLATION),
+					errmsg("new default partition constraint is violated by some row")));
+				ResetExprContext(econtext);
+				CHECK_FOR_INTERRUPTS();
+			}
+			CacheInvalidateRelcache(part_rel);
+			MemoryContextSwitchTo(oldCxt);
+			heap_endscan(scan);
+			UnregisterSnapshot(snapshot);
+			ExecDropSingleTupleTableSlot(tupslot);
+			FreeExecutorState(estate);
+			heap_close(part_rel, NoLock);
+		}
+		CacheInvalidateRelcache(defrel);
+		heap_close(defrel, AccessExclusiveLock);
+	}
 }
 
 /*
@@ -884,6 +1027,117 @@ get_partition_parent(Oid relid)
 }
 
 /*
+ * Returns true if the partition bound is default
+ */
+bool
+isDefaultPartitionBound(Node *value)
+{
+	if (IsA(value, DefElem))
+	{
+		DefElem *defvalue = (DefElem *) value;
+		if(!strcmp(defvalue->defname, "DEFAULT"))
+			return true;
+		return false;
+	}
+	return false;
+}
+
+/*
+ * Return the bound spec list to be used
+ * in partition constraint of default partition
+ */
+List *
+get_qual_for_default(Relation parent, Oid *defid)
+{
+	List	   *inhoids;
+	ListCell   *cell;
+	List	   *boundspecs = 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 *cell1;
+
+		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));
+		foreach(cell1, bspec->listdatums)
+		{
+			Node *value = lfirst(cell1);
+			if (isDefaultPartitionBound(value))
+			{
+				def_elem = true;
+				*defid  = inhrelid;
+				break;
+			}
+		}
+		if (!def_elem)
+		{
+			foreach(cell1, bspec->listdatums)
+			{
+				Node *value = lfirst(cell1);
+				boundspecs = lappend(boundspecs, value);
+			}
+		}
+		ReleaseSysCache(tuple);
+	}
+	return boundspecs;
+}
+
+/*
+ * Return a list of executable expressions as new partition constraint
+ * for default partition while adding a new partition after default
+ */
+List *
+generate_qual_for_defaultpart(Relation parent, Node *bound, Oid * defid)
+{
+	PartitionKey key = RelationGetPartitionKey(parent);
+	PartitionBoundSpec *spec;
+	List	   *partConstraint = NIL;
+	ListCell   *cell;
+	List       *boundspecs = NIL;
+	List       *bound_datums;
+
+	spec = (PartitionBoundSpec *) bound;
+	bound_datums = list_copy(spec->listdatums);
+
+	boundspecs = get_qual_for_default(parent, defid);
+
+	foreach(cell, bound_datums)
+	{
+		Node *value = lfirst(cell);
+		boundspecs = lappend(boundspecs, value);
+	}
+	partConstraint = get_qual_for_list(key, spec, true, boundspecs);
+	return partConstraint;
+}
+
+/*
  * get_qual_from_partbound
  *		Given a parser node for partition bound, return the list of executable
  *		expressions as partition constraint
@@ -894,6 +1148,10 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
 	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
 	PartitionKey key = RelationGetPartitionKey(parent);
 	List	   *my_qual = NIL;
+	bool		is_def = false;
+	Oid        defid;
+	ListCell   *cell;
+	List	   *boundspecs = NIL;
 
 	Assert(key != NULL);
 
@@ -901,9 +1159,16 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
 	{
 		case PARTITION_STRATEGY_LIST:
 			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
-			my_qual = get_qual_for_list(key, spec);
+			foreach(cell, spec->listdatums)
+			{
+				Node *value = lfirst(cell);
+				if (isDefaultPartitionBound(value))
+					is_def = true;
+			}
+			if (is_def)
+				boundspecs = get_qual_for_default(parent, &defid);
+			my_qual = get_qual_for_list(key, spec, is_def, boundspecs);
 			break;
-
 		case PARTITION_STRATEGY_RANGE:
 			Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
 			my_qual = get_qual_for_range(key, spec);
@@ -1151,7 +1416,8 @@ RelationGetPartitionDispatchInfo(Relation rel, int lockmode,
  * Returns a list of expressions to use as a list partition's constraint.
  */
 static List *
-get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
+get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec, bool is_def,
+					List *boundspecs)
 {
 	List	   *result;
 	ArrayExpr  *arr;
@@ -1177,12 +1443,14 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 	else
 		keyCol = (Expr *) copyObject(linitial(key->partexprs));
 
+	if (!is_def)
+		boundspecs = spec->listdatums;
 	/*
 	 * We must remove any NULL value in the list; we handle it separately
 	 * below.
 	 */
 	prev = NULL;
-	for (cell = list_head(spec->listdatums); cell; cell = next)
+	for (cell = list_head(boundspecs); cell; cell = next)
 	{
 		Const	   *val = (Const *) lfirst(cell);
 
@@ -1191,14 +1459,14 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 		if (val->constisnull)
 		{
 			list_has_null = true;
-			spec->listdatums = list_delete_cell(spec->listdatums,
+			boundspecs = list_delete_cell(boundspecs,
 												cell, prev);
 		}
 		else
 			prev = cell;
 	}
 
-	if (!list_has_null)
+	if ((is_def && list_has_null) || (!is_def && !list_has_null))
 	{
 		/*
 		 * Gin up a col IS NOT NULL test that will be AND'd with other
@@ -1210,7 +1478,7 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 		nulltest1->argisrow = false;
 		nulltest1->location = -1;
 	}
-	else
+	else if(!is_def && list_has_null)
 	{
 		/*
 		 * Gin up a col IS NULL test that will be OR'd with other expressions
@@ -1229,7 +1497,7 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 		: key->parttypid[0];
 	arr->array_collid = key->parttypcoll[0];
 	arr->element_typeid = key->parttypid[0];
-	arr->elements = spec->listdatums;
+	arr->elements = boundspecs;
 	arr->multidims = false;
 	arr->location = -1;
 
@@ -1243,15 +1511,34 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 										  key->partcollation[0],
 										  COERCE_EXPLICIT_CAST);
 
+	/* Build leftop <> ALL(rightop). This is for default partition */
+	if (is_def)
+	{
+		/* Find the negator for the partition operator above */
+		if(get_negator(operoid) == InvalidOid)
+			ereport(ERROR, (errcode(ERRCODE_CHECK_VIOLATION),
+			errmsg("DEFAULT partition cannot be used without negator of operator  %s",
+						get_opname(operoid))));
+		operoid = get_negator(operoid);
+		opexpr = makeNode(ScalarArrayOpExpr);
+		opexpr->opno = operoid;
+		opexpr->opfuncid = get_opcode(operoid);
+		opexpr->useOr = false;
+		opexpr->inputcollid = key->partcollation[0];
+		opexpr->args = list_make2(keyCol, arr);
+		opexpr->location = -1;
+	}
 	/* Build leftop = ANY (rightop) */
-	opexpr = makeNode(ScalarArrayOpExpr);
-	opexpr->opno = operoid;
-	opexpr->opfuncid = get_opcode(operoid);
-	opexpr->useOr = true;
-	opexpr->inputcollid = key->partcollation[0];
-	opexpr->args = list_make2(keyCol, arr);
-	opexpr->location = -1;
-
+	else
+	{
+		opexpr = makeNode(ScalarArrayOpExpr);
+		opexpr->opno = operoid;
+		opexpr->opfuncid = get_opcode(operoid);
+		opexpr->useOr = true;
+		opexpr->inputcollid = key->partcollation[0];
+		opexpr->args = list_make2(keyCol, arr);
+		opexpr->location = -1;
+	}
 	if (nulltest1)
 		result = list_make2(nulltest1, opexpr);
 	else if (nulltest2)
@@ -1775,10 +2062,25 @@ get_partition_for_tuple(PartitionDispatch *pd,
 		 */
 		if (cur_index < 0)
 		{
-			result = -1;
-			*failed_at = parent;
-			*failed_slot = slot;
-			break;
+			/*
+			 * If partitioned table has a default partition, return
+			 * its sequence number
+			 */
+			if (partdesc->boundinfo->has_default)
+			{
+				result = parent->indexes[partdesc->boundinfo->default_index];
+				if (result >= 0)
+					break;
+				else
+					parent = pd[-parent->indexes[partdesc->boundinfo->default_index]];
+			}
+			else
+			{
+				result = -1;
+				*failed_at = parent;
+				*failed_slot = slot;
+				break;
+			}
 		}
 		else if (parent->indexes[cur_index] >= 0)
 		{
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 818d2c2..d471583 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -579,6 +579,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <node>		ForValues
 %type <node>		partbound_datum
 %type <list>		partbound_datum_list
+%type <list>		default_partition
 %type <partrange_datum>	PartitionRangeDatum
 %type <list>		range_datum_list
 
@@ -2669,6 +2670,17 @@ ForValues:
 
 					$$ = (Node *) n;
 				}
+			| default_partition
+				{
+					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+					n->strategy = PARTITION_DEFAULT;
+					n->listdatums = $1;
+					n->location = @1;
+
+					$$ = (Node *) n;
+				}
+
 		;
 
 partbound_datum:
@@ -2683,6 +2695,12 @@ partbound_datum_list:
 												{ $$ = lappend($1, $3); }
 		;
 
+default_partition:
+			DEFAULT	{
+						Node *def = (Node *)makeDefElem("DEFAULT", NULL, @1);
+						$$ = list_make1(def);
+					}
+
 range_datum_list:
 			PartitionRangeDatum					{ $$ = list_make1($1); }
 			| range_datum_list ',' PartitionRangeDatum
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e187409..13295a3 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -3298,55 +3299,67 @@ transformPartitionBound(ParseState *pstate, Relation parent, Node *bound)
 										 false, false);
 
 		if (spec->strategy != PARTITION_STRATEGY_LIST)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
-				  errmsg("invalid bound specification for a list partition"),
+		{
+			/*
+			 * If the partition is the default partition switch
+			 * back to PARTITION_STRATEGY_LIST
+			 */
+			if (spec->strategy == PARTITION_DEFAULT)
+				result_spec->strategy = PARTITION_STRATEGY_LIST;
+			else
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("invalid bound specification for a list partition"),
 					 parser_errposition(pstate, exprLocation(bound))));
+		}
 
 		result_spec->listdatums = NIL;
 		foreach(cell, spec->listdatums)
 		{
-			A_Const    *con = (A_Const *) lfirst(cell);
-			Node	   *value;
-			ListCell   *cell2;
-			bool		duplicate;
-
-			value = (Node *) make_const(pstate, &con->val, con->location);
-			value = coerce_to_target_type(pstate,
-										  value, exprType(value),
-										  get_partition_col_typid(key, 0),
-										  get_partition_col_typmod(key, 0),
-										  COERCION_ASSIGNMENT,
-										  COERCE_IMPLICIT_CAST,
-										  -1);
-
-			if (value == NULL)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
-							 format_type_be(get_partition_col_typid(key, 0)),
-								colname),
-						 parser_errposition(pstate,
-											exprLocation((Node *) con))));
-
-			/* Simplify the expression */
-			value = (Node *) expression_planner((Expr *) value);
-
-			/* Don't add to the result if the value is a duplicate */
-			duplicate = false;
-			foreach(cell2, result_spec->listdatums)
+			Node *value = lfirst(cell);
+			/* Perform the transformation only for non default partition */
+			if (!(isDefaultPartitionBound(value)))
 			{
-				Const	   *value2 = (Const *) lfirst(cell2);
+				A_Const    *con = (A_Const *) lfirst(cell);
+				ListCell   *cell2;
+				bool		duplicate;
 
-				if (equal(value, value2))
+				value = (Node *) make_const(pstate, &con->val, con->location);
+				value = coerce_to_target_type(pstate,
+											value, exprType(value),
+											get_partition_col_typid(key, 0),
+											get_partition_col_typmod(key, 0),
+											COERCION_ASSIGNMENT,
+											COERCE_IMPLICIT_CAST,
+											-1);
+
+				if (value == NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+								format_type_be(get_partition_col_typid(key, 0)),
+									colname),
+							parser_errposition(pstate,
+												exprLocation((Node *) con))));
+
+				/* Simplify the expression */
+				value = (Node *) expression_planner((Expr *) value);
+
+				/* Don't add to the result if the value is a duplicate */
+				duplicate = false;
+				foreach(cell2, result_spec->listdatums)
 				{
-					duplicate = true;
-					break;
+					Const	   *value2 = (Const *) lfirst(cell2);
+
+					if (equal(value, value2))
+					{
+						duplicate = true;
+						break;
+					}
 				}
+				if (duplicate)
+					continue;
 			}
-			if (duplicate)
-				continue;
-
 			result_spec->listdatums = lappend(result_spec->listdatums,
 											  value);
 		}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index cbde1ff..c3c9e70 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -24,6 +24,7 @@
 #include "access/sysattr.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
+#include "catalog/partition.h"
 #include "catalog/pg_aggregate.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_authid.h"
@@ -8616,25 +8617,39 @@ get_rule_expr(Node *node, deparse_context *context,
 				PartitionBoundSpec *spec = (PartitionBoundSpec *) node;
 				ListCell   *cell;
 				char	   *sep;
+				bool       is_def = false;
 
 				switch (spec->strategy)
 				{
 					case PARTITION_STRATEGY_LIST:
 						Assert(spec->listdatums != NIL);
-
-						appendStringInfoString(buf, "FOR VALUES");
-						appendStringInfoString(buf, " IN (");
-						sep = "";
 						foreach(cell, spec->listdatums)
 						{
-							Const	   *val = lfirst(cell);
+							Node *value = lfirst(cell);
 
-							appendStringInfoString(buf, sep);
-							get_const_expr(val, context, -1);
-							sep = ", ";
+							if (isDefaultPartitionBound(value))
+							{
+								appendStringInfoString(buf, "DEFAULT");
+								is_def = true;
+								break;
+							}
 						}
+						if(!is_def)
+						{
+							appendStringInfoString(buf, "FOR VALUES");
+							appendStringInfoString(buf, " IN (");
+							sep = "";
+							foreach(cell, spec->listdatums)
+							{
+								Const	   *val = lfirst(cell);
 
-						appendStringInfoString(buf, ")");
+								appendStringInfoString(buf, sep);
+								get_const_expr(val, context, -1);
+								sep = ", ";
+							}
+
+							appendStringInfoString(buf, ")");
+						}
 						break;
 
 					case PARTITION_STRATEGY_RANGE:
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index e2a3351..477d63c 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2004,7 +2004,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("ATTACH", "PARTITION", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 	else if (TailMatches2("FOR", "VALUES"))
 		COMPLETE_WITH_LIST2("FROM (", "IN (");
 	/*
@@ -2401,7 +2401,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("PARTITION", "OF", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 
 /* CREATE TABLESPACE */
 	else if (Matches3("CREATE", "TABLESPACE", MatchAny))
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 421644c..7cb4f0a 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -74,9 +74,12 @@ extern void RelationBuildPartitionDesc(Relation relation);
 extern bool partition_bounds_equal(PartitionKey key,
 					   PartitionBoundInfo p1, PartitionBoundInfo p2);
 
+extern bool isDefaultPartitionBound(Node *value);
 extern void check_new_partition_bound(char *relname, Relation parent, Node *bound);
 extern Oid	get_partition_parent(Oid relid);
 extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
+extern List *generate_qual_for_defaultpart(Relation parent, Node *bound, Oid *defid);
+extern List *get_qual_for_default(Relation parent, Oid *defid);
 extern List *map_partition_varattnos(List *expr, int target_varno,
 						Relation partrel, Relation parent);
 extern List *RelationGetPartitionQual(Relation rel);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index e1d454a..5853c70 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -780,6 +780,7 @@ typedef struct PartitionSpec
 
 #define PARTITION_STRATEGY_LIST		'l'
 #define PARTITION_STRATEGY_RANGE	'r'
+#define PARTITION_DEFAULT	'd'
 
 /*
  * PartitionBoundSpec - a partition bound specification
#83Robert Haas
robertmhaas@gmail.com
In reply to: Rahila Syed (#82)
Re: Adding support for Default partition in partitioning

On Thu, May 11, 2017 at 10:07 AM, Rahila Syed <rahilasyed90@gmail.com> wrote:

Please find attached an updated patch with review comments and bugs reported
till date implemented.

You haven't done anything about the repeated suggestion that this
should also cover range partitioning.

+            /*
+             * If the partition is the default partition switch
+             * back to PARTITION_STRATEGY_LIST
+             */
+            if (spec->strategy == PARTITION_DEFAULT)
+                result_spec->strategy = PARTITION_STRATEGY_LIST;
+            else
+                ereport(ERROR,
+                        (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+                     errmsg("invalid bound specification for a list
partition"),
                      parser_errposition(pstate, exprLocation(bound))));

This is incredibly ugly. I don't know exactly what should be done
about it, but I think PARTITION_DEFAULT is a bad idea and has got to
go. Maybe add a separate isDefault flag to PartitionBoundSpec.

+            /*
+             * Skip if it's a partitioned table.  Only RELKIND_RELATION
+             * relations (ie, leaf partitions) need to be scanned.
+             */
+            if (part_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)

What about foreign table partitions?

Doesn't it strike you as a bit strange that get_qual_for_default()
doesn't return a qual? Functions should generally have names that
describe what they do.

+    bound_datums = list_copy(spec->listdatums);
+
+    boundspecs = get_qual_for_default(parent, defid);
+
+    foreach(cell, bound_datums)
+    {
+        Node *value = lfirst(cell);
+        boundspecs = lappend(boundspecs, value);
+    }

There's an existing function that you can use to concatenate two lists
instead of open-coding it.

Also, I think that before you ask anyone to spend too much more time
and energy reviewing this, you should really add the documentation and
regression tests which you mentioned as a TODO. And run the code
through pgindent.

--
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

#84Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Rahila Syed (#82)
Re: Adding support for Default partition in partitioning

Hi Rahila,

On Thu, May 11, 2017 at 7:37 PM, Rahila Syed <rahilasyed90@gmail.com> wrote:

3.
In following function isDefaultPartitionBound, first statement "return

false"

is not needed.

It is needed to return false if the node is not DefElem.

Please have a look at following code:

+ * Returns true if the partition bound is default
+ */
+bool
+isDefaultPartitionBound(Node *value)
+{
+ if (IsA(value, DefElem))
+ {
+ DefElem defvalue = (DefElem ) value;
+ if(!strcmp(defvalue->defname, "DEFAULT"))
+ return true;
+ return false;
+ }
+ return false;
+}

By first return false, I mean to say the return statement inside the
if block "if (IsA(value, DefElem))":

+ if(!strcmp(defvalue->defname, "DEFAULT"))
+ return true;
+ return false;

Even if this "return false" is not present, the control is anyway going to
fall through and will return false from the outermost return statement.

I leave this decision to you, but further this block could be rewritten as
below and also can be defined as a macro:

bool
isDefaultPartitionBound(Node *value)
{
return (IsA(value, DefElem) &&
!strcmp(((DefElem) value)->defname, "DEFAULT"));
}

Regards,
Jeevan Ladhe

#85Beena Emerson
memissemerson@gmail.com
In reply to: Rahila Syed (#82)
Re: Adding support for Default partition in partitioning

On Thu, May 11, 2017 at 7:37 PM, Rahila Syed <rahilasyed90@gmail.com> wrote:

Hello,

Please find attached an updated patch with review comments and bugs
reported till date implemented.

Hello Rahila,

Tested on "efa2c18 Doc fix: scale(numeric) returns integer, not numeric."

(1) With the new patch, we allow new partitions when there is overlapping
data with default partition. The entries in default are ignored when
running queries satisfying the new partition.

DROP TABLE list1;
CREATE TABLE list1 (
a int,
b int
) PARTITION BY LIST (a);
CREATE TABLE list1_1 (LIKE list1);
ALTER TABLE list1 ATTACH PARTITION list1_1 FOR VALUES IN (1);
CREATE TABLE list1_def PARTITION OF list1 DEFAULT;
INSERT INTO list1 SELECT generate_series(1,2),1;
-- Partition overlapping with DEF
CREATE TABLE list1_2 PARTITION OF list1 FOR VALUES IN (2);
INSERT INTO list1 SELECT generate_series(2,3),2;

postgres=# SELECT * FROM list1 ORDER BY a,b;
a | b
---+---
1 | 1
2 | 1
2 | 2
3 | 2
(4 rows)

postgres=# SELECT * FROM list1 WHERE a=2;
a | b
---+---
2 | 2
(1 row)

This ignores the a=2 entries in the DEFAULT.

postgres=# SELECT * FROM list1_def;
a | b
---+---
2 | 1
3 | 2
(2 rows)

(2) I get the following warning:

partition.c: In function ‘check_new_partition_bound’:
partition.c:882:15: warning: ‘boundinfo’ may be used uninitialized in this
function [-Wmaybe-uninitialized]
&& boundinfo->has_default)
^
preproc.y:3250.2-8: warning: type clash on default action: <str> != <>

Show quoted text

1.
In following block, we can just do with def_index, and we do not need

found_def

flag. We can check if def_index is -1 or not to decide if default

partition is

present.

found_def is used to set boundinfo->has_default which is used at couple
of other places to check if default partition exists. The implementation
is similar
to has_null.

3.
In following function isDefaultPartitionBound, first statement "return

false"

is not needed.

It is needed to return false if the node is not DefElem.

Todo:
Add regression tests
Documentation

Thank you,
Rahila Syed

#86Rahila Syed
rahilasyed90@gmail.com
In reply to: Beena Emerson (#85)
1 attachment(s)
Re: Adding support for Default partition in partitioning

Hello,

(1) With the new patch, we allow new partitions when there is overlapping

data with default partition. The entries in default are ignored when
running queries satisfying the new partition.
This was introduced in latest version. We are not allowing adding a
partition when entries with same key value exist in default partition. So
this scenario should not
come in picture. Please find attached an updated patch which corrects this.

(2) I get the following warning:

partition.c: In function ‘check_new_partition_bound’:
partition.c:882:15: warning: ‘boundinfo’ may be used uninitialized in this

function [-Wmaybe-uninitialized]

&& boundinfo->has_default)

^

preproc.y:3250.2-8: warning: type clash on default action: <str> != <>

I failed to notice this warning. I will look into it.

This is incredibly ugly. I don't know exactly what should be done
about it, but I think PARTITION_DEFAULT is a bad idea and has got to
go. Maybe add a separate isDefault flag to PartitionBoundSpec

Will look at other ways to do it.

Doesn't it strike you as a bit strange that get_qual_for_default()
doesn't return a qual? Functions should generally have names that
describe what they do.

Will fix this.

There's an existing function that you can use to concatenate two lists
instead of open-coding it.

Will check this.

you should really add the documentation and
regression tests which you mentioned as a TODO. And run the code
through pgindent

I will also update the next version with documentation and regression tests
and run pgindent

Thank you,
Rahila Syed

On Fri, May 12, 2017 at 4:33 PM, Beena Emerson <memissemerson@gmail.com>
wrote:

Show quoted text

On Thu, May 11, 2017 at 7:37 PM, Rahila Syed <rahilasyed90@gmail.com>
wrote:

Hello,

Please find attached an updated patch with review comments and bugs
reported till date implemented.

Hello Rahila,

Tested on "efa2c18 Doc fix: scale(numeric) returns integer, not numeric."

(1) With the new patch, we allow new partitions when there is overlapping
data with default partition. The entries in default are ignored when
running queries satisfying the new partition.

DROP TABLE list1;
CREATE TABLE list1 (
a int,
b int
) PARTITION BY LIST (a);
CREATE TABLE list1_1 (LIKE list1);
ALTER TABLE list1 ATTACH PARTITION list1_1 FOR VALUES IN (1);
CREATE TABLE list1_def PARTITION OF list1 DEFAULT;
INSERT INTO list1 SELECT generate_series(1,2),1;
-- Partition overlapping with DEF
CREATE TABLE list1_2 PARTITION OF list1 FOR VALUES IN (2);
INSERT INTO list1 SELECT generate_series(2,3),2;

postgres=# SELECT * FROM list1 ORDER BY a,b;
a | b
---+---
1 | 1
2 | 1
2 | 2
3 | 2
(4 rows)

postgres=# SELECT * FROM list1 WHERE a=2;
a | b
---+---
2 | 2
(1 row)

This ignores the a=2 entries in the DEFAULT.

postgres=# SELECT * FROM list1_def;
a | b
---+---
2 | 1
3 | 2
(2 rows)

(2) I get the following warning:

partition.c: In function ‘check_new_partition_bound’:
partition.c:882:15: warning: ‘boundinfo’ may be used uninitialized in this
function [-Wmaybe-uninitialized]
&& boundinfo->has_default)
^
preproc.y:3250.2-8: warning: type clash on default action: <str> != <>

1.
In following block, we can just do with def_index, and we do not need

found_def

flag. We can check if def_index is -1 or not to decide if default

partition is

present.

found_def is used to set boundinfo->has_default which is used at couple
of other places to check if default partition exists. The implementation
is similar
to has_null.

3.
In following function isDefaultPartitionBound, first statement "return

false"

is not needed.

It is needed to return false if the node is not DefElem.

Todo:
Add regression tests
Documentation

Thank you,
Rahila Syed

Attachments:

default_partition_v11.patchapplication/x-download; name=default_partition_v11.patchDownload
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 8641ae1..1f1ed9f 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -34,6 +34,7 @@
 #include "nodes/nodeFuncs.h"
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
+#include "optimizer/prep.h"
 #include "optimizer/planmain.h"
 #include "optimizer/var.h"
 #include "rewrite/rewriteManip.h"
@@ -90,6 +91,10 @@ typedef struct PartitionBoundInfoData
 								 * for range partitioned tables */
 	int			null_index;		/* Index of the null-accepting partition; -1
 								 * for range partitioned tables */
+	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 */
 } PartitionBoundInfoData;
 
 /*
@@ -118,7 +123,8 @@ static int32 qsort_partition_list_value_cmp(const void *a, const void *b,
 static int32 qsort_partition_rbound_cmp(const void *a, const void *b,
 						   void *arg);
 
-static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec,
+								bool is_def, List *boundspecs);
 static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
 static Oid get_partition_operator(PartitionKey key, int col,
 					   StrategyNumber strategy, bool *need_relabel);
@@ -166,6 +172,8 @@ RelationBuildPartitionDesc(Relation rel)
 	/* List partitioning specific */
 	PartitionListValue **all_values = NULL;
 	bool		found_null = false;
+	bool		found_def = false;
+	int			def_index = -1;
 	int			null_index = -1;
 
 	/* Range partitioning specific */
@@ -249,9 +257,16 @@ RelationBuildPartitionDesc(Relation rel)
 
 				foreach(c, spec->listdatums)
 				{
+					Node *value = lfirst(c);
 					Const	   *val = lfirst(c);
 					PartitionListValue *list_value = NULL;
 
+					if (isDefaultPartitionBound(value))
+					{
+						found_def = true;
+						def_index = i;
+						continue;
+					}
 					if (!val->constisnull)
 					{
 						list_value = (PartitionListValue *)
@@ -459,6 +474,7 @@ RelationBuildPartitionDesc(Relation rel)
 			case PARTITION_STRATEGY_LIST:
 				{
 					boundinfo->has_null = found_null;
+					boundinfo->has_default = found_def;
 					boundinfo->indexes = (int *) palloc(ndatums * sizeof(int));
 
 					/*
@@ -496,6 +512,8 @@ RelationBuildPartitionDesc(Relation rel)
 						if (mapping[null_index] == -1)
 							mapping[null_index] = next_index++;
 					}
+					if (found_def && mapping[def_index] == -1)
+						mapping[def_index] = next_index++;
 
 					/* All partition must now have a valid mapping */
 					Assert(next_index == nparts);
@@ -504,6 +522,11 @@ RelationBuildPartitionDesc(Relation rel)
 						boundinfo->null_index = mapping[null_index];
 					else
 						boundinfo->null_index = -1;
+
+					if (found_def)
+						boundinfo->default_index = mapping[def_index];
+					else
+						boundinfo->default_index = -1;
 					break;
 				}
 
@@ -513,6 +536,7 @@ RelationBuildPartitionDesc(Relation rel)
 												sizeof(RangeDatumContent *));
 					boundinfo->indexes = (int *) palloc((ndatums + 1) *
 														sizeof(int));
+					boundinfo->has_default = found_def;
 
 					for (i = 0; i < ndatums; i++)
 					{
@@ -671,6 +695,7 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
 	PartitionKey key = RelationGetPartitionKey(parent);
 	PartitionDesc partdesc = RelationGetPartitionDesc(parent);
+	PartitionBoundInfo boundinfo = partdesc->boundinfo;
 	ParseState *pstate = make_parsestate(NULL);
 	int			with = -1;
 	bool		overlap = false;
@@ -683,18 +708,19 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 
 				if (partdesc->nparts > 0)
 				{
-					PartitionBoundInfo boundinfo = partdesc->boundinfo;
 					ListCell   *cell;
 
 					Assert(boundinfo &&
 						   boundinfo->strategy == PARTITION_STRATEGY_LIST &&
-						   (boundinfo->ndatums > 0 || boundinfo->has_null));
+						   (boundinfo->ndatums > 0 || boundinfo->has_null
+							|| boundinfo->has_default));
 
 					foreach(cell, spec->listdatums)
 					{
-						Const	   *val = lfirst(cell);
+						Node	   *value = lfirst(cell);
+						Const	   *val = (Const *) value;
 
-						if (!val->constisnull)
+						if (!val->constisnull && !(isDefaultPartitionBound(value)))
 						{
 							int			offset;
 							bool		equal;
@@ -709,7 +735,14 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 								break;
 							}
 						}
-						else if (boundinfo->has_null)
+						else if (isDefaultPartitionBound(value) &&
+								 boundinfo->has_default)
+						{
+							overlap = true;
+							with = boundinfo->default_index;
+							break;
+						}
+						else if (val->constisnull && boundinfo->has_null)
 						{
 							overlap = true;
 							with = boundinfo->null_index;
@@ -836,6 +869,116 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 						relname, get_rel_name(partdesc->oids[with])),
 				 parser_errposition(pstate, spec->location)));
 	}
+
+	/*
+	 * 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.
+	 */
+	if (spec->strategy == PARTITION_STRATEGY_LIST && partdesc->nparts > 0
+		&& boundinfo->has_default)
+	{
+		List	       *partConstraint = NIL;
+		ExprContext    *econtext;
+		EState	       *estate;
+		Relation	    defrel;
+		HeapScanDesc    scan;
+		HeapTuple	    tuple;
+		ExprState	   *partqualstate = NULL;
+		Snapshot	    snapshot;
+		Oid			    defid;
+		MemoryContext   oldCxt;
+		TupleTableSlot *tupslot;
+		TupleDesc	    tupdesc;
+		List           *all_parts;
+		ListCell       *lc;
+
+
+		partConstraint = generate_qual_for_defaultpart(parent, bound, &defid);
+		partConstraint = (List *) eval_const_expressions(NULL,
+													(Node *) partConstraint);
+		partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
+		partConstraint = list_make1(make_ands_explicit(partConstraint));
+
+		/*
+		 * Generate the constraint and default execution states
+		 */
+		defrel = heap_open(defid, AccessExclusiveLock);
+
+		if (defrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			all_parts = find_all_inheritors(RelationGetRelid(defrel),
+											AccessExclusiveLock, NULL);
+		else
+			all_parts = list_make1_oid(RelationGetRelid(defrel));
+
+		foreach(lc, all_parts)
+		{
+			Oid			part_relid = lfirst_oid(lc);
+			Relation	part_rel;
+			Expr	   *constr;
+			Expr	   *partition_constraint;
+
+			if (part_relid != RelationGetRelid(defrel))
+				part_rel = heap_open(part_relid, NoLock);
+			else if (part_relid == RELKIND_PARTITIONED_TABLE)
+				continue;
+			else
+				part_rel = defrel;
+
+			/*
+			 * Skip if it's a partitioned table.  Only RELKIND_RELATION
+			 * relations (ie, leaf partitions) need to be scanned.
+			 */
+			if (part_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			{
+				heap_close(part_rel, NoLock);
+				continue;
+			}
+			tupdesc = CreateTupleDescCopy(RelationGetDescr(part_rel));
+			constr = linitial(partConstraint);
+			partition_constraint = (Expr *)
+				map_partition_varattnos((List *) constr, 1,
+										part_rel, parent);
+			estate = CreateExecutorState();
+
+			/* Build expression execution states for partition check quals */
+			partqualstate = ExecPrepareExpr(partition_constraint,
+							estate);
+
+			econtext = GetPerTupleExprContext(estate);
+			snapshot = RegisterSnapshot(GetLatestSnapshot());
+			scan = heap_beginscan(part_rel, snapshot, 0, NULL);
+			tupslot = MakeSingleTupleTableSlot(tupdesc);
+
+			/*
+			 * Switch to per-tuple memory context and reset it for each tuple
+			 * produced, so we don't leak memory.
+			 */
+			oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+			while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+			{
+				ExecStoreTuple(tuple, tupslot, InvalidBuffer, false);
+				econtext->ecxt_scantuple = tupslot;
+				if (partqualstate && !ExecCheck(partqualstate, econtext))
+					ereport(ERROR,
+							(errcode(ERRCODE_CHECK_VIOLATION),
+					errmsg("new default partition constraint is violated by some row")));
+				ResetExprContext(econtext);
+				CHECK_FOR_INTERRUPTS();
+			}
+			CacheInvalidateRelcache(part_rel);
+			MemoryContextSwitchTo(oldCxt);
+			heap_endscan(scan);
+			UnregisterSnapshot(snapshot);
+			ExecDropSingleTupleTableSlot(tupslot);
+			FreeExecutorState(estate);
+			heap_close(part_rel, NoLock);
+		}
+		CacheInvalidateRelcache(defrel);
+		heap_close(defrel, AccessExclusiveLock);
+	}
 }
 
 /*
@@ -884,6 +1027,117 @@ get_partition_parent(Oid relid)
 }
 
 /*
+ * Returns true if the partition bound is default
+ */
+bool
+isDefaultPartitionBound(Node *value)
+{
+	if (IsA(value, DefElem))
+	{
+		DefElem *defvalue = (DefElem *) value;
+		if(!strcmp(defvalue->defname, "DEFAULT"))
+			return true;
+		return false;
+	}
+	return false;
+}
+
+/*
+ * Return the bound spec list to be used
+ * in partition constraint of default partition
+ */
+List *
+get_qual_for_default(Relation parent, Oid *defid)
+{
+	List	   *inhoids;
+	ListCell   *cell;
+	List	   *boundspecs = 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 *cell1;
+
+		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));
+		foreach(cell1, bspec->listdatums)
+		{
+			Node *value = lfirst(cell1);
+			if (isDefaultPartitionBound(value))
+			{
+				def_elem = true;
+				*defid  = inhrelid;
+				break;
+			}
+		}
+		if (!def_elem)
+		{
+			foreach(cell1, bspec->listdatums)
+			{
+				Node *value = lfirst(cell1);
+				boundspecs = lappend(boundspecs, value);
+			}
+		}
+		ReleaseSysCache(tuple);
+	}
+	return boundspecs;
+}
+
+/*
+ * Return a list of executable expressions as new partition constraint
+ * for default partition while adding a new partition after default
+ */
+List *
+generate_qual_for_defaultpart(Relation parent, Node *bound, Oid * defid)
+{
+	PartitionKey key = RelationGetPartitionKey(parent);
+	PartitionBoundSpec *spec;
+	List	   *partConstraint = NIL;
+	ListCell   *cell;
+	List       *boundspecs = NIL;
+	List       *bound_datums;
+
+	spec = (PartitionBoundSpec *) bound;
+	bound_datums = list_copy(spec->listdatums);
+
+	boundspecs = get_qual_for_default(parent, defid);
+
+	foreach(cell, bound_datums)
+	{
+		Node *value = lfirst(cell);
+		boundspecs = lappend(boundspecs, value);
+	}
+	partConstraint = get_qual_for_list(key, spec, true, boundspecs);
+	return partConstraint;
+}
+
+/*
  * get_qual_from_partbound
  *		Given a parser node for partition bound, return the list of executable
  *		expressions as partition constraint
@@ -894,6 +1148,10 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
 	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
 	PartitionKey key = RelationGetPartitionKey(parent);
 	List	   *my_qual = NIL;
+	bool		is_def = false;
+	Oid        defid;
+	ListCell   *cell;
+	List	   *boundspecs = NIL;
 
 	Assert(key != NULL);
 
@@ -901,9 +1159,16 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
 	{
 		case PARTITION_STRATEGY_LIST:
 			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
-			my_qual = get_qual_for_list(key, spec);
+			foreach(cell, spec->listdatums)
+			{
+				Node *value = lfirst(cell);
+				if (isDefaultPartitionBound(value))
+					is_def = true;
+			}
+			if (is_def)
+				boundspecs = get_qual_for_default(parent, &defid);
+			my_qual = get_qual_for_list(key, spec, is_def, boundspecs);
 			break;
-
 		case PARTITION_STRATEGY_RANGE:
 			Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
 			my_qual = get_qual_for_range(key, spec);
@@ -1151,7 +1416,8 @@ RelationGetPartitionDispatchInfo(Relation rel, int lockmode,
  * Returns a list of expressions to use as a list partition's constraint.
  */
 static List *
-get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
+get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec, bool is_def,
+					List *boundspecs)
 {
 	List	   *result;
 	ArrayExpr  *arr;
@@ -1177,12 +1443,14 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 	else
 		keyCol = (Expr *) copyObject(linitial(key->partexprs));
 
+	if (!is_def)
+		boundspecs = spec->listdatums;
 	/*
 	 * We must remove any NULL value in the list; we handle it separately
 	 * below.
 	 */
 	prev = NULL;
-	for (cell = list_head(spec->listdatums); cell; cell = next)
+	for (cell = list_head(boundspecs); cell; cell = next)
 	{
 		Const	   *val = (Const *) lfirst(cell);
 
@@ -1191,14 +1459,14 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 		if (val->constisnull)
 		{
 			list_has_null = true;
-			spec->listdatums = list_delete_cell(spec->listdatums,
+			boundspecs = list_delete_cell(boundspecs,
 												cell, prev);
 		}
 		else
 			prev = cell;
 	}
 
-	if (!list_has_null)
+	if ((is_def && list_has_null) || (!is_def && !list_has_null))
 	{
 		/*
 		 * Gin up a col IS NOT NULL test that will be AND'd with other
@@ -1210,7 +1478,7 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 		nulltest1->argisrow = false;
 		nulltest1->location = -1;
 	}
-	else
+	else if(!is_def && list_has_null)
 	{
 		/*
 		 * Gin up a col IS NULL test that will be OR'd with other expressions
@@ -1229,7 +1497,7 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 		: key->parttypid[0];
 	arr->array_collid = key->parttypcoll[0];
 	arr->element_typeid = key->parttypid[0];
-	arr->elements = spec->listdatums;
+	arr->elements = boundspecs;
 	arr->multidims = false;
 	arr->location = -1;
 
@@ -1243,15 +1511,34 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 										  key->partcollation[0],
 										  COERCE_EXPLICIT_CAST);
 
+	/* Build leftop <> ALL(rightop). This is for default partition */
+	if (is_def)
+	{
+		/* Find the negator for the partition operator above */
+		if(get_negator(operoid) == InvalidOid)
+			ereport(ERROR, (errcode(ERRCODE_CHECK_VIOLATION),
+			errmsg("DEFAULT partition cannot be used without negator of operator  %s",
+						get_opname(operoid))));
+		operoid = get_negator(operoid);
+		opexpr = makeNode(ScalarArrayOpExpr);
+		opexpr->opno = operoid;
+		opexpr->opfuncid = get_opcode(operoid);
+		opexpr->useOr = false;
+		opexpr->inputcollid = key->partcollation[0];
+		opexpr->args = list_make2(keyCol, arr);
+		opexpr->location = -1;
+	}
 	/* Build leftop = ANY (rightop) */
-	opexpr = makeNode(ScalarArrayOpExpr);
-	opexpr->opno = operoid;
-	opexpr->opfuncid = get_opcode(operoid);
-	opexpr->useOr = true;
-	opexpr->inputcollid = key->partcollation[0];
-	opexpr->args = list_make2(keyCol, arr);
-	opexpr->location = -1;
-
+	else
+	{
+		opexpr = makeNode(ScalarArrayOpExpr);
+		opexpr->opno = operoid;
+		opexpr->opfuncid = get_opcode(operoid);
+		opexpr->useOr = true;
+		opexpr->inputcollid = key->partcollation[0];
+		opexpr->args = list_make2(keyCol, arr);
+		opexpr->location = -1;
+	}
 	if (nulltest1)
 		result = list_make2(nulltest1, opexpr);
 	else if (nulltest2)
@@ -1775,10 +2062,25 @@ get_partition_for_tuple(PartitionDispatch *pd,
 		 */
 		if (cur_index < 0)
 		{
-			result = -1;
-			*failed_at = parent;
-			*failed_slot = slot;
-			break;
+			/*
+			 * If partitioned table has a default partition, return
+			 * its sequence number
+			 */
+			if (partdesc->boundinfo->has_default)
+			{
+				result = parent->indexes[partdesc->boundinfo->default_index];
+				if (result >= 0)
+					break;
+				else
+					parent = pd[-parent->indexes[partdesc->boundinfo->default_index]];
+			}
+			else
+			{
+				result = -1;
+				*failed_at = parent;
+				*failed_slot = slot;
+				break;
+			}
 		}
 		else if (parent->indexes[cur_index] >= 0)
 		{
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 65c004c..f2590e2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -579,6 +579,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <node>		ForValues
 %type <node>		partbound_datum
 %type <list>		partbound_datum_list
+%type <list>		default_partition
 %type <partrange_datum>	PartitionRangeDatum
 %type <list>		range_datum_list
 
@@ -2677,6 +2678,17 @@ ForValues:
 
 					$$ = (Node *) n;
 				}
+			| default_partition
+				{
+					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+					n->strategy = PARTITION_DEFAULT;
+					n->listdatums = $1;
+					n->location = @1;
+
+					$$ = (Node *) n;
+				}
+
 		;
 
 partbound_datum:
@@ -2691,6 +2703,12 @@ partbound_datum_list:
 												{ $$ = lappend($1, $3); }
 		;
 
+default_partition:
+			DEFAULT	{
+						Node *def = (Node *)makeDefElem("DEFAULT", NULL, @1);
+						$$ = list_make1(def);
+					}
+
 range_datum_list:
 			PartitionRangeDatum					{ $$ = list_make1($1); }
 			| range_datum_list ',' PartitionRangeDatum
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 882955b..27c8460 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -3298,55 +3299,67 @@ transformPartitionBound(ParseState *pstate, Relation parent, Node *bound)
 										 false, false);
 
 		if (spec->strategy != PARTITION_STRATEGY_LIST)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
-				  errmsg("invalid bound specification for a list partition"),
+		{
+			/*
+			 * If the partition is the default partition switch
+			 * back to PARTITION_STRATEGY_LIST
+			 */
+			if (spec->strategy == PARTITION_DEFAULT)
+				result_spec->strategy = PARTITION_STRATEGY_LIST;
+			else
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("invalid bound specification for a list partition"),
 					 parser_errposition(pstate, exprLocation(bound))));
+		}
 
 		result_spec->listdatums = NIL;
 		foreach(cell, spec->listdatums)
 		{
-			A_Const    *con = (A_Const *) lfirst(cell);
-			Node	   *value;
-			ListCell   *cell2;
-			bool		duplicate;
-
-			value = (Node *) make_const(pstate, &con->val, con->location);
-			value = coerce_to_target_type(pstate,
-										  value, exprType(value),
-										  get_partition_col_typid(key, 0),
-										  get_partition_col_typmod(key, 0),
-										  COERCION_ASSIGNMENT,
-										  COERCE_IMPLICIT_CAST,
-										  -1);
-
-			if (value == NULL)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
-							 format_type_be(get_partition_col_typid(key, 0)),
-								colname),
-						 parser_errposition(pstate,
-											exprLocation((Node *) con))));
-
-			/* Simplify the expression */
-			value = (Node *) expression_planner((Expr *) value);
-
-			/* Don't add to the result if the value is a duplicate */
-			duplicate = false;
-			foreach(cell2, result_spec->listdatums)
+			Node *value = lfirst(cell);
+			/* Perform the transformation only for non default partition */
+			if (!(isDefaultPartitionBound(value)))
 			{
-				Const	   *value2 = (Const *) lfirst(cell2);
+				A_Const    *con = (A_Const *) lfirst(cell);
+				ListCell   *cell2;
+				bool		duplicate;
+
+				value = (Node *) make_const(pstate, &con->val, con->location);
+				value = coerce_to_target_type(pstate,
+											value, exprType(value),
+											get_partition_col_typid(key, 0),
+											get_partition_col_typmod(key, 0),
+											COERCION_ASSIGNMENT,
+											COERCE_IMPLICIT_CAST,
+											-1);
 
-				if (equal(value, value2))
+				if (value == NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+								format_type_be(get_partition_col_typid(key, 0)),
+									colname),
+							parser_errposition(pstate,
+												exprLocation((Node *) con))));
+
+				/* Simplify the expression */
+				value = (Node *) expression_planner((Expr *) value);
+
+				/* Don't add to the result if the value is a duplicate */
+				duplicate = false;
+				foreach(cell2, result_spec->listdatums)
 				{
-					duplicate = true;
-					break;
+					Const	   *value2 = (Const *) lfirst(cell2);
+
+					if (equal(value, value2))
+					{
+						duplicate = true;
+						break;
+					}
 				}
+				if (duplicate)
+					continue;
 			}
-			if (duplicate)
-				continue;
-
 			result_spec->listdatums = lappend(result_spec->listdatums,
 											  value);
 		}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index cbde1ff..c3c9e70 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -24,6 +24,7 @@
 #include "access/sysattr.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
+#include "catalog/partition.h"
 #include "catalog/pg_aggregate.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_authid.h"
@@ -8616,25 +8617,39 @@ get_rule_expr(Node *node, deparse_context *context,
 				PartitionBoundSpec *spec = (PartitionBoundSpec *) node;
 				ListCell   *cell;
 				char	   *sep;
+				bool       is_def = false;
 
 				switch (spec->strategy)
 				{
 					case PARTITION_STRATEGY_LIST:
 						Assert(spec->listdatums != NIL);
-
-						appendStringInfoString(buf, "FOR VALUES");
-						appendStringInfoString(buf, " IN (");
-						sep = "";
 						foreach(cell, spec->listdatums)
 						{
-							Const	   *val = lfirst(cell);
+							Node *value = lfirst(cell);
 
-							appendStringInfoString(buf, sep);
-							get_const_expr(val, context, -1);
-							sep = ", ";
+							if (isDefaultPartitionBound(value))
+							{
+								appendStringInfoString(buf, "DEFAULT");
+								is_def = true;
+								break;
+							}
 						}
+						if(!is_def)
+						{
+							appendStringInfoString(buf, "FOR VALUES");
+							appendStringInfoString(buf, " IN (");
+							sep = "";
+							foreach(cell, spec->listdatums)
+							{
+								Const	   *val = lfirst(cell);
 
-						appendStringInfoString(buf, ")");
+								appendStringInfoString(buf, sep);
+								get_const_expr(val, context, -1);
+								sep = ", ";
+							}
+
+							appendStringInfoString(buf, ")");
+						}
 						break;
 
 					case PARTITION_STRATEGY_RANGE:
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 3bd5277..cc0af61 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2001,7 +2001,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("ATTACH", "PARTITION", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 	else if (TailMatches2("FOR", "VALUES"))
 		COMPLETE_WITH_LIST2("FROM (", "IN (");
 	/*
@@ -2398,7 +2398,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("PARTITION", "OF", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 
 /* CREATE TABLESPACE */
 	else if (Matches3("CREATE", "TABLESPACE", MatchAny))
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 421644c..7cb4f0a 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -74,9 +74,12 @@ extern void RelationBuildPartitionDesc(Relation relation);
 extern bool partition_bounds_equal(PartitionKey key,
 					   PartitionBoundInfo p1, PartitionBoundInfo p2);
 
+extern bool isDefaultPartitionBound(Node *value);
 extern void check_new_partition_bound(char *relname, Relation parent, Node *bound);
 extern Oid	get_partition_parent(Oid relid);
 extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
+extern List *generate_qual_for_defaultpart(Relation parent, Node *bound, Oid *defid);
+extern List *get_qual_for_default(Relation parent, Oid *defid);
 extern List *map_partition_varattnos(List *expr, int target_varno,
 						Relation partrel, Relation parent);
 extern List *RelationGetPartitionQual(Relation rel);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 46c23c2..8236af3 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -780,6 +780,7 @@ typedef struct PartitionSpec
 
 #define PARTITION_STRATEGY_LIST		'l'
 #define PARTITION_STRATEGY_RANGE	'r'
+#define PARTITION_DEFAULT	'd'
 
 /*
  * PartitionBoundSpec - a partition bound specification
#87Beena Emerson
memissemerson@gmail.com
In reply to: Rahila Syed (#86)
Re: Adding support for Default partition in partitioning

Hello,

On Fri, May 12, 2017 at 5:30 PM, Rahila Syed <rahilasyed90@gmail.com> wrote:

Hello,

(1) With the new patch, we allow new partitions when there is overlapping

data with default partition. The entries in default are ignored when
running queries satisfying the new partition.
This was introduced in latest version. We are not allowing adding a
partition when entries with same key value exist in default partition. So
this scenario should not
come in picture. Please find attached an updated patch which corrects this.

Thank you for the updated patch. However, now I cannot create a partition
after default.

CREATE TABLE list1 (
a int,
b int
) PARTITION BY LIST (a);

CREATE TABLE list1_1 (LIKE list1);
ALTER TABLE list1 ATTACH PARTITION list1_1 FOR VALUES IN (1);
CREATE TABLE list1_def PARTITION OF list1 DEFAULT;
CREATE TABLE list1_5 PARTITION OF list1 FOR VALUES IN (3);

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.
!>

(2) I get the following warning:

partition.c: In function ‘check_new_partition_bound’:
partition.c:882:15: warning: ‘boundinfo’ may be used uninitialized in

this function [-Wmaybe-uninitialized]

&& boundinfo->has_default)

^

preproc.y:3250.2-8: warning: type clash on default action: <str> != <>

I failed to notice this warning. I will look into it.

This is incredibly ugly. I don't know exactly what should be done
about it, but I think PARTITION_DEFAULT is a bad idea and has got to
go. Maybe add a separate isDefault flag to PartitionBoundSpec

Will look at other ways to do it.

Doesn't it strike you as a bit strange that get_qual_for_default()
doesn't return a qual? Functions should generally have names that
describe what they do.

Will fix this.

There's an existing function that you can use to concatenate two lists
instead of open-coding it.

Will check this.

you should really add the documentation and
regression tests which you mentioned as a TODO. And run the code
through pgindent

I will also update the next version with documentation and regression tests
and run pgindent

Thank you,
Rahila Syed

On Fri, May 12, 2017 at 4:33 PM, Beena Emerson <memissemerson@gmail.com>
wrote:

On Thu, May 11, 2017 at 7:37 PM, Rahila Syed <rahilasyed90@gmail.com>
wrote:

Hello,

Please find attached an updated patch with review comments and bugs
reported till date implemented.

Hello Rahila,

Tested on "efa2c18 Doc fix: scale(numeric) returns integer, not numeric."

(1) With the new patch, we allow new partitions when there is overlapping
data with default partition. The entries in default are ignored when
running queries satisfying the new partition.

DROP TABLE list1;
CREATE TABLE list1 (
a int,
b int
) PARTITION BY LIST (a);
CREATE TABLE list1_1 (LIKE list1);
ALTER TABLE list1 ATTACH PARTITION list1_1 FOR VALUES IN (1);
CREATE TABLE list1_def PARTITION OF list1 DEFAULT;
INSERT INTO list1 SELECT generate_series(1,2),1;
-- Partition overlapping with DEF
CREATE TABLE list1_2 PARTITION OF list1 FOR VALUES IN (2);
INSERT INTO list1 SELECT generate_series(2,3),2;

postgres=# SELECT * FROM list1 ORDER BY a,b;
a | b
---+---
1 | 1
2 | 1
2 | 2
3 | 2
(4 rows)

postgres=# SELECT * FROM list1 WHERE a=2;
a | b
---+---
2 | 2
(1 row)

This ignores the a=2 entries in the DEFAULT.

postgres=# SELECT * FROM list1_def;
a | b
---+---
2 | 1
3 | 2
(2 rows)

(2) I get the following warning:

partition.c: In function ‘check_new_partition_bound’:
partition.c:882:15: warning: ‘boundinfo’ may be used uninitialized in
this function [-Wmaybe-uninitialized]
&& boundinfo->has_default)
^
preproc.y:3250.2-8: warning: type clash on default action: <str> != <>

1.
In following block, we can just do with def_index, and we do not need

found_def

flag. We can check if def_index is -1 or not to decide if default

partition is

present.

found_def is used to set boundinfo->has_default which is used at couple
of other places to check if default partition exists. The implementation
is similar
to has_null.

3.
In following function isDefaultPartitionBound, first statement "return

false"

is not needed.

It is needed to return false if the node is not DefElem.

Todo:
Add regression tests
Documentation

Thank you,
Rahila Syed

--

Beena Emerson

EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#88Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Beena Emerson (#87)
1 attachment(s)
Re: Adding support for Default partition in partitioning

On Fri, May 12, 2017 at 7:34 PM, Beena Emerson <memissemerson@gmail.com>
wrote:

Thank you for the updated patch. However, now I cannot create a partition
after default.

CREATE TABLE list1 (
a int,
b int
) PARTITION BY LIST (a);

CREATE TABLE list1_1 (LIKE list1);
ALTER TABLE list1 ATTACH PARTITION list1_1 FOR VALUES IN (1);
CREATE TABLE list1_def PARTITION OF list1 DEFAULT;
CREATE TABLE list1_5 PARTITION OF list1 FOR VALUES IN (3);

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.
!>

Hi,

I have fixed the crash in attached patch.
Also the patch needed bit of adjustments due to recent commit.
I have re-based the patch on latest commit.

PFA.

Regards,
Jeevan Ladhe

Attachments:

default_partition_v12.patchapplication/octet-stream; name=default_partition_v12.patchDownload
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 832049c..8477195 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -34,6 +34,7 @@
 #include "nodes/nodeFuncs.h"
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
+#include "optimizer/prep.h"
 #include "optimizer/planmain.h"
 #include "optimizer/var.h"
 #include "rewrite/rewriteManip.h"
@@ -90,6 +91,10 @@ typedef struct PartitionBoundInfoData
 								 * for range partitioned tables */
 	int			null_index;		/* Index of the null-accepting partition; -1
 								 * for range partitioned tables */
+	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 */
 } PartitionBoundInfoData;
 
 /*
@@ -121,14 +126,15 @@ static int32 qsort_partition_rbound_cmp(const void *a, const void *b,
 static Oid get_partition_operator(PartitionKey key, int col,
 					   StrategyNumber strategy, bool *need_relabel);
 static Expr *make_partition_op_expr(PartitionKey key, int keynum,
-					   uint16 strategy, Expr *arg1, Expr *arg2);
+					   uint16 strategy, Expr *arg1, Expr *arg2, bool is_def);
 static void get_range_key_properties(PartitionKey key, int keynum,
 						 PartitionRangeDatum *ldatum,
 						 PartitionRangeDatum *udatum,
 						 ListCell **partexprs_item,
 						 Expr **keyCol,
 						 Const **lower_val, Const **upper_val);
-static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec,
+							   bool is_def, List *boundspecs);
 static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
 static List *generate_partition_qual(Relation rel);
 
@@ -174,6 +180,8 @@ RelationBuildPartitionDesc(Relation rel)
 	/* List partitioning specific */
 	PartitionListValue **all_values = NULL;
 	bool		found_null = false;
+	bool		found_def = false;
+	int			def_index = -1;
 	int			null_index = -1;
 
 	/* Range partitioning specific */
@@ -257,9 +265,16 @@ RelationBuildPartitionDesc(Relation rel)
 
 				foreach(c, spec->listdatums)
 				{
+					Node *value = lfirst(c);
 					Const	   *val = lfirst(c);
 					PartitionListValue *list_value = NULL;
 
+					if (isDefaultPartitionBound(value))
+					{
+						found_def = true;
+						def_index = i;
+						continue;
+					}
 					if (!val->constisnull)
 					{
 						list_value = (PartitionListValue *)
@@ -467,6 +482,7 @@ RelationBuildPartitionDesc(Relation rel)
 			case PARTITION_STRATEGY_LIST:
 				{
 					boundinfo->has_null = found_null;
+					boundinfo->has_default = found_def;
 					boundinfo->indexes = (int *) palloc(ndatums * sizeof(int));
 
 					/*
@@ -504,6 +520,8 @@ RelationBuildPartitionDesc(Relation rel)
 						if (mapping[null_index] == -1)
 							mapping[null_index] = next_index++;
 					}
+					if (found_def && mapping[def_index] == -1)
+						mapping[def_index] = next_index++;
 
 					/* All partition must now have a valid mapping */
 					Assert(next_index == nparts);
@@ -512,6 +530,11 @@ RelationBuildPartitionDesc(Relation rel)
 						boundinfo->null_index = mapping[null_index];
 					else
 						boundinfo->null_index = -1;
+
+					if (found_def)
+						boundinfo->default_index = mapping[def_index];
+					else
+						boundinfo->default_index = -1;
 					break;
 				}
 
@@ -521,6 +544,7 @@ RelationBuildPartitionDesc(Relation rel)
 												sizeof(RangeDatumContent *));
 					boundinfo->indexes = (int *) palloc((ndatums + 1) *
 														sizeof(int));
+					boundinfo->has_default = found_def;
 
 					for (i = 0; i < ndatums; i++)
 					{
@@ -679,6 +703,7 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
 	PartitionKey key = RelationGetPartitionKey(parent);
 	PartitionDesc partdesc = RelationGetPartitionDesc(parent);
+	PartitionBoundInfo boundinfo = partdesc->boundinfo;
 	ParseState *pstate = make_parsestate(NULL);
 	int			with = -1;
 	bool		overlap = false;
@@ -691,18 +716,19 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 
 				if (partdesc->nparts > 0)
 				{
-					PartitionBoundInfo boundinfo = partdesc->boundinfo;
 					ListCell   *cell;
 
 					Assert(boundinfo &&
 						   boundinfo->strategy == PARTITION_STRATEGY_LIST &&
-						   (boundinfo->ndatums > 0 || boundinfo->has_null));
+						   (boundinfo->ndatums > 0 || boundinfo->has_null
+							|| boundinfo->has_default));
 
 					foreach(cell, spec->listdatums)
 					{
-						Const	   *val = lfirst(cell);
+						Node	   *value = lfirst(cell);
+						Const	   *val = (Const *) value;
 
-						if (!val->constisnull)
+						if (!val->constisnull && !(isDefaultPartitionBound(value)))
 						{
 							int			offset;
 							bool		equal;
@@ -717,7 +743,14 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 								break;
 							}
 						}
-						else if (boundinfo->has_null)
+						else if (isDefaultPartitionBound(value) &&
+								 boundinfo->has_default)
+						{
+							overlap = true;
+							with = boundinfo->default_index;
+							break;
+						}
+						else if (val->constisnull && boundinfo->has_null)
 						{
 							overlap = true;
 							with = boundinfo->null_index;
@@ -844,6 +877,112 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 						relname, get_rel_name(partdesc->oids[with])),
 				 parser_errposition(pstate, spec->location)));
 	}
+
+	/*
+	 * 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.
+	 */
+	if (spec->strategy == PARTITION_STRATEGY_LIST && partdesc->nparts > 0
+		&& boundinfo->has_default)
+	{
+		List	       *partConstraint = NIL;
+		ExprContext    *econtext;
+		EState	       *estate;
+		Relation	    defrel;
+		HeapScanDesc    scan;
+		HeapTuple	    tuple;
+		ExprState	   *partqualstate = NULL;
+		Snapshot	    snapshot;
+		Oid			    defid;
+		MemoryContext   oldCxt;
+		TupleTableSlot *tupslot;
+		TupleDesc	    tupdesc;
+		List           *all_parts;
+		ListCell       *lc;
+
+
+		partConstraint = generate_qual_for_defaultpart(parent, bound, &defid);
+		partConstraint = (List *) eval_const_expressions(NULL,
+													(Node *) partConstraint);
+		partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
+		partConstraint = list_make1(make_ands_explicit(partConstraint));
+
+		/*
+		 * Generate the constraint and default execution states
+		 */
+		defrel = heap_open(defid, AccessExclusiveLock);
+
+		if (defrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			all_parts = find_all_inheritors(RelationGetRelid(defrel),
+											AccessExclusiveLock, NULL);
+		else
+			all_parts = list_make1_oid(RelationGetRelid(defrel));
+
+		foreach(lc, all_parts)
+		{
+			Oid			part_relid = lfirst_oid(lc);
+			Relation	part_rel;
+			Expr	   *constr;
+			Expr	   *partition_constraint;
+
+			if (part_relid != RelationGetRelid(defrel))
+				part_rel = heap_open(part_relid, NoLock);
+			else
+				part_rel = defrel;
+
+			/*
+			 * Skip if it's a partitioned table.  Only RELKIND_RELATION
+			 * relations (ie, leaf partitions) need to be scanned.
+			 */
+			if (part_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			{
+				heap_close(part_rel, NoLock);
+				continue;
+			}
+			tupdesc = CreateTupleDescCopy(RelationGetDescr(part_rel));
+			constr = linitial(partConstraint);
+			partition_constraint = (Expr *)
+				map_partition_varattnos((List *) constr, 1,
+										part_rel, parent);
+			estate = CreateExecutorState();
+
+			/* Build expression execution states for partition check quals */
+			partqualstate = ExecPrepareExpr(partition_constraint,
+							estate);
+
+			econtext = GetPerTupleExprContext(estate);
+			snapshot = RegisterSnapshot(GetLatestSnapshot());
+			scan = heap_beginscan(part_rel, snapshot, 0, NULL);
+			tupslot = MakeSingleTupleTableSlot(tupdesc);
+
+			/*
+			 * Switch to per-tuple memory context and reset it for each tuple
+			 * produced, so we don't leak memory.
+			 */
+			oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+			while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+			{
+				ExecStoreTuple(tuple, tupslot, InvalidBuffer, false);
+				econtext->ecxt_scantuple = tupslot;
+				if (partqualstate && !ExecCheck(partqualstate, econtext))
+					ereport(ERROR,
+							(errcode(ERRCODE_CHECK_VIOLATION),
+					errmsg("new default partition constraint is violated by some row")));
+				ResetExprContext(econtext);
+				CHECK_FOR_INTERRUPTS();
+			}
+			CacheInvalidateRelcache(part_rel);
+			MemoryContextSwitchTo(oldCxt);
+			heap_endscan(scan);
+			UnregisterSnapshot(snapshot);
+			ExecDropSingleTupleTableSlot(tupslot);
+			FreeExecutorState(estate);
+			heap_close(part_rel, NoLock);
+		}
+	}
 }
 
 /*
@@ -892,6 +1031,116 @@ get_partition_parent(Oid relid)
 }
 
 /*
+ * Returns true if the partition bound is default
+ */
+bool
+isDefaultPartitionBound(Node *value)
+{
+	if (IsA(value, DefElem))
+	{
+		DefElem *defvalue = (DefElem *) value;
+		if(!strcmp(defvalue->defname, "DEFAULT"))
+			return true;
+	}
+	return false;
+}
+
+/*
+ * Return the bound spec list to be used
+ * in partition constraint of default partition
+ */
+List *
+get_qual_for_default(Relation parent, Oid *defid)
+{
+	List	   *inhoids;
+	ListCell   *cell;
+	List	   *boundspecs = 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 *cell1;
+
+		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));
+		foreach(cell1, bspec->listdatums)
+		{
+			Node *value = lfirst(cell1);
+			if (isDefaultPartitionBound(value))
+			{
+				def_elem = true;
+				*defid  = inhrelid;
+				break;
+			}
+		}
+		if (!def_elem)
+		{
+			foreach(cell1, bspec->listdatums)
+			{
+				Node *value = lfirst(cell1);
+				boundspecs = lappend(boundspecs, value);
+			}
+		}
+		ReleaseSysCache(tuple);
+	}
+	return boundspecs;
+}
+
+/*
+ * Return a list of executable expressions as new partition constraint
+ * for default partition while adding a new partition after default
+ */
+List *
+generate_qual_for_defaultpart(Relation parent, Node *bound, Oid * defid)
+{
+	PartitionKey key = RelationGetPartitionKey(parent);
+	PartitionBoundSpec *spec;
+	List	   *partConstraint = NIL;
+	ListCell   *cell;
+	List       *boundspecs = NIL;
+	List       *bound_datums;
+
+	spec = (PartitionBoundSpec *) bound;
+	bound_datums = list_copy(spec->listdatums);
+
+	boundspecs = get_qual_for_default(parent, defid);
+
+	foreach(cell, bound_datums)
+	{
+		Node *value = lfirst(cell);
+		boundspecs = lappend(boundspecs, value);
+	}
+	partConstraint = get_qual_for_list(key, spec, true, boundspecs);
+	return partConstraint;
+}
+
+/*
  * get_qual_from_partbound
  *		Given a parser node for partition bound, return the list of executable
  *		expressions as partition constraint
@@ -902,6 +1151,10 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
 	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
 	PartitionKey key = RelationGetPartitionKey(parent);
 	List	   *my_qual = NIL;
+	bool		is_def = false;
+	Oid        defid;
+	ListCell   *cell;
+	List	   *boundspecs = NIL;
 
 	Assert(key != NULL);
 
@@ -909,9 +1162,16 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
 	{
 		case PARTITION_STRATEGY_LIST:
 			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
-			my_qual = get_qual_for_list(key, spec);
+			foreach(cell, spec->listdatums)
+			{
+				Node *value = lfirst(cell);
+				if (isDefaultPartitionBound(value))
+					is_def = true;
+			}
+			if (is_def)
+				boundspecs = get_qual_for_default(parent, &defid);
+			my_qual = get_qual_for_list(key, spec, is_def, boundspecs);
 			break;
-
 		case PARTITION_STRATEGY_RANGE:
 			Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
 			my_qual = get_qual_for_range(key, spec);
@@ -1239,7 +1499,8 @@ get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
  */
 static Expr *
 make_partition_op_expr(PartitionKey key, int keynum,
-					   uint16 strategy, Expr *arg1, Expr *arg2)
+					   uint16 strategy, Expr *arg1, Expr *arg2,
+					   bool is_def)
 {
 	Oid			operoid;
 	bool		need_relabel = false;
@@ -1269,11 +1530,17 @@ make_partition_op_expr(PartitionKey key, int keynum,
 			{
 				ScalarArrayOpExpr *saopexpr;
 
+				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))));
+
 				/* Build leftop = ANY (rightop) */
 				saopexpr = makeNode(ScalarArrayOpExpr);
 				saopexpr->opno = operoid;
 				saopexpr->opfuncid = get_opcode(operoid);
-				saopexpr->useOr = true;
+				saopexpr->useOr = !is_def;
 				saopexpr->inputcollid = key->partcollation[0];
 				saopexpr->args = list_make2(arg1, arg2);
 				saopexpr->location = -1;
@@ -1305,7 +1572,8 @@ make_partition_op_expr(PartitionKey key, int keynum,
  * Returns a list of expressions to use as a list partition's constraint.
  */
 static List *
-get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
+get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec, bool is_def,
+					List *boundspecs)
 {
 	List	   *result;
 	ArrayExpr  *arr;
@@ -1329,12 +1597,14 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 	else
 		keyCol = (Expr *) copyObject(linitial(key->partexprs));
 
+	if (!is_def)
+		boundspecs = spec->listdatums;
 	/*
 	 * We must remove any NULL value in the list; we handle it separately
 	 * below.
 	 */
 	prev = NULL;
-	for (cell = list_head(spec->listdatums); cell; cell = next)
+	for (cell = list_head(boundspecs); cell; cell = next)
 	{
 		Const	   *val = (Const *) lfirst(cell);
 
@@ -1343,14 +1613,14 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 		if (val->constisnull)
 		{
 			list_has_null = true;
-			spec->listdatums = list_delete_cell(spec->listdatums,
+			boundspecs = list_delete_cell(boundspecs,
 												cell, prev);
 		}
 		else
 			prev = cell;
 	}
 
-	if (!list_has_null)
+	if ((is_def && list_has_null) || (!is_def && !list_has_null))
 	{
 		/*
 		 * Gin up a col IS NOT NULL test that will be AND'd with other
@@ -1362,7 +1632,7 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 		nulltest1->argisrow = false;
 		nulltest1->location = -1;
 	}
-	else
+	else if(!is_def && list_has_null)
 	{
 		/*
 		 * Gin up a col IS NULL test that will be OR'd with other expressions
@@ -1381,13 +1651,13 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 		: key->parttypid[0];
 	arr->array_collid = key->parttypcoll[0];
 	arr->element_typeid = key->parttypid[0];
-	arr->elements = spec->listdatums;
+	arr->elements = boundspecs;
 	arr->multidims = false;
 	arr->location = -1;
 
 	/* Generate the main expression, i.e., keyCol = ANY (arr) */
 	opexpr = make_partition_op_expr(key, 0, BTEqualStrategyNumber,
-									keyCol, (Expr *) arr);
+									keyCol, (Expr *) arr, is_def);
 
 	if (nulltest1)
 		result = list_make2(nulltest1, opexpr);
@@ -1596,7 +1866,7 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 		oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
 		test_expr = make_partition_op_expr(key, i, BTEqualStrategyNumber,
 										   (Expr *) lower_val,
-										   (Expr *) upper_val);
+										   (Expr *) upper_val, false);
 		fix_opfuncids((Node *) test_expr);
 		test_exprstate = ExecInitExpr(test_expr, NULL);
 		test_result = ExecEvalExprSwitchContext(test_exprstate,
@@ -1620,7 +1890,8 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 		/* Equal, so generate keyCol = lower_val expression */
 		result = lappend(result,
 						 make_partition_op_expr(key, i, BTEqualStrategyNumber,
-												keyCol, (Expr *) lower_val));
+												keyCol, (Expr *) lower_val,
+												false));
 
 		i++;
 	}
@@ -1678,7 +1949,8 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 											make_partition_op_expr(key, j,
 																   strategy,
 																   keyCol,
-														(Expr *) lower_val));
+														(Expr *) lower_val,
+																   false));
 			}
 
 			if (need_next_upper_arm && upper_val)
@@ -1700,7 +1972,8 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 											make_partition_op_expr(key, j,
 																   strategy,
 																   keyCol,
-														(Expr *) upper_val));
+														(Expr *) upper_val,
+																   false));
 
 			}
 
@@ -2026,10 +2299,25 @@ get_partition_for_tuple(PartitionDispatch *pd,
 		 */
 		if (cur_index < 0)
 		{
-			result = -1;
-			*failed_at = parent;
-			*failed_slot = slot;
-			break;
+			/*
+			 * If partitioned table has a default partition, return
+			 * its sequence number
+			 */
+			if (partdesc->boundinfo->has_default)
+			{
+				result = parent->indexes[partdesc->boundinfo->default_index];
+				if (result >= 0)
+					break;
+				else
+					parent = pd[-parent->indexes[partdesc->boundinfo->default_index]];
+			}
+			else
+			{
+				result = -1;
+				*failed_at = parent;
+				*failed_slot = slot;
+				break;
+			}
 		}
 		else if (parent->indexes[cur_index] >= 0)
 		{
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 2822331..3af286f 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -578,6 +578,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <node>		ForValues
 %type <node>		partbound_datum
 %type <list>		partbound_datum_list
+%type <list>		default_partition
 %type <partrange_datum>	PartitionRangeDatum
 %type <list>		range_datum_list
 
@@ -2676,6 +2677,17 @@ ForValues:
 
 					$$ = (Node *) n;
 				}
+			| default_partition
+				{
+					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+					n->strategy = PARTITION_DEFAULT;
+					n->listdatums = $1;
+					n->location = @1;
+
+					$$ = (Node *) n;
+				}
+
 		;
 
 partbound_datum:
@@ -2690,6 +2702,12 @@ partbound_datum_list:
 												{ $$ = lappend($1, $3); }
 		;
 
+default_partition:
+			DEFAULT	{
+						Node *def = (Node *)makeDefElem("DEFAULT", NULL, @1);
+						$$ = list_make1(def);
+					}
+
 range_datum_list:
 			PartitionRangeDatum					{ $$ = list_make1($1); }
 			| range_datum_list ',' PartitionRangeDatum
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 882955b..27c8460 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -3298,55 +3299,67 @@ transformPartitionBound(ParseState *pstate, Relation parent, Node *bound)
 										 false, false);
 
 		if (spec->strategy != PARTITION_STRATEGY_LIST)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
-				  errmsg("invalid bound specification for a list partition"),
+		{
+			/*
+			 * If the partition is the default partition switch
+			 * back to PARTITION_STRATEGY_LIST
+			 */
+			if (spec->strategy == PARTITION_DEFAULT)
+				result_spec->strategy = PARTITION_STRATEGY_LIST;
+			else
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("invalid bound specification for a list partition"),
 					 parser_errposition(pstate, exprLocation(bound))));
+		}
 
 		result_spec->listdatums = NIL;
 		foreach(cell, spec->listdatums)
 		{
-			A_Const    *con = (A_Const *) lfirst(cell);
-			Node	   *value;
-			ListCell   *cell2;
-			bool		duplicate;
-
-			value = (Node *) make_const(pstate, &con->val, con->location);
-			value = coerce_to_target_type(pstate,
-										  value, exprType(value),
-										  get_partition_col_typid(key, 0),
-										  get_partition_col_typmod(key, 0),
-										  COERCION_ASSIGNMENT,
-										  COERCE_IMPLICIT_CAST,
-										  -1);
-
-			if (value == NULL)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
-							 format_type_be(get_partition_col_typid(key, 0)),
-								colname),
-						 parser_errposition(pstate,
-											exprLocation((Node *) con))));
-
-			/* Simplify the expression */
-			value = (Node *) expression_planner((Expr *) value);
-
-			/* Don't add to the result if the value is a duplicate */
-			duplicate = false;
-			foreach(cell2, result_spec->listdatums)
+			Node *value = lfirst(cell);
+			/* Perform the transformation only for non default partition */
+			if (!(isDefaultPartitionBound(value)))
 			{
-				Const	   *value2 = (Const *) lfirst(cell2);
+				A_Const    *con = (A_Const *) lfirst(cell);
+				ListCell   *cell2;
+				bool		duplicate;
+
+				value = (Node *) make_const(pstate, &con->val, con->location);
+				value = coerce_to_target_type(pstate,
+											value, exprType(value),
+											get_partition_col_typid(key, 0),
+											get_partition_col_typmod(key, 0),
+											COERCION_ASSIGNMENT,
+											COERCE_IMPLICIT_CAST,
+											-1);
 
-				if (equal(value, value2))
+				if (value == NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+								format_type_be(get_partition_col_typid(key, 0)),
+									colname),
+							parser_errposition(pstate,
+												exprLocation((Node *) con))));
+
+				/* Simplify the expression */
+				value = (Node *) expression_planner((Expr *) value);
+
+				/* Don't add to the result if the value is a duplicate */
+				duplicate = false;
+				foreach(cell2, result_spec->listdatums)
 				{
-					duplicate = true;
-					break;
+					Const	   *value2 = (Const *) lfirst(cell2);
+
+					if (equal(value, value2))
+					{
+						duplicate = true;
+						break;
+					}
 				}
+				if (duplicate)
+					continue;
 			}
-			if (duplicate)
-				continue;
-
 			result_spec->listdatums = lappend(result_spec->listdatums,
 											  value);
 		}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 43b1475..3fcbe72 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8646,12 +8646,28 @@ get_rule_expr(Node *node, deparse_context *context,
 				PartitionBoundSpec *spec = (PartitionBoundSpec *) node;
 				ListCell   *cell;
 				char	   *sep;
+				bool       is_def = false;
 
 				switch (spec->strategy)
 				{
 					case PARTITION_STRATEGY_LIST:
 						Assert(spec->listdatums != NIL);
 
+						foreach(cell, spec->listdatums)
+						{
+							Node *value = lfirst(cell);
+
+							if (isDefaultPartitionBound(value))
+							{
+								appendStringInfoString(buf, "DEFAULT");
+								is_def = true;
+								break;
+							}
+						}
+
+						if (is_def)
+							break;
+
 						appendStringInfoString(buf, "FOR VALUES");
 						appendStringInfoString(buf, " IN (");
 						sep = "";
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b9e3491..0e93926 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2020,7 +2020,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("ATTACH", "PARTITION", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 	else if (TailMatches2("FOR", "VALUES"))
 		COMPLETE_WITH_LIST2("FROM (", "IN (");
 	/*
@@ -2429,7 +2429,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("PARTITION", "OF", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 
 /* CREATE TABLESPACE */
 	else if (Matches3("CREATE", "TABLESPACE", MatchAny))
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 25fb0a0..68f46fe 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -74,9 +74,12 @@ extern void RelationBuildPartitionDesc(Relation relation);
 extern bool partition_bounds_equal(PartitionKey key,
 					   PartitionBoundInfo p1, PartitionBoundInfo p2);
 
+extern bool isDefaultPartitionBound(Node *value);
 extern void check_new_partition_bound(char *relname, Relation parent, Node *bound);
 extern Oid	get_partition_parent(Oid relid);
 extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
+extern List *generate_qual_for_defaultpart(Relation parent, Node *bound, Oid *defid);
+extern List *get_qual_for_default(Relation parent, Oid *defid);
 extern List *map_partition_varattnos(List *expr, int target_varno,
 						Relation partrel, Relation parent);
 extern List *RelationGetPartitionQual(Relation rel);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d396be3..24e6ac1 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -780,6 +780,7 @@ typedef struct PartitionSpec
 
 #define PARTITION_STRATEGY_LIST		'l'
 #define PARTITION_STRATEGY_RANGE	'r'
+#define PARTITION_DEFAULT	'd'
 
 /*
  * PartitionBoundSpec - a partition bound specification
#89Robert Haas
robertmhaas@gmail.com
In reply to: Jeevan Ladhe (#88)
Re: Adding support for Default partition in partitioning

On Tue, May 16, 2017 at 8:57 AM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

I have fixed the crash in attached patch.
Also the patch needed bit of adjustments due to recent commit.
I have re-based the patch on latest commit.

+    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 */

Why do we need both has_default and default_index? If default_index
== -1 means that there is no default, we don't also need a separate
bool to record the same thing, do we?

get_qual_for_default() still returns a list of things that are not
quals. I think that this logic is all pretty poorly organized. The
logic to create a partitioning constraint for a list partition should
be part of get_qual_for_list(), whether or not it is a default. And
when we have range partitions, the logic to create a default range
partitioning constraint should be part of get_qual_for_range(). The
code the way it's organized today makes it look like there are three
kinds of partitions: list, range, and default. But that's not right
at all. There are two kinds: list and range. And a list partition
might or might not be a default partition, and similarly for range.

+                    ereport(ERROR, (errcode(ERRCODE_CHECK_VIOLATION),
+                                    errmsg("DEFAULT partition cannot be used"
+                                           " without negator of operator  %s",
+                                           get_opname(operoid))));

I don't think ERRCODE_CHECK_VIOLATION is the right error code here,
and we have a policy against splitting message strings like this.

--
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

#90Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Robert Haas (#89)
Re: Adding support for Default partition in partitioning

On Tue, May 16, 2017 at 9:01 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Tue, May 16, 2017 at 8:57 AM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

I have fixed the crash in attached patch.
Also the patch needed bit of adjustments due to recent commit.
I have re-based the patch on latest commit.

+    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 */

We have has_null and null_index for list partitioning. There
null_index == -1 = has_null. May be Rahila and/or Jeevan just copied
that style. Probably we should change that as well?

--
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

#91Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Ashutosh Bapat (#90)
Re: Adding support for Default partition in partitioning

On 2017/05/17 17:58, Ashutosh Bapat wrote:

On Tue, May 16, 2017 at 9:01 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Tue, May 16, 2017 at 8:57 AM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

I have fixed the crash in attached patch.
Also the patch needed bit of adjustments due to recent commit.
I have re-based the patch on latest commit.

+    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 */

We have has_null and null_index for list partitioning. There
null_index == -1 = has_null. May be Rahila and/or Jeevan just copied
that style. Probably we should change that as well?

Probably a good idea.

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#92Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Ashutosh Bapat (#90)
Re: Adding support for Default partition in partitioning

On Wed, May 17, 2017 at 2:28 PM, Ashutosh Bapat <
ashutosh.bapat@enterprisedb.com> wrote:

On Tue, May 16, 2017 at 9:01 PM, Robert Haas <robertmhaas@gmail.com>
wrote:

On Tue, May 16, 2017 at 8:57 AM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

I have fixed the crash in attached patch.
Also the patch needed bit of adjustments due to recent commit.
I have re-based the patch on latest commit.

+    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 */

We have has_null and null_index for list partitioning. There
null_index == -1 = has_null. May be Rahila and/or Jeevan just copied
that style. Probably we should change that as well?

I agree with Ashutosh.
I had given similar comment on earlier version of patch[1]/messages/by-id/CAOgcT0NUUQXWRXmeVKkYTDQvWoKLYRMiPbc89ua6i_gG8FPDmA@mail.gmail.com
</messages/by-id/CAOgcT0NUUQXWRXmeVKkYTDQvWoKLYRMiPbc89ua6i_gG8FPDmA@mail.gmail.com&gt;,
and Rahila reverted
with above reasoning, hence did not change the logic she introduced.

Probably its a good idea to have a separate patch that removes has_null
logic,
in a separate thread.

[1]: /messages/by-id/CAOgcT0NUUQXWRXmeVKkYTDQvWoKLYRMiPbc89ua6i_gG8FPDmA@mail.gmail.com
/messages/by-id/CAOgcT0NUUQXWRXmeVKkYTDQvWoKLYRMiPbc89ua6i_gG8FPDmA@mail.gmail.com

Regards,
Jeevan Ladhe.

#93Beena Emerson
memissemerson@gmail.com
In reply to: Jeevan Ladhe (#92)
1 attachment(s)
Re: Adding support for Default partition in partitioning

Hello,

Patch for default range partition has been added. PFA the rebased v12 patch
for the same.
I have not removed the has_default variable yet.

Default range partition:
/messages/by-id/CAOG9ApEYj34fWMcvBMBQ-YtqR9fTdXhdN82QEKG0SVZ6zeL1xg@mail.gmail.com
--

Beena Emerson

EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

Attachments:

default_partition_v12_rebase.patchapplication/octet-stream; name=default_partition_v12_rebase.patchDownload
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 7304f6c..86b512a 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -34,6 +34,7 @@
 #include "nodes/nodeFuncs.h"
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
+#include "optimizer/prep.h"
 #include "optimizer/planmain.h"
 #include "optimizer/var.h"
 #include "rewrite/rewriteManip.h"
@@ -88,6 +89,10 @@ 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 */
 } PartitionBoundInfoData;
 
 #define partition_bound_accepts_nulls(bi) ((bi)->null_index != -1)
@@ -121,14 +126,15 @@ static int32 qsort_partition_rbound_cmp(const void *a, const void *b,
 static Oid get_partition_operator(PartitionKey key, int col,
 					   StrategyNumber strategy, bool *need_relabel);
 static Expr *make_partition_op_expr(PartitionKey key, int keynum,
-					   uint16 strategy, Expr *arg1, Expr *arg2);
+					   uint16 strategy, Expr *arg1, Expr *arg2, bool is_def);
 static void get_range_key_properties(PartitionKey key, int keynum,
 						 PartitionRangeDatum *ldatum,
 						 PartitionRangeDatum *udatum,
 						 ListCell **partexprs_item,
 						 Expr **keyCol,
 						 Const **lower_val, Const **upper_val);
-static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec,
+							   bool is_def, List *boundspecs);
 static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
 static List *generate_partition_qual(Relation rel);
 
@@ -173,6 +179,8 @@ RelationBuildPartitionDesc(Relation rel)
 
 	/* List partitioning specific */
 	PartitionListValue **all_values = NULL;
+	bool		found_def = false;
+	int			def_index = -1;
 	int			null_index = -1;
 
 	/* Range partitioning specific */
@@ -255,9 +263,16 @@ RelationBuildPartitionDesc(Relation rel)
 
 				foreach(c, spec->listdatums)
 				{
+					Node *value = lfirst(c);
 					Const	   *val = lfirst(c);
 					PartitionListValue *list_value = NULL;
 
+					if (isDefaultPartitionBound(value))
+					{
+						found_def = true;
+						def_index = i;
+						continue;
+					}
 					if (!val->constisnull)
 					{
 						list_value = (PartitionListValue *)
@@ -463,6 +478,7 @@ RelationBuildPartitionDesc(Relation rel)
 		{
 			case PARTITION_STRATEGY_LIST:
 				{
+					boundinfo->has_default = found_def;
 					boundinfo->indexes = (int *) palloc(ndatums * sizeof(int));
 
 					/*
@@ -503,9 +519,15 @@ RelationBuildPartitionDesc(Relation rel)
 					}
 					else
 						boundinfo->null_index = -1;
+					if (found_def && mapping[def_index] == -1)
+						mapping[def_index] = next_index++;
 
 					/* All partition must now have a valid mapping */
 					Assert(next_index == nparts);
+					if (found_def)
+						boundinfo->default_index = mapping[def_index];
+					else
+						boundinfo->default_index = -1;
 					break;
 				}
 
@@ -515,6 +537,7 @@ RelationBuildPartitionDesc(Relation rel)
 												sizeof(RangeDatumContent *));
 					boundinfo->indexes = (int *) palloc((ndatums + 1) *
 														sizeof(int));
+					boundinfo->has_default = found_def;
 
 					for (i = 0; i < ndatums; i++)
 					{
@@ -670,6 +693,7 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
 	PartitionKey key = RelationGetPartitionKey(parent);
 	PartitionDesc partdesc = RelationGetPartitionDesc(parent);
+	PartitionBoundInfo boundinfo = partdesc->boundinfo;
 	ParseState *pstate = make_parsestate(NULL);
 	int			with = -1;
 	bool		overlap = false;
@@ -682,19 +706,20 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 
 				if (partdesc->nparts > 0)
 				{
-					PartitionBoundInfo boundinfo = partdesc->boundinfo;
 					ListCell   *cell;
 
 					Assert(boundinfo &&
 						   boundinfo->strategy == PARTITION_STRATEGY_LIST &&
 						   (boundinfo->ndatums > 0 ||
-							partition_bound_accepts_nulls(boundinfo)));
+							partition_bound_accepts_nulls(boundinfo) ||
+							boundinfo->has_default));
 
 					foreach(cell, spec->listdatums)
 					{
-						Const	   *val = lfirst(cell);
+						Node	   *value = lfirst(cell);
+						Const	   *val = (Const *) value;
 
-						if (!val->constisnull)
+						if (!val->constisnull && !(isDefaultPartitionBound(value)))
 						{
 							int			offset;
 							bool		equal;
@@ -709,7 +734,15 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 								break;
 							}
 						}
-						else if (partition_bound_accepts_nulls(boundinfo))
+						else if (isDefaultPartitionBound(value) &&
+								 boundinfo->has_default)
+						{
+							overlap = true;
+							with = boundinfo->default_index;
+							break;
+						}
+
+						else if (val->constisnull && partition_bound_accepts_nulls(boundinfo))
 						{
 							overlap = true;
 							with = boundinfo->null_index;
@@ -836,6 +869,112 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 						relname, get_rel_name(partdesc->oids[with])),
 				 parser_errposition(pstate, spec->location)));
 	}
+
+	/*
+	 * 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.
+	 */
+	if (spec->strategy == PARTITION_STRATEGY_LIST && partdesc->nparts > 0
+		&& boundinfo->has_default)
+	{
+		List	       *partConstraint = NIL;
+		ExprContext    *econtext;
+		EState	       *estate;
+		Relation	    defrel;
+		HeapScanDesc    scan;
+		HeapTuple	    tuple;
+		ExprState	   *partqualstate = NULL;
+		Snapshot	    snapshot;
+		Oid			    defid;
+		MemoryContext   oldCxt;
+		TupleTableSlot *tupslot;
+		TupleDesc	    tupdesc;
+		List           *all_parts;
+		ListCell       *lc;
+
+
+		partConstraint = generate_qual_for_defaultpart(parent, bound, &defid);
+		partConstraint = (List *) eval_const_expressions(NULL,
+													(Node *) partConstraint);
+		partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
+		partConstraint = list_make1(make_ands_explicit(partConstraint));
+
+		/*
+		 * Generate the constraint and default execution states
+		 */
+		defrel = heap_open(defid, AccessExclusiveLock);
+
+		if (defrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			all_parts = find_all_inheritors(RelationGetRelid(defrel),
+											AccessExclusiveLock, NULL);
+		else
+			all_parts = list_make1_oid(RelationGetRelid(defrel));
+
+		foreach(lc, all_parts)
+		{
+			Oid			part_relid = lfirst_oid(lc);
+			Relation	part_rel;
+			Expr	   *constr;
+			Expr	   *partition_constraint;
+
+			if (part_relid != RelationGetRelid(defrel))
+				part_rel = heap_open(part_relid, NoLock);
+			else
+				part_rel = defrel;
+
+			/*
+			 * Skip if it's a partitioned table.  Only RELKIND_RELATION
+			 * relations (ie, leaf partitions) need to be scanned.
+			 */
+			if (part_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			{
+				heap_close(part_rel, NoLock);
+				continue;
+			}
+			tupdesc = CreateTupleDescCopy(RelationGetDescr(part_rel));
+			constr = linitial(partConstraint);
+			partition_constraint = (Expr *)
+				map_partition_varattnos((List *) constr, 1,
+										part_rel, parent);
+			estate = CreateExecutorState();
+
+			/* Build expression execution states for partition check quals */
+			partqualstate = ExecPrepareExpr(partition_constraint,
+							estate);
+
+			econtext = GetPerTupleExprContext(estate);
+			snapshot = RegisterSnapshot(GetLatestSnapshot());
+			scan = heap_beginscan(part_rel, snapshot, 0, NULL);
+			tupslot = MakeSingleTupleTableSlot(tupdesc);
+
+			/*
+			 * Switch to per-tuple memory context and reset it for each tuple
+			 * produced, so we don't leak memory.
+			 */
+			oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+			while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+			{
+				ExecStoreTuple(tuple, tupslot, InvalidBuffer, false);
+				econtext->ecxt_scantuple = tupslot;
+				if (partqualstate && !ExecCheck(partqualstate, econtext))
+					ereport(ERROR,
+							(errcode(ERRCODE_CHECK_VIOLATION),
+					errmsg("new default partition constraint is violated by some row")));
+				ResetExprContext(econtext);
+				CHECK_FOR_INTERRUPTS();
+			}
+			CacheInvalidateRelcache(part_rel);
+			MemoryContextSwitchTo(oldCxt);
+			heap_endscan(scan);
+			UnregisterSnapshot(snapshot);
+			ExecDropSingleTupleTableSlot(tupslot);
+			FreeExecutorState(estate);
+			heap_close(part_rel, NoLock);
+		}
+	}
 }
 
 /*
@@ -884,6 +1023,116 @@ get_partition_parent(Oid relid)
 }
 
 /*
+ * Returns true if the partition bound is default
+ */
+bool
+isDefaultPartitionBound(Node *value)
+{
+	if (IsA(value, DefElem))
+	{
+		DefElem *defvalue = (DefElem *) value;
+		if(!strcmp(defvalue->defname, "DEFAULT"))
+			return true;
+	}
+	return false;
+}
+
+/*
+ * Return the bound spec list to be used
+ * in partition constraint of default partition
+ */
+List *
+get_qual_for_default(Relation parent, Oid *defid)
+{
+	List	   *inhoids;
+	ListCell   *cell;
+	List	   *boundspecs = 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 *cell1;
+
+		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));
+		foreach(cell1, bspec->listdatums)
+		{
+			Node *value = lfirst(cell1);
+			if (isDefaultPartitionBound(value))
+			{
+				def_elem = true;
+				*defid  = inhrelid;
+				break;
+			}
+		}
+		if (!def_elem)
+		{
+			foreach(cell1, bspec->listdatums)
+			{
+				Node *value = lfirst(cell1);
+				boundspecs = lappend(boundspecs, value);
+			}
+		}
+		ReleaseSysCache(tuple);
+	}
+	return boundspecs;
+}
+
+/*
+ * Return a list of executable expressions as new partition constraint
+ * for default partition while adding a new partition after default
+ */
+List *
+generate_qual_for_defaultpart(Relation parent, Node *bound, Oid * defid)
+{
+	PartitionKey key = RelationGetPartitionKey(parent);
+	PartitionBoundSpec *spec;
+	List	   *partConstraint = NIL;
+	ListCell   *cell;
+	List       *boundspecs = NIL;
+	List       *bound_datums;
+
+	spec = (PartitionBoundSpec *) bound;
+	bound_datums = list_copy(spec->listdatums);
+
+	boundspecs = get_qual_for_default(parent, defid);
+
+	foreach(cell, bound_datums)
+	{
+		Node *value = lfirst(cell);
+		boundspecs = lappend(boundspecs, value);
+	}
+	partConstraint = get_qual_for_list(key, spec, true, boundspecs);
+	return partConstraint;
+}
+
+/*
  * get_qual_from_partbound
  *		Given a parser node for partition bound, return the list of executable
  *		expressions as partition constraint
@@ -894,6 +1143,10 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
 	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
 	PartitionKey key = RelationGetPartitionKey(parent);
 	List	   *my_qual = NIL;
+	bool		is_def = false;
+	Oid        defid;
+	ListCell   *cell;
+	List	   *boundspecs = NIL;
 
 	Assert(key != NULL);
 
@@ -901,9 +1154,16 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
 	{
 		case PARTITION_STRATEGY_LIST:
 			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
-			my_qual = get_qual_for_list(key, spec);
+			foreach(cell, spec->listdatums)
+			{
+				Node *value = lfirst(cell);
+				if (isDefaultPartitionBound(value))
+					is_def = true;
+			}
+			if (is_def)
+				boundspecs = get_qual_for_default(parent, &defid);
+			my_qual = get_qual_for_list(key, spec, is_def, boundspecs);
 			break;
-
 		case PARTITION_STRATEGY_RANGE:
 			Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
 			my_qual = get_qual_for_range(key, spec);
@@ -1231,7 +1491,8 @@ get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
  */
 static Expr *
 make_partition_op_expr(PartitionKey key, int keynum,
-					   uint16 strategy, Expr *arg1, Expr *arg2)
+					   uint16 strategy, Expr *arg1, Expr *arg2,
+					   bool is_def)
 {
 	Oid			operoid;
 	bool		need_relabel = false;
@@ -1261,11 +1522,17 @@ make_partition_op_expr(PartitionKey key, int keynum,
 			{
 				ScalarArrayOpExpr *saopexpr;
 
+				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))));
+
 				/* Build leftop = ANY (rightop) */
 				saopexpr = makeNode(ScalarArrayOpExpr);
 				saopexpr->opno = operoid;
 				saopexpr->opfuncid = get_opcode(operoid);
-				saopexpr->useOr = true;
+				saopexpr->useOr = !is_def;
 				saopexpr->inputcollid = key->partcollation[keynum];
 				saopexpr->args = list_make2(arg1, arg2);
 				saopexpr->location = -1;
@@ -1297,7 +1564,8 @@ make_partition_op_expr(PartitionKey key, int keynum,
  * Returns a list of expressions to use as a list partition's constraint.
  */
 static List *
-get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
+get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec, bool is_def,
+					List *boundspecs)
 {
 	List	   *result;
 	ArrayExpr  *arr;
@@ -1321,12 +1589,14 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 	else
 		keyCol = (Expr *) copyObject(linitial(key->partexprs));
 
+	if (!is_def)
+		boundspecs = spec->listdatums;
 	/*
 	 * We must remove any NULL value in the list; we handle it separately
 	 * below.
 	 */
 	prev = NULL;
-	for (cell = list_head(spec->listdatums); cell; cell = next)
+	for (cell = list_head(boundspecs); cell; cell = next)
 	{
 		Const	   *val = (Const *) lfirst(cell);
 
@@ -1335,14 +1605,14 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 		if (val->constisnull)
 		{
 			list_has_null = true;
-			spec->listdatums = list_delete_cell(spec->listdatums,
+			boundspecs = list_delete_cell(boundspecs,
 												cell, prev);
 		}
 		else
 			prev = cell;
 	}
 
-	if (!list_has_null)
+	if ((is_def && list_has_null) || (!is_def && !list_has_null))
 	{
 		/*
 		 * Gin up a col IS NOT NULL test that will be AND'd with other
@@ -1354,7 +1624,7 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 		nulltest1->argisrow = false;
 		nulltest1->location = -1;
 	}
-	else
+	else if(!is_def && list_has_null)
 	{
 		/*
 		 * Gin up a col IS NULL test that will be OR'd with other expressions
@@ -1373,13 +1643,13 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 		: key->parttypid[0];
 	arr->array_collid = key->parttypcoll[0];
 	arr->element_typeid = key->parttypid[0];
-	arr->elements = spec->listdatums;
+	arr->elements = boundspecs;
 	arr->multidims = false;
 	arr->location = -1;
 
 	/* Generate the main expression, i.e., keyCol = ANY (arr) */
 	opexpr = make_partition_op_expr(key, 0, BTEqualStrategyNumber,
-									keyCol, (Expr *) arr);
+									keyCol, (Expr *) arr, is_def);
 
 	if (nulltest1)
 		result = list_make2(nulltest1, opexpr);
@@ -1594,7 +1864,7 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 		oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
 		test_expr = make_partition_op_expr(key, i, BTEqualStrategyNumber,
 										   (Expr *) lower_val,
-										   (Expr *) upper_val);
+										   (Expr *) upper_val, false);
 		fix_opfuncids((Node *) test_expr);
 		test_exprstate = ExecInitExpr(test_expr, NULL);
 		test_result = ExecEvalExprSwitchContext(test_exprstate,
@@ -1618,7 +1888,8 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 		/* Equal, so generate keyCol = lower_val expression */
 		result = lappend(result,
 						 make_partition_op_expr(key, i, BTEqualStrategyNumber,
-												keyCol, (Expr *) lower_val));
+												keyCol, (Expr *) lower_val,
+												false));
 
 		i++;
 	}
@@ -1676,7 +1947,8 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 											make_partition_op_expr(key, j,
 																   strategy,
 																   keyCol,
-														(Expr *) lower_val));
+														(Expr *) lower_val,
+																   false));
 			}
 
 			if (need_next_upper_arm && upper_val)
@@ -1698,7 +1970,8 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 											make_partition_op_expr(key, j,
 																   strategy,
 																   keyCol,
-														(Expr *) upper_val));
+														(Expr *) upper_val,
+																   false));
 
 			}
 
@@ -2034,10 +2307,25 @@ get_partition_for_tuple(PartitionDispatch *pd,
 		 */
 		if (cur_index < 0)
 		{
-			result = -1;
-			*failed_at = parent;
-			*failed_slot = slot;
-			break;
+			/*
+			 * If partitioned table has a default partition, return
+			 * its sequence number
+			 */
+			if (partdesc->boundinfo->has_default)
+			{
+				result = parent->indexes[partdesc->boundinfo->default_index];
+				if (result >= 0)
+					break;
+				else
+					parent = pd[-parent->indexes[partdesc->boundinfo->default_index]];
+			}
+			else
+			{
+				result = -1;
+				*failed_at = parent;
+				*failed_slot = slot;
+				break;
+			}
 		}
 		else if (parent->indexes[cur_index] >= 0)
 		{
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 2822331..3af286f 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -578,6 +578,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <node>		ForValues
 %type <node>		partbound_datum
 %type <list>		partbound_datum_list
+%type <list>		default_partition
 %type <partrange_datum>	PartitionRangeDatum
 %type <list>		range_datum_list
 
@@ -2676,6 +2677,17 @@ ForValues:
 
 					$$ = (Node *) n;
 				}
+			| default_partition
+				{
+					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+					n->strategy = PARTITION_DEFAULT;
+					n->listdatums = $1;
+					n->location = @1;
+
+					$$ = (Node *) n;
+				}
+
 		;
 
 partbound_datum:
@@ -2690,6 +2702,12 @@ partbound_datum_list:
 												{ $$ = lappend($1, $3); }
 		;
 
+default_partition:
+			DEFAULT	{
+						Node *def = (Node *)makeDefElem("DEFAULT", NULL, @1);
+						$$ = list_make1(def);
+					}
+
 range_datum_list:
 			PartitionRangeDatum					{ $$ = list_make1($1); }
 			| range_datum_list ',' PartitionRangeDatum
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index beb0995..306a5d1 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -3310,55 +3311,67 @@ transformPartitionBound(ParseState *pstate, Relation parent, Node *bound)
 										 false, false);
 
 		if (spec->strategy != PARTITION_STRATEGY_LIST)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
-				  errmsg("invalid bound specification for a list partition"),
+		{
+			/*
+			 * If the partition is the default partition switch
+			 * back to PARTITION_STRATEGY_LIST
+			 */
+			if (spec->strategy == PARTITION_DEFAULT)
+				result_spec->strategy = PARTITION_STRATEGY_LIST;
+			else
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("invalid bound specification for a list partition"),
 					 parser_errposition(pstate, exprLocation(bound))));
+		}
 
 		result_spec->listdatums = NIL;
 		foreach(cell, spec->listdatums)
 		{
-			A_Const    *con = (A_Const *) lfirst(cell);
-			Node	   *value;
-			ListCell   *cell2;
-			bool		duplicate;
-
-			value = (Node *) make_const(pstate, &con->val, con->location);
-			value = coerce_to_target_type(pstate,
-										  value, exprType(value),
-										  get_partition_col_typid(key, 0),
-										  get_partition_col_typmod(key, 0),
-										  COERCION_ASSIGNMENT,
-										  COERCE_IMPLICIT_CAST,
-										  -1);
-
-			if (value == NULL)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
-							 format_type_be(get_partition_col_typid(key, 0)),
-								colname),
-						 parser_errposition(pstate,
-											exprLocation((Node *) con))));
-
-			/* Simplify the expression */
-			value = (Node *) expression_planner((Expr *) value);
-
-			/* Don't add to the result if the value is a duplicate */
-			duplicate = false;
-			foreach(cell2, result_spec->listdatums)
+			Node *value = lfirst(cell);
+			/* Perform the transformation only for non default partition */
+			if (!(isDefaultPartitionBound(value)))
 			{
-				Const	   *value2 = (Const *) lfirst(cell2);
+				A_Const    *con = (A_Const *) lfirst(cell);
+				ListCell   *cell2;
+				bool		duplicate;
+
+				value = (Node *) make_const(pstate, &con->val, con->location);
+				value = coerce_to_target_type(pstate,
+											value, exprType(value),
+											get_partition_col_typid(key, 0),
+											get_partition_col_typmod(key, 0),
+											COERCION_ASSIGNMENT,
+											COERCE_IMPLICIT_CAST,
+											-1);
 
-				if (equal(value, value2))
+				if (value == NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+								format_type_be(get_partition_col_typid(key, 0)),
+									colname),
+							parser_errposition(pstate,
+												exprLocation((Node *) con))));
+
+				/* Simplify the expression */
+				value = (Node *) expression_planner((Expr *) value);
+
+				/* Don't add to the result if the value is a duplicate */
+				duplicate = false;
+				foreach(cell2, result_spec->listdatums)
 				{
-					duplicate = true;
-					break;
+					Const	   *value2 = (Const *) lfirst(cell2);
+
+					if (equal(value, value2))
+					{
+						duplicate = true;
+						break;
+					}
 				}
+				if (duplicate)
+					continue;
 			}
-			if (duplicate)
-				continue;
-
 			result_spec->listdatums = lappend(result_spec->listdatums,
 											  value);
 		}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 9234bc2..5bff62a 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8646,12 +8646,28 @@ get_rule_expr(Node *node, deparse_context *context,
 				PartitionBoundSpec *spec = (PartitionBoundSpec *) node;
 				ListCell   *cell;
 				char	   *sep;
+				bool       is_def = false;
 
 				switch (spec->strategy)
 				{
 					case PARTITION_STRATEGY_LIST:
 						Assert(spec->listdatums != NIL);
 
+						foreach(cell, spec->listdatums)
+						{
+							Node *value = lfirst(cell);
+
+							if (isDefaultPartitionBound(value))
+							{
+								appendStringInfoString(buf, "DEFAULT");
+								is_def = true;
+								break;
+							}
+						}
+
+						if (is_def)
+							break;
+
 						appendStringInfoString(buf, "FOR VALUES");
 						appendStringInfoString(buf, " IN (");
 						sep = "";
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 2abd087..b01ba07 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2044,7 +2044,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("ATTACH", "PARTITION", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 	else if (TailMatches2("FOR", "VALUES"))
 		COMPLETE_WITH_LIST2("FROM (", "IN (");
 
@@ -2483,7 +2483,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("PARTITION", "OF", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 
 /* CREATE TABLESPACE */
 	else if (Matches3("CREATE", "TABLESPACE", MatchAny))
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 25fb0a0..68f46fe 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -74,9 +74,12 @@ extern void RelationBuildPartitionDesc(Relation relation);
 extern bool partition_bounds_equal(PartitionKey key,
 					   PartitionBoundInfo p1, PartitionBoundInfo p2);
 
+extern bool isDefaultPartitionBound(Node *value);
 extern void check_new_partition_bound(char *relname, Relation parent, Node *bound);
 extern Oid	get_partition_parent(Oid relid);
 extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
+extern List *generate_qual_for_defaultpart(Relation parent, Node *bound, Oid *defid);
+extern List *get_qual_for_default(Relation parent, Oid *defid);
 extern List *map_partition_varattnos(List *expr, int target_varno,
 						Relation partrel, Relation parent);
 extern List *RelationGetPartitionQual(Relation rel);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 4b8727e..e632a42 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -780,6 +780,7 @@ typedef struct PartitionSpec
 
 #define PARTITION_STRATEGY_LIST		'l'
 #define PARTITION_STRATEGY_RANGE	'r'
+#define PARTITION_DEFAULT	'd'
 
 /*
  * PartitionBoundSpec - a partition bound specification
#94Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Beena Emerson (#93)
1 attachment(s)
Re: Adding support for Default partition in partitioning

Hi,

I started looking into Rahila's default_partition_v11.patch, and reworked on
few things as below:

- I tried to cover all the review comments posted on the thread. Do let
me know if something is missing.

- Got rid of the functions get_qual_for_default() and
generate_qual_for_defaultpart().
There is no need of collecting boundspecs of all the partitions in case of
list
partition, the list is available in boundinfo->ndatums, an expression for
default can be created from the information that is available in boundinfo.

- Got rid of variable has_default, and added a macro for it.

- Changed the logic of checking the overlapping of existing rows in default
partition. Earlier version of patch used to build new constraints for
default
partition table and then was checking if any of existing rows violate those
constraints. However, current version of patch just checks if any of the
rows in
default partition satisfy the new partition's constraint and fail if there
exists any.
This logic can also be used as it is for default partition in case of RANGE
partitioning.

- Simplified grammar rule.

- Got rid of PARTITION_DEFAULT since DEFAULT is not a different partition
strategy, the applicable logic is also revised:

- There are few other code adjustments like: indentation, commenting, code
simplification etc.

- Added regression tests.

TODO:
Documentation, I am working on it. Will updated the patch soon.

PFA.

Regards,
Jeevan

On Mon, May 22, 2017 at 7:31 AM, Beena Emerson <memissemerson@gmail.com>
wrote:

Show quoted text

Hello,

Patch for default range partition has been added. PFA the rebased v12
patch for the same.
I have not removed the has_default variable yet.

Default range partition: https://www.postgresql.org/message-id/
CAOG9ApEYj34fWMcvBMBQ-YtqR9fTdXhdN82QEKG0SVZ6zeL1xg%40mail.gmail.com
--

Beena Emerson

EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

Attachments:

default_partition_v13.patchapplication/octet-stream; name=default_partition_v13.patchDownload
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 7f2fd58..4972823 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -35,6 +35,7 @@
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planmain.h"
+#include "optimizer/prep.h"
 #include "optimizer/var.h"
 #include "rewrite/rewriteManip.h"
 #include "storage/lmgr.h"
@@ -88,9 +89,12 @@ typedef struct PartitionBoundInfoData
 								 * partitioned table) */
 	int			null_index;		/* Index of the null-accepting partition; -1
 								 * if there isn't one */
+	int			default_index;	/* Index of the default partition if any; -1
+								 * if there isn't one */
 } 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
@@ -121,14 +125,15 @@ static int32 qsort_partition_rbound_cmp(const void *a, const void *b,
 static Oid get_partition_operator(PartitionKey key, int col,
 					   StrategyNumber strategy, bool *need_relabel);
 static Expr *make_partition_op_expr(PartitionKey key, int keynum,
-					   uint16 strategy, Expr *arg1, Expr *arg2);
+					   uint16 strategy, Expr *arg1, Expr *arg2,
+					   bool is_default);
 static void get_range_key_properties(PartitionKey key, int keynum,
 						 PartitionRangeDatum *ldatum,
 						 PartitionRangeDatum *udatum,
 						 ListCell **partexprs_item,
 						 Expr **keyCol,
 						 Const **lower_val, Const **upper_val);
-static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_list(Relation parent, PartitionBoundSpec *spec);
 static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
 static List *generate_partition_qual(Relation rel);
 
@@ -147,6 +152,8 @@ static int32 partition_bound_cmp(PartitionKey key,
 static int partition_bound_bsearch(PartitionKey key,
 						PartitionBoundInfo boundinfo,
 						void *probe, bool probe_is_bound, bool *is_equal);
+static void check_default_allows_bound(Relation parent,
+						   PartitionBoundSpec *new_spec);
 
 /*
  * RelationBuildPartitionDesc
@@ -174,6 +181,7 @@ RelationBuildPartitionDesc(Relation rel)
 	/* List partitioning specific */
 	PartitionListValue **all_values = NULL;
 	int			null_index = -1;
+	int			default_index = -1;
 
 	/* Range partitioning specific */
 	PartitionRangeBound **rbounds = NULL;
@@ -258,6 +266,12 @@ RelationBuildPartitionDesc(Relation rel)
 					Const	   *val = lfirst(c);
 					PartitionListValue *list_value = NULL;
 
+					if (isDefaultPartitionBound((Node *) lfirst(c)))
+					{
+						default_index = i;
+						continue;
+					}
+
 					if (!val->constisnull)
 					{
 						list_value = (PartitionListValue *)
@@ -504,6 +518,17 @@ RelationBuildPartitionDesc(Relation rel)
 					else
 						boundinfo->null_index = -1;
 
+					/* Assign mapping index for 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];
+					}
+					else
+						boundinfo->default_index = -1;
+
 					/* All partition must now have a valid mapping */
 					Assert(next_index == nparts);
 					break;
@@ -559,6 +584,12 @@ RelationBuildPartitionDesc(Relation rel)
 						}
 					}
 					boundinfo->indexes[i] = -1;
+
+					/*
+					 * Currently range partition do not have default partition
+					 * support.
+					 */
+					boundinfo->default_index = -1;
 					break;
 				}
 
@@ -608,6 +639,9 @@ partition_bounds_equal(PartitionKey key,
 	if (b1->null_index != b2->null_index)
 		return false;
 
+	if (b1->default_index != b2->default_index)
+		return false;
+
 	for (i = 0; i < b1->ndatums; i++)
 	{
 		int			j;
@@ -670,6 +704,7 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
 	PartitionKey key = RelationGetPartitionKey(parent);
 	PartitionDesc partdesc = RelationGetPartitionDesc(parent);
+	PartitionBoundInfo boundinfo = partdesc->boundinfo;
 	ParseState *pstate = make_parsestate(NULL);
 	int			with = -1;
 	bool		overlap = false;
@@ -682,13 +717,29 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 
 				if (partdesc->nparts > 0)
 				{
-					PartitionBoundInfo boundinfo = partdesc->boundinfo;
 					ListCell   *cell;
 
 					Assert(boundinfo &&
 						   boundinfo->strategy == PARTITION_STRATEGY_LIST &&
 						   (boundinfo->ndatums > 0 ||
-							partition_bound_accepts_nulls(boundinfo)));
+							partition_bound_accepts_nulls(boundinfo) ||
+							partition_bound_has_default(boundinfo)));
+
+					/*
+					 * Default partition cannot be added if there already
+					 * exists one.
+					 */
+					cell = list_head(spec->listdatums);
+					if (cell && isDefaultPartitionBound((Node *) lfirst(cell)))
+					{
+						if (partition_bound_has_default(boundinfo))
+						{
+							overlap = true;
+							with = boundinfo->default_index;
+						}
+
+						break;
+					}
 
 					foreach(cell, spec->listdatums)
 					{
@@ -836,6 +887,139 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 						relname, get_rel_name(partdesc->oids[with])),
 				 parser_errposition(pstate, spec->location)));
 	}
+
+	/*
+	 * If a default partition exists, it's partition constraint will change
+	 * after the addition of this new partition such that it won't allow any
+	 * row that qualifies for this new partition. So, check if the existing
+	 * data in the default partition satisfies this *would be* default
+	 * partition constraint.
+	 */
+	if (partdesc->nparts > 0 && partition_bound_has_default(boundinfo))
+		check_default_allows_bound(parent, spec);
+}
+
+/*
+ * check_default_allows_bound
+ *
+ * Checks if there exists any row in default partition that passes the check
+ * for constraints of new partition, if any reports an error.
+ */
+static void
+check_default_allows_bound(Relation parent, PartitionBoundSpec *new_spec)
+{
+	Relation	default_rel;
+	int			default_index;
+	List	   *new_part_constraints = NIL;
+	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 = (List *) eval_const_expressions(NULL,
+											  (Node *) new_part_constraints);
+	new_part_constraints =
+		(List *) canonicalize_qual((Expr *) new_part_constraints);
+	new_part_constraints = list_make1(make_ands_explicit(new_part_constraints));
+
+	/* Generate the constraint and default execution states. */
+	default_index = parent->rd_partdesc->boundinfo->default_index;
+	default_rel = heap_open(parent->rd_partdesc->oids[default_index],
+							AccessExclusiveLock);
+
+	if (default_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		all_parts = find_all_inheritors(RelationGetRelid(default_rel),
+										AccessExclusiveLock, NULL);
+	else
+		all_parts = list_make1_oid(RelationGetRelid(default_rel));
+
+	foreach(lc, all_parts)
+	{
+		Oid			part_relid = lfirst_oid(lc);
+		Relation	part_rel;
+		Expr	   *constr;
+		Expr	   *partition_constraint;
+		EState	   *estate;
+		HeapTuple	tuple;
+		ExprState  *partqualstate = NULL;
+		Snapshot	snapshot;
+		TupleDesc	tupdesc;
+		ExprContext *econtext;
+		HeapScanDesc scan;
+		MemoryContext oldCxt;
+		TupleTableSlot *tupslot;
+
+		/* Lock already taken above. */
+		if (part_relid != RelationGetRelid(default_rel))
+			part_rel = heap_open(part_relid, NoLock);
+		else
+			part_rel = default_rel;
+
+		/*
+		 * Skip if it's a partitioned table. Only RELKIND_RELATION relations
+		 * (ie, leaf partitions) need to be scanned.
+		 */
+		if (part_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			heap_close(part_rel, NoLock);
+			continue;
+		}
+
+		tupdesc = CreateTupleDescCopy(RelationGetDescr(part_rel));
+		constr = linitial(new_part_constraints);
+		partition_constraint = (Expr *) map_partition_varattnos((List *) constr,
+														1, part_rel, parent);
+		estate = CreateExecutorState();
+
+		/* Build expression execution states for partition check quals */
+		partqualstate = ExecPrepareExpr(partition_constraint, estate);
+
+		econtext = GetPerTupleExprContext(estate);
+		snapshot = RegisterSnapshot(GetLatestSnapshot());
+		scan = heap_beginscan(part_rel, snapshot, 0, NULL);
+		tupslot = MakeSingleTupleTableSlot(tupdesc);
+
+		/*
+		 * Switch to per-tuple memory context and reset it for each tuple
+		 * produced, so we don't leak memory.
+		 */
+		oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		{
+			char	   *val_desc;
+
+			ExecStoreTuple(tuple, tupslot, InvalidBuffer, false);
+			econtext->ecxt_scantuple = tupslot;
+
+			if (partqualstate && ExecCheck(partqualstate, econtext))
+			{
+				val_desc =
+					ExecBuildSlotValueDescription(RelationGetRelid(part_rel),
+												  tupslot, tupdesc, NULL, 64);
+
+				ereport(ERROR,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("new default partition constraint is violated by some row"),
+						 val_desc ?
+					 errdetail("Violating row contains %s.", val_desc) : 0));
+			}
+
+			ResetExprContext(econtext);
+			CHECK_FOR_INTERRUPTS();
+		}
+
+		CacheInvalidateRelcache(part_rel);
+		MemoryContextSwitchTo(oldCxt);
+		heap_endscan(scan);
+		UnregisterSnapshot(snapshot);
+		ExecDropSingleTupleTableSlot(tupslot);
+		FreeExecutorState(estate);
+		heap_close(part_rel, NoLock);
+	}
 }
 
 /*
@@ -884,6 +1068,16 @@ get_partition_parent(Oid relid)
 }
 
 /*
+ * Returns true if the partition bound is default.
+ */
+bool
+isDefaultPartitionBound(Node *value)
+{
+	return (IsA(value, DefElem) &&
+			!strcmp(castNode(DefElem, value)->defname, "default_partition"));
+}
+
+/*
  * get_qual_from_partbound
  *		Given a parser node for partition bound, return the list of executable
  *		expressions as partition constraint
@@ -901,7 +1095,7 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
 	{
 		case PARTITION_STRATEGY_LIST:
 			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
-			my_qual = get_qual_for_list(key, spec);
+			my_qual = get_qual_for_list(parent, spec);
 			break;
 
 		case PARTITION_STRATEGY_RANGE:
@@ -1231,7 +1425,7 @@ get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
  */
 static Expr *
 make_partition_op_expr(PartitionKey key, int keynum,
-					   uint16 strategy, Expr *arg1, Expr *arg2)
+					uint16 strategy, Expr *arg1, Expr *arg2, bool is_default)
 {
 	Oid			operoid;
 	bool		need_relabel = false;
@@ -1261,11 +1455,17 @@ make_partition_op_expr(PartitionKey key, int keynum,
 			{
 				ScalarArrayOpExpr *saopexpr;
 
+				if (is_default &&
+					((operoid = get_negator(operoid)) == InvalidOid))
+					ereport(ERROR, (errcode(ERRCODE_RESTRICT_VIOLATION),
+									errmsg("DEFAULT partition cannot be used without negator of operator  %s",
+										   get_opname(operoid))));
+
 				/* Build leftop = ANY (rightop) */
 				saopexpr = makeNode(ScalarArrayOpExpr);
 				saopexpr->opno = operoid;
 				saopexpr->opfuncid = get_opcode(operoid);
-				saopexpr->useOr = true;
+				saopexpr->useOr = !is_default;
 				saopexpr->inputcollid = key->partcollation[keynum];
 				saopexpr->args = list_make2(arg1, arg2);
 				saopexpr->location = -1;
@@ -1297,8 +1497,9 @@ make_partition_op_expr(PartitionKey key, int keynum,
  * Returns a list of expressions to use as a list partition's constraint.
  */
 static List *
-get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
+get_qual_for_list(Relation parent, PartitionBoundSpec *spec)
 {
+	PartitionKey key = RelationGetPartitionKey(parent);
 	List	   *result;
 	ArrayExpr  *arr;
 	Expr	   *opexpr;
@@ -1309,6 +1510,8 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 	bool		list_has_null = false;
 	NullTest   *nulltest1 = NULL,
 			   *nulltest2 = NULL;
+	List	   *listdatums = spec->listdatums;
+	bool		is_default = isDefaultPartitionBound((Node *) linitial(listdatums));
 
 	/* Left operand is either a simple Var or arbitrary expression */
 	if (key->partattrs[0] != 0)
@@ -1322,11 +1525,52 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 		keyCol = (Expr *) copyObject(linitial(key->partexprs));
 
 	/*
+	 * In case of default partition for list, the partition constraint is
+	 * basically any value that is not equal to any of the values in
+	 * boundinfo->datums array. So, construct a list of constants from
+	 * boundinfo->datums to pass to function make_partition_op_expr via
+	 * ArrayExpr, which would return an negated expression for default
+	 * partition.
+	 */
+	if (is_default)
+	{
+		int			i;
+		MemoryContext oldcxt;
+
+		PartitionBoundInfo boundinfo =
+			RelationGetPartitionDesc(parent)->boundinfo;
+
+		oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+
+		listdatums = NIL;
+		for (i = 0; i < boundinfo->ndatums; i++)
+		{
+			Const	   *val;
+
+			/* Construct const from datum */
+			val = makeConst(key->parttypid[0],
+							key->parttypmod[0],
+							key->parttypcoll[0],
+							key->parttyplen[0],
+							*boundinfo->datums[i],
+							false,		/* isnull */
+							true /* byval */ );
+
+			listdatums = lappend(listdatums, val);
+		}
+
+		if (partition_bound_accepts_nulls(boundinfo))
+			list_has_null = true;
+
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	/*
 	 * We must remove any NULL value in the list; we handle it separately
 	 * below.
 	 */
 	prev = NULL;
-	for (cell = list_head(spec->listdatums); cell; cell = next)
+	for (cell = list_head(listdatums); cell && !is_default; cell = next)
 	{
 		Const	   *val = (Const *) lfirst(cell);
 
@@ -1335,14 +1579,13 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 		if (val->constisnull)
 		{
 			list_has_null = true;
-			spec->listdatums = list_delete_cell(spec->listdatums,
-												cell, prev);
+			listdatums = list_delete_cell(listdatums, cell, prev);
 		}
 		else
 			prev = cell;
 	}
 
-	if (!list_has_null)
+	if ((!list_has_null && !is_default) || (list_has_null && is_default))
 	{
 		/*
 		 * Gin up a col IS NOT NULL test that will be AND'd with other
@@ -1373,13 +1616,13 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 		: key->parttypid[0];
 	arr->array_collid = key->parttypcoll[0];
 	arr->element_typeid = key->parttypid[0];
-	arr->elements = spec->listdatums;
+	arr->elements = listdatums;
 	arr->multidims = false;
 	arr->location = -1;
 
 	/* Generate the main expression, i.e., keyCol = ANY (arr) */
 	opexpr = make_partition_op_expr(key, 0, BTEqualStrategyNumber,
-									keyCol, (Expr *) arr);
+									keyCol, (Expr *) arr, is_default);
 
 	if (nulltest1)
 		result = list_make2(nulltest1, opexpr);
@@ -1593,7 +1836,7 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 		oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
 		test_expr = make_partition_op_expr(key, i, BTEqualStrategyNumber,
 										   (Expr *) lower_val,
-										   (Expr *) upper_val);
+										   (Expr *) upper_val, false);
 		fix_opfuncids((Node *) test_expr);
 		test_exprstate = ExecInitExpr(test_expr, NULL);
 		test_result = ExecEvalExprSwitchContext(test_exprstate,
@@ -1617,7 +1860,7 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 		/* Equal, so generate keyCol = lower_val expression */
 		result = lappend(result,
 						 make_partition_op_expr(key, i, BTEqualStrategyNumber,
-												keyCol, (Expr *) lower_val));
+										 keyCol, (Expr *) lower_val, false));
 
 		i++;
 	}
@@ -1675,7 +1918,8 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 											make_partition_op_expr(key, j,
 																   strategy,
 																   keyCol,
-														(Expr *) lower_val));
+														  (Expr *) lower_val,
+																   false));
 			}
 
 			if (need_next_upper_arm && upper_val)
@@ -1697,7 +1941,8 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 											make_partition_op_expr(key, j,
 																   strategy,
 																   keyCol,
-														(Expr *) upper_val));
+														  (Expr *) upper_val,
+																   false));
 
 			}
 
@@ -2033,10 +2278,26 @@ get_partition_for_tuple(PartitionDispatch *pd,
 		 */
 		if (cur_index < 0)
 		{
-			result = -1;
-			*failed_at = parent;
-			*failed_slot = slot;
-			break;
+			/*
+			 * If partitioned table has a default partition, return its
+			 * sequence number.
+			 */
+			if (partition_bound_has_default(partdesc->boundinfo))
+			{
+				result = parent->indexes[partdesc->boundinfo->default_index];
+
+				if (result >= 0)
+					break;
+				else
+					parent = pd[-result];
+			}
+			else
+			{
+				result = -1;
+				*failed_at = parent;
+				*failed_slot = slot;
+				break;
+			}
 		}
 		else if (parent->indexes[cur_index] >= 0)
 		{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4a899f1..dae592a 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -92,11 +92,6 @@ static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid,
 						  Bitmapset *modifiedCols,
 						  AclMode requiredPerms);
 static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
-static char *ExecBuildSlotValueDescription(Oid reloid,
-							  TupleTableSlot *slot,
-							  TupleDesc tupdesc,
-							  Bitmapset *modifiedCols,
-							  int maxfieldlen);
 static char *ExecBuildSlotPartitionKeyDescription(Relation rel,
 									 Datum *values,
 									 bool *isnull,
@@ -2174,7 +2169,7 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
  * column involved, that subset will be returned with a key identifying which
  * columns they are.
  */
-static char *
+char *
 ExecBuildSlotValueDescription(Oid reloid,
 							  TupleTableSlot *slot,
 							  TupleDesc tupdesc,
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 2822331..bb59bfc 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2676,6 +2676,23 @@ ForValues:
 
 					$$ = (Node *) n;
 				}
+
+			/*
+			 * A default partition, that can be partition of either LIST or RANGE
+			 * partitioned table.
+			 * Currently this is supported only for LIST partition.
+			 */
+			| DEFAULT
+				{
+					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+					n->listdatums =
+						list_make1(makeDefElem("default_partition", NULL, @1));
+					n->location = @1;
+
+					$$ = (Node *) n;
+				}
+
 		;
 
 partbound_datum:
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index beb0995..bbd95d6 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -3310,19 +3311,38 @@ transformPartitionBound(ParseState *pstate, Relation parent, Node *bound)
 										 false, false);
 
 		if (spec->strategy != PARTITION_STRATEGY_LIST)
-			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->listdatums) == 1) &&
+				isDefaultPartitionBound((Node *) linitial(spec->listdatums)))
+				result_spec->strategy = PARTITION_STRATEGY_LIST;
+			else
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				  errmsg("invalid bound specification for a list partition"),
-					 parser_errposition(pstate, exprLocation(bound))));
+						 parser_errposition(pstate, exprLocation(bound))));
+		}
 
 		result_spec->listdatums = NIL;
 		foreach(cell, spec->listdatums)
 		{
 			A_Const    *con = (A_Const *) lfirst(cell);
-			Node	   *value;
+			Node	   *value = lfirst(cell);
 			ListCell   *cell2;
 			bool		duplicate;
 
+			/* Perform the transformation only for non default partition. */
+			if (isDefaultPartitionBound(value))
+			{
+				result_spec->listdatums = lappend(result_spec->listdatums,
+												  value);
+				continue;
+			}
+
 			value = (Node *) make_const(pstate, &con->val, con->location);
 			value = coerce_to_target_type(pstate,
 										  value, exprType(value),
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 9234bc2..fffeb4f 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8652,6 +8652,18 @@ get_rule_expr(Node *node, deparse_context *context,
 					case PARTITION_STRATEGY_LIST:
 						Assert(spec->listdatums != NIL);
 
+						/*
+						 * If the boundspec is of Default partition, it does
+						 * not have list of datums, but has only one node to
+						 * indicate its a default partition.
+						 */
+						if (isDefaultPartitionBound(
+										(Node *) linitial(spec->listdatums)))
+						{
+							appendStringInfoString(buf, "DEFAULT");
+							break;
+						}
+
 						appendStringInfoString(buf, "FOR VALUES");
 						appendStringInfoString(buf, " IN (");
 						sep = "";
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 2abd087..b01ba07 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2044,7 +2044,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("ATTACH", "PARTITION", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 	else if (TailMatches2("FOR", "VALUES"))
 		COMPLETE_WITH_LIST2("FROM (", "IN (");
 
@@ -2483,7 +2483,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("PARTITION", "OF", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 
 /* CREATE TABLESPACE */
 	else if (Matches3("CREATE", "TABLESPACE", MatchAny))
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 25fb0a0..081c1e4 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -81,6 +81,7 @@ extern List *map_partition_varattnos(List *expr, int target_varno,
 						Relation partrel, Relation parent);
 extern List *RelationGetPartitionQual(Relation rel);
 extern Expr *get_partition_qual_relid(Oid relid);
+extern bool isDefaultPartitionBound(Node *value);
 
 /* For tuple routing */
 extern PartitionDispatch *RelationGetPartitionDispatchInfo(Relation rel,
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 8cc5f3a..8bd574c 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -537,5 +537,10 @@ extern void CheckCmdReplicaIdentity(Relation rel, CmdType cmd);
 
 extern void CheckSubscriptionRelkind(char relkind, const char *nspname,
 						 const char *relname);
+char *ExecBuildSlotValueDescription(Oid reloid,
+							  TupleTableSlot *slot,
+							  TupleDesc tupdesc,
+							  Bitmapset *modifiedCols,
+							  int maxfieldlen);
 
 #endif   /* EXECUTOR_H  */
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index c88fd76..c31db25 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3224,6 +3224,11 @@ SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::reg
 CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
 ERROR:  partition "fail_part" would overlap partition "part_1"
+-- attaching default partition overlaps if a default partition already exists
+CREATE TABLE part_def1 PARTITION OF list_parted DEFAULT;
+CREATE TABLE part_def2 (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION part_def2 DEFAULT;
+ERROR:  partition "part_def2" would overlap partition "part_def1"
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
 	a int,
@@ -3237,6 +3242,16 @@ ERROR:  partition constraint is violated by some row
 -- should be ok after deleting the bad row
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+-- check partition cannot be attached if default has some row for its values
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
+INSERT INTO list_parted2_def VALUES (11, 'z');
+CREATE TABLE part_3 (LIKE list_parted2);
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+ERROR:  new default partition constraint is violated by some row
+DETAIL:  Violating row contains (11, z).
+-- should be ok after deleting the bad row
+DELETE FROM list_parted2_def WHERE a = 11;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
 CREATE TABLE part_3_4 (
@@ -3289,9 +3304,22 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
 ERROR:  partition constraint is violated by some row
 -- delete the faulting row and also add a constraint to skip the scan
 DELETE FROM part_5_a WHERE a NOT IN (3);
-ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT NULL;
-ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
+ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5, 55)), ALTER a SET NOT NULL;
+ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5, 55);
 INFO:  partition constraint for table "part_5" is implied by existing constraints
+-- check that leaf partitons of default partition are scanned when
+-- attaching a partitioned table.
+ALTER TABLE part_5 DROP CONSTRAINT check_a;
+CREATE TABLE part5_def PARTITION OF part_5 DEFAULT PARTITION BY LIST(a);
+CREATE TABLE part5_def_p1 PARTITION OF part5_def FOR VALUES IN (44, 55);
+INSERT INTO part5_def_p1 VALUES (55, 'y');
+CREATE TABLE part5_p1 (LIKE part_5);
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+ERROR:  new default partition constraint is violated by some row
+DETAIL:  Violating row contains (55, y).
+-- should be ok after deleting the bad row
+DELETE FROM part5_def_p1 WHERE b = 'y';
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
 -- check that the table being attached is not already a partition
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 ERROR:  "part_2" is already a partition
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 39edf04..2588497 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -449,6 +449,7 @@ CREATE TABLE list_parted (
 CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
 CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
 CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
 ERROR:  syntax error at or near "int"
 LINE 1: ... fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
@@ -457,6 +458,8 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
 ERROR:  syntax error at or near "::"
 LINE 1: ...fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
                                                                 ^
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
+ERROR:  partition "fail_default_part" would overlap partition "part_default"
 -- syntax does not allow empty list of values for list partitions
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
 ERROR:  syntax error at or near ")"
@@ -493,6 +496,11 @@ 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);
@@ -544,10 +552,16 @@ CREATE TABLE list_parted2 (
 ) PARTITION BY LIST (a);
 CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
 CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
 ERROR:  partition "fail_part" would overlap partition "part_null_z"
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
 ERROR:  partition "fail_part" would overlap partition "part_ab"
+-- check default partition overlap
+INSERT INTO list_parted2 VALUES('X');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y');
+ERROR:  new default partition constraint is violated by some row
+DETAIL:  Violating row contains (X).
 CREATE TABLE range_parted2 (
 	a int
 ) PARTITION BY RANGE (a);
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 8b0752a..841f7c3 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -202,6 +202,7 @@ create table list_parted (
 create table part_aa_bb partition of list_parted FOR VALUES IN ('aa', 'bb');
 create table part_cc_dd partition of list_parted FOR VALUES IN ('cc', 'dd');
 create table part_null partition of list_parted FOR VALUES IN (null);
+create table part_default partition of list_parted default;
 -- fail
 insert into part_aa_bb values ('cc', 1);
 ERROR:  new row for relation "part_aa_bb" violates partition constraint
@@ -212,24 +213,46 @@ DETAIL:  Failing row contains (AAa, 1).
 insert into part_aa_bb values (null);
 ERROR:  new row for relation "part_aa_bb" violates partition constraint
 DETAIL:  Failing row contains (null, null).
+insert into part_default values ('aa', 2);
+ERROR:  new row for relation "part_default" violates partition constraint
+DETAIL:  Failing row contains (aa, 2).
+insert into part_default values (null, 2);
+ERROR:  new row for relation "part_default" violates partition constraint
+DETAIL:  Failing row contains (null, 2).
 -- ok
 insert into part_cc_dd values ('cC', 1);
 insert into part_null values (null, 0);
+insert into part_default values ('Zz', 2);
 -- check in case of multi-level partitioned table
 create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b);
 create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
 create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
+-- check in case of multi-level default partitioned table
+drop table part_default;
+create table part_default partition of list_parted default partition by range(b);
+create table part_default_p1 partition of part_default for values from (20) to (30);
+create table part_default_p2 partition of part_default for values from (30) to (40);
 -- fail
 insert into part_ee_ff1 values ('EE', 11);
 ERROR:  new row for relation "part_ee_ff1" violates partition constraint
 DETAIL:  Failing row contains (EE, 11).
+insert into part_default_p2 values ('gg', 43);
+ERROR:  new row for relation "part_default_p2" violates partition constraint
+DETAIL:  Failing row contains (gg, 43).
 -- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
 insert into part_ee_ff1 values ('cc', 1);
 ERROR:  new row for relation "part_ee_ff1" violates partition constraint
 DETAIL:  Failing row contains (cc, 1).
+insert into part_default values ('gg', 43);
+ERROR:  no partition of relation "part_default" found for row
+DETAIL:  Partition key of the failing row contains (b) = (43).
 -- ok
 insert into part_ee_ff1 values ('ff', 1);
 insert into part_ee_ff2 values ('ff', 11);
+insert into part_default_p1 values ('cd', 25);
+insert into part_default_p2 values ('gg', 35);
+insert into list_parted values ('ab', 21);
+drop table part_default;
 -- Check tuple routing for partitioned tables
 -- fail
 insert into range_parted values ('a', 0);
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index 9366f04..0f41e7e 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -218,5 +218,24 @@ 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;
+create table list_parted (
+	a text,
+	b int
+) partition by list (a);
+create table list_part1  partition of list_parted for values in ('a', 'b');
+create table list_default partition of list_parted default;
+insert into list_part1 values ('a', 1);
+insert into list_default values ('d', 10);
+-- fail
+update list_part1 set a = 'c' where a = 'a';
+ERROR:  new row for relation "list_part1" violates partition constraint
+DETAIL:  Failing row contains (c, 1).
+update list_default set a = 'a' where a = 'd';
+ERROR:  new row for relation "list_default" violates partition constraint
+DETAIL:  Failing row contains (a, 10).
+-- ok
+update list_part1 set a = 'b' where a = 'a';
+update list_default set a = 'x' where a = 'd';
 -- cleanup
 drop table range_parted;
+drop table list_parted;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index c0e2972..dab3cfa 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2075,6 +2075,10 @@ SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::reg
 -- check that the new partition won't overlap with an existing partition
 CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+-- attaching default partition overlaps if a default partition already exists
+CREATE TABLE part_def1 PARTITION OF list_parted DEFAULT;
+CREATE TABLE part_def2 (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION part_def2 DEFAULT;
 
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
@@ -2091,6 +2095,15 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 
+-- check partition cannot be attached if default has some row for its values
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
+INSERT INTO list_parted2_def VALUES (11, 'z');
+CREATE TABLE part_3 (LIKE list_parted2);
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+-- should be ok after deleting the bad row
+DELETE FROM list_parted2_def WHERE a = 11;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
 CREATE TABLE part_3_4 (
@@ -2149,9 +2162,20 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
 
 -- delete the faulting row and also add a constraint to skip the scan
 DELETE FROM part_5_a WHERE a NOT IN (3);
-ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT NULL;
-ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
-
+ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5, 55)), ALTER a SET NOT NULL;
+ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5, 55);
+
+-- check that leaf partitons of default partition are scanned when
+-- attaching a partitioned table.
+ALTER TABLE part_5 DROP CONSTRAINT check_a;
+CREATE TABLE part5_def PARTITION OF part_5 DEFAULT PARTITION BY LIST(a);
+CREATE TABLE part5_def_p1 PARTITION OF part5_def FOR VALUES IN (44, 55);
+INSERT INTO part5_def_p1 VALUES (55, 'y');
+CREATE TABLE part5_p1 (LIKE part_5);
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+-- should be ok after deleting the bad row
+DELETE FROM part5_def_p1 WHERE b = 'y';
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
 
 -- check that the table being attached is not already a partition
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 5a27743..31cbab1 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -439,8 +439,10 @@ CREATE TABLE list_parted (
 CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
 CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
 CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
 
 -- syntax does not allow empty list of values for list partitions
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
@@ -467,6 +469,8 @@ 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);
@@ -512,9 +516,13 @@ CREATE TABLE list_parted2 (
 ) PARTITION BY LIST (a);
 CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
 CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+-- check default partition overlap
+INSERT INTO list_parted2 VALUES('X');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y');
 
 CREATE TABLE range_parted2 (
 	a int
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index db8967b..a9f2289 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -118,27 +118,42 @@ create table list_parted (
 create table part_aa_bb partition of list_parted FOR VALUES IN ('aa', 'bb');
 create table part_cc_dd partition of list_parted FOR VALUES IN ('cc', 'dd');
 create table part_null partition of list_parted FOR VALUES IN (null);
+create table part_default partition of list_parted default;
 
 -- fail
 insert into part_aa_bb values ('cc', 1);
 insert into part_aa_bb values ('AAa', 1);
 insert into part_aa_bb values (null);
+insert into part_default values ('aa', 2);
+insert into part_default values (null, 2);
 -- ok
 insert into part_cc_dd values ('cC', 1);
 insert into part_null values (null, 0);
+insert into part_default values ('Zz', 2);
 
 -- check in case of multi-level partitioned table
 create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b);
 create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
 create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
+-- check in case of multi-level default partitioned table
+drop table part_default;
+create table part_default partition of list_parted default partition by range(b);
+create table part_default_p1 partition of part_default for values from (20) to (30);
+create table part_default_p2 partition of part_default for values from (30) to (40);
 
 -- fail
 insert into part_ee_ff1 values ('EE', 11);
+insert into part_default_p2 values ('gg', 43);
 -- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
 insert into part_ee_ff1 values ('cc', 1);
+insert into part_default values ('gg', 43);
 -- ok
 insert into part_ee_ff1 values ('ff', 1);
 insert into part_ee_ff2 values ('ff', 11);
+insert into part_default_p1 values ('cd', 25);
+insert into part_default_p2 values ('gg', 35);
+insert into list_parted values ('ab', 21);
+drop table part_default;
 
 -- Check tuple routing for partitioned tables
 
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 6637119..02840a4 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -125,5 +125,22 @@ update range_parted set b = b - 1 where b = 10;
 -- ok
 update range_parted set b = b + 1 where b = 10;
 
+create table list_parted (
+	a text,
+	b int
+) partition by list (a);
+create table list_part1  partition of list_parted for values in ('a', 'b');
+create table list_default partition of list_parted default;
+insert into list_part1 values ('a', 1);
+insert into list_default values ('d', 10);
+
+-- fail
+update list_part1 set a = 'c' where a = 'a';
+update list_default set a = 'a' where a = 'd';
+-- ok
+update list_part1 set a = 'b' where a = 'a';
+update list_default set a = 'x' where a = 'd';
+
 -- cleanup
 drop table range_parted;
+drop table list_parted;
#95Rajkumar Raghuwanshi
rajkumar.raghuwanshi@enterprisedb.com
In reply to: Jeevan Ladhe (#94)
Re: Adding support for Default partition in partitioning

On Thu, May 25, 2017 at 12:10 PM, Jeevan Ladhe <
jeevan.ladhe@enterprisedb.com> wrote:

PFA.

Hi

I have applied v13 patch, got a crash when trying to attach default temp
partition.

postgres=# CREATE TEMP TABLE temp_list_part (a int) PARTITION BY LIST (a);
CREATE TABLE
postgres=# CREATE TEMP TABLE temp_def_part (a int);
CREATE TABLE
postgres=# ALTER TABLE temp_list_part ATTACH PARTITION temp_def_part
DEFAULT;
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

#96Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Rajkumar Raghuwanshi (#95)
Re: Adding support for Default partition in partitioning

Hi Rajkumar,

postgres=# CREATE TEMP TABLE temp_list_part (a int) PARTITION BY LIST (a);

CREATE TABLE
postgres=# CREATE TEMP TABLE temp_def_part (a int);
CREATE TABLE
postgres=# ALTER TABLE temp_list_part ATTACH PARTITION temp_def_part
DEFAULT;
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 for reporting.
PFA patch that fixes above issue.

Regards,
Jeevan Ladhe

#97Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Jeevan Ladhe (#96)
1 attachment(s)
Re: Adding support for Default partition in partitioning

Forgot to attach the patch.
PFA.

On Thu, May 25, 2017 at 3:02 PM, Jeevan Ladhe <jeevan.ladhe@enterprisedb.com

Show quoted text

wrote:

Hi Rajkumar,

postgres=# CREATE TEMP TABLE temp_list_part (a int) PARTITION BY LIST (a);

CREATE TABLE
postgres=# CREATE TEMP TABLE temp_def_part (a int);
CREATE TABLE
postgres=# ALTER TABLE temp_list_part ATTACH PARTITION temp_def_part
DEFAULT;
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 for reporting.
PFA patch that fixes above issue.

Regards,
Jeevan Ladhe

Attachments:

default_partition_v14.patchapplication/octet-stream; name=default_partition_v14.patchDownload
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 7f2fd58..b8217b7 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -35,6 +35,7 @@
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planmain.h"
+#include "optimizer/prep.h"
 #include "optimizer/var.h"
 #include "rewrite/rewriteManip.h"
 #include "storage/lmgr.h"
@@ -88,9 +89,12 @@ typedef struct PartitionBoundInfoData
 								 * partitioned table) */
 	int			null_index;		/* Index of the null-accepting partition; -1
 								 * if there isn't one */
+	int			default_index;	/* Index of the default partition if any; -1
+								 * if there isn't one */
 } 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
@@ -121,14 +125,15 @@ static int32 qsort_partition_rbound_cmp(const void *a, const void *b,
 static Oid get_partition_operator(PartitionKey key, int col,
 					   StrategyNumber strategy, bool *need_relabel);
 static Expr *make_partition_op_expr(PartitionKey key, int keynum,
-					   uint16 strategy, Expr *arg1, Expr *arg2);
+					   uint16 strategy, Expr *arg1, Expr *arg2,
+					   bool is_default);
 static void get_range_key_properties(PartitionKey key, int keynum,
 						 PartitionRangeDatum *ldatum,
 						 PartitionRangeDatum *udatum,
 						 ListCell **partexprs_item,
 						 Expr **keyCol,
 						 Const **lower_val, Const **upper_val);
-static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_list(Relation parent, PartitionBoundSpec *spec);
 static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
 static List *generate_partition_qual(Relation rel);
 
@@ -147,6 +152,8 @@ static int32 partition_bound_cmp(PartitionKey key,
 static int partition_bound_bsearch(PartitionKey key,
 						PartitionBoundInfo boundinfo,
 						void *probe, bool probe_is_bound, bool *is_equal);
+static void check_default_allows_bound(Relation parent,
+						   PartitionBoundSpec *new_spec);
 
 /*
  * RelationBuildPartitionDesc
@@ -174,6 +181,7 @@ RelationBuildPartitionDesc(Relation rel)
 	/* List partitioning specific */
 	PartitionListValue **all_values = NULL;
 	int			null_index = -1;
+	int			default_index = -1;
 
 	/* Range partitioning specific */
 	PartitionRangeBound **rbounds = NULL;
@@ -258,6 +266,12 @@ RelationBuildPartitionDesc(Relation rel)
 					Const	   *val = lfirst(c);
 					PartitionListValue *list_value = NULL;
 
+					if (isDefaultPartitionBound((Node *) lfirst(c)))
+					{
+						default_index = i;
+						continue;
+					}
+
 					if (!val->constisnull)
 					{
 						list_value = (PartitionListValue *)
@@ -504,6 +518,17 @@ RelationBuildPartitionDesc(Relation rel)
 					else
 						boundinfo->null_index = -1;
 
+					/* Assign mapping index for 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];
+					}
+					else
+						boundinfo->default_index = -1;
+
 					/* All partition must now have a valid mapping */
 					Assert(next_index == nparts);
 					break;
@@ -559,6 +584,12 @@ RelationBuildPartitionDesc(Relation rel)
 						}
 					}
 					boundinfo->indexes[i] = -1;
+
+					/*
+					 * Currently range partition do not have default partition
+					 * support.
+					 */
+					boundinfo->default_index = -1;
 					break;
 				}
 
@@ -608,6 +639,9 @@ partition_bounds_equal(PartitionKey key,
 	if (b1->null_index != b2->null_index)
 		return false;
 
+	if (b1->default_index != b2->default_index)
+		return false;
+
 	for (i = 0; i < b1->ndatums; i++)
 	{
 		int			j;
@@ -670,6 +704,7 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
 	PartitionKey key = RelationGetPartitionKey(parent);
 	PartitionDesc partdesc = RelationGetPartitionDesc(parent);
+	PartitionBoundInfo boundinfo = partdesc->boundinfo;
 	ParseState *pstate = make_parsestate(NULL);
 	int			with = -1;
 	bool		overlap = false;
@@ -682,13 +717,29 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 
 				if (partdesc->nparts > 0)
 				{
-					PartitionBoundInfo boundinfo = partdesc->boundinfo;
 					ListCell   *cell;
 
 					Assert(boundinfo &&
 						   boundinfo->strategy == PARTITION_STRATEGY_LIST &&
 						   (boundinfo->ndatums > 0 ||
-							partition_bound_accepts_nulls(boundinfo)));
+							partition_bound_accepts_nulls(boundinfo) ||
+							partition_bound_has_default(boundinfo)));
+
+					/*
+					 * Default partition cannot be added if there already
+					 * exists one.
+					 */
+					cell = list_head(spec->listdatums);
+					if (cell && isDefaultPartitionBound((Node *) lfirst(cell)))
+					{
+						if (partition_bound_has_default(boundinfo))
+						{
+							overlap = true;
+							with = boundinfo->default_index;
+						}
+
+						break;
+					}
 
 					foreach(cell, spec->listdatums)
 					{
@@ -836,6 +887,139 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound)
 						relname, get_rel_name(partdesc->oids[with])),
 				 parser_errposition(pstate, spec->location)));
 	}
+
+	/*
+	 * If a default partition exists, it's partition constraint will change
+	 * after the addition of this new partition such that it won't allow any
+	 * row that qualifies for this new partition. So, check if the existing
+	 * data in the default partition satisfies this *would be* default
+	 * partition constraint.
+	 */
+	if (partdesc->nparts > 0 && partition_bound_has_default(boundinfo))
+		check_default_allows_bound(parent, spec);
+}
+
+/*
+ * check_default_allows_bound
+ *
+ * Checks if there exists any row in default partition that passes the check
+ * for constraints of new partition, if any reports an error.
+ */
+static void
+check_default_allows_bound(Relation parent, PartitionBoundSpec *new_spec)
+{
+	Relation	default_rel;
+	int			default_index;
+	List	   *new_part_constraints = NIL;
+	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 = (List *) eval_const_expressions(NULL,
+											  (Node *) new_part_constraints);
+	new_part_constraints =
+		(List *) canonicalize_qual((Expr *) new_part_constraints);
+	new_part_constraints = list_make1(make_ands_explicit(new_part_constraints));
+
+	/* Generate the constraint and default execution states. */
+	default_index = parent->rd_partdesc->boundinfo->default_index;
+	default_rel = heap_open(parent->rd_partdesc->oids[default_index],
+							AccessExclusiveLock);
+
+	if (default_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		all_parts = find_all_inheritors(RelationGetRelid(default_rel),
+										AccessExclusiveLock, NULL);
+	else
+		all_parts = list_make1_oid(RelationGetRelid(default_rel));
+
+	foreach(lc, all_parts)
+	{
+		Oid			part_relid = lfirst_oid(lc);
+		Relation	part_rel;
+		Expr	   *constr;
+		Expr	   *partition_constraint;
+		EState	   *estate;
+		HeapTuple	tuple;
+		ExprState  *partqualstate = NULL;
+		Snapshot	snapshot;
+		TupleDesc	tupdesc;
+		ExprContext *econtext;
+		HeapScanDesc scan;
+		MemoryContext oldCxt;
+		TupleTableSlot *tupslot;
+
+		/* Lock already taken above. */
+		if (part_relid != RelationGetRelid(default_rel))
+			part_rel = heap_open(part_relid, NoLock);
+		else
+			part_rel = default_rel;
+
+		/*
+		 * Skip if it's a partitioned table. Only RELKIND_RELATION relations
+		 * (ie, leaf partitions) need to be scanned.
+		 */
+		if (part_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			heap_close(part_rel, NoLock);
+			continue;
+		}
+
+		tupdesc = CreateTupleDescCopy(RelationGetDescr(part_rel));
+		constr = linitial(new_part_constraints);
+		partition_constraint = (Expr *) map_partition_varattnos((List *) constr,
+														1, part_rel, parent);
+		estate = CreateExecutorState();
+
+		/* Build expression execution states for partition check quals */
+		partqualstate = ExecPrepareExpr(partition_constraint, estate);
+
+		econtext = GetPerTupleExprContext(estate);
+		snapshot = RegisterSnapshot(GetLatestSnapshot());
+		scan = heap_beginscan(part_rel, snapshot, 0, NULL);
+		tupslot = MakeSingleTupleTableSlot(tupdesc);
+
+		/*
+		 * Switch to per-tuple memory context and reset it for each tuple
+		 * produced, so we don't leak memory.
+		 */
+		oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		{
+			char	   *val_desc;
+
+			ExecStoreTuple(tuple, tupslot, InvalidBuffer, false);
+			econtext->ecxt_scantuple = tupslot;
+
+			if (partqualstate && ExecCheck(partqualstate, econtext))
+			{
+				val_desc =
+					ExecBuildSlotValueDescription(RelationGetRelid(part_rel),
+												  tupslot, tupdesc, NULL, 64);
+
+				ereport(ERROR,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("new default partition constraint is violated by some row"),
+						 val_desc ?
+					 errdetail("Violating row contains %s.", val_desc) : 0));
+			}
+
+			ResetExprContext(econtext);
+			CHECK_FOR_INTERRUPTS();
+		}
+
+		CacheInvalidateRelcache(part_rel);
+		MemoryContextSwitchTo(oldCxt);
+		heap_endscan(scan);
+		UnregisterSnapshot(snapshot);
+		ExecDropSingleTupleTableSlot(tupslot);
+		FreeExecutorState(estate);
+		heap_close(part_rel, NoLock);
+	}
 }
 
 /*
@@ -884,6 +1068,16 @@ get_partition_parent(Oid relid)
 }
 
 /*
+ * Returns true if the partition bound is default.
+ */
+bool
+isDefaultPartitionBound(Node *value)
+{
+	return (IsA(value, DefElem) &&
+			!strcmp(castNode(DefElem, value)->defname, "default_partition"));
+}
+
+/*
  * get_qual_from_partbound
  *		Given a parser node for partition bound, return the list of executable
  *		expressions as partition constraint
@@ -901,7 +1095,7 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
 	{
 		case PARTITION_STRATEGY_LIST:
 			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
-			my_qual = get_qual_for_list(key, spec);
+			my_qual = get_qual_for_list(parent, spec);
 			break;
 
 		case PARTITION_STRATEGY_RANGE:
@@ -1231,7 +1425,7 @@ get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
  */
 static Expr *
 make_partition_op_expr(PartitionKey key, int keynum,
-					   uint16 strategy, Expr *arg1, Expr *arg2)
+					uint16 strategy, Expr *arg1, Expr *arg2, bool is_default)
 {
 	Oid			operoid;
 	bool		need_relabel = false;
@@ -1261,11 +1455,17 @@ make_partition_op_expr(PartitionKey key, int keynum,
 			{
 				ScalarArrayOpExpr *saopexpr;
 
+				if (is_default &&
+					((operoid = get_negator(operoid)) == InvalidOid))
+					ereport(ERROR, (errcode(ERRCODE_RESTRICT_VIOLATION),
+									errmsg("DEFAULT partition cannot be used without negator of operator  %s",
+										   get_opname(operoid))));
+
 				/* Build leftop = ANY (rightop) */
 				saopexpr = makeNode(ScalarArrayOpExpr);
 				saopexpr->opno = operoid;
 				saopexpr->opfuncid = get_opcode(operoid);
-				saopexpr->useOr = true;
+				saopexpr->useOr = !is_default;
 				saopexpr->inputcollid = key->partcollation[keynum];
 				saopexpr->args = list_make2(arg1, arg2);
 				saopexpr->location = -1;
@@ -1297,8 +1497,9 @@ make_partition_op_expr(PartitionKey key, int keynum,
  * Returns a list of expressions to use as a list partition's constraint.
  */
 static List *
-get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
+get_qual_for_list(Relation parent, PartitionBoundSpec *spec)
 {
+	PartitionKey key = RelationGetPartitionKey(parent);
 	List	   *result;
 	ArrayExpr  *arr;
 	Expr	   *opexpr;
@@ -1309,6 +1510,9 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 	bool		list_has_null = false;
 	NullTest   *nulltest1 = NULL,
 			   *nulltest2 = NULL;
+	List	   *listdatums = spec->listdatums;
+	bool		is_default =
+		isDefaultPartitionBound((Node *) linitial(listdatums));
 
 	/* Left operand is either a simple Var or arbitrary expression */
 	if (key->partattrs[0] != 0)
@@ -1322,11 +1526,54 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 		keyCol = (Expr *) copyObject(linitial(key->partexprs));
 
 	/*
+	 * In case of default partition for list, the partition constraint is
+	 * basically any value that is not equal to any of the values in
+	 * boundinfo->datums array. So, construct a list of constants from
+	 * boundinfo->datums to pass to function make_partition_op_expr via
+	 * ArrayExpr, which would return an negated expression for default
+	 * partition.
+	 */
+	if (is_default)
+	{
+		int			i;
+		int			ndatums = 0;
+		MemoryContext oldcxt;
+		PartitionDesc pdesc = RelationGetPartitionDesc(parent);
+		PartitionBoundInfo boundinfo = pdesc->boundinfo;
+
+		ndatums = (pdesc->nparts > 0) ? boundinfo->ndatums : 0;
+
+		oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+
+		listdatums = NIL;
+		for (i = 0; i < ndatums; i++)
+		{
+			Const	   *val;
+
+			/* Construct const from datum */
+			val = makeConst(key->parttypid[0],
+							key->parttypmod[0],
+							key->parttypcoll[0],
+							key->parttyplen[0],
+							*boundinfo->datums[i],
+							false,		/* isnull */
+							true /* byval */ );
+
+			listdatums = lappend(listdatums, val);
+		}
+
+		if (boundinfo && partition_bound_accepts_nulls(boundinfo))
+			list_has_null = true;
+
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	/*
 	 * We must remove any NULL value in the list; we handle it separately
 	 * below.
 	 */
 	prev = NULL;
-	for (cell = list_head(spec->listdatums); cell; cell = next)
+	for (cell = list_head(listdatums); cell && !is_default; cell = next)
 	{
 		Const	   *val = (Const *) lfirst(cell);
 
@@ -1335,14 +1582,13 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 		if (val->constisnull)
 		{
 			list_has_null = true;
-			spec->listdatums = list_delete_cell(spec->listdatums,
-												cell, prev);
+			listdatums = list_delete_cell(listdatums, cell, prev);
 		}
 		else
 			prev = cell;
 	}
 
-	if (!list_has_null)
+	if ((!list_has_null && !is_default) || (list_has_null && is_default))
 	{
 		/*
 		 * Gin up a col IS NOT NULL test that will be AND'd with other
@@ -1373,13 +1619,13 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 		: key->parttypid[0];
 	arr->array_collid = key->parttypcoll[0];
 	arr->element_typeid = key->parttypid[0];
-	arr->elements = spec->listdatums;
+	arr->elements = listdatums;
 	arr->multidims = false;
 	arr->location = -1;
 
 	/* Generate the main expression, i.e., keyCol = ANY (arr) */
 	opexpr = make_partition_op_expr(key, 0, BTEqualStrategyNumber,
-									keyCol, (Expr *) arr);
+									keyCol, (Expr *) arr, is_default);
 
 	if (nulltest1)
 		result = list_make2(nulltest1, opexpr);
@@ -1593,7 +1839,7 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 		oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
 		test_expr = make_partition_op_expr(key, i, BTEqualStrategyNumber,
 										   (Expr *) lower_val,
-										   (Expr *) upper_val);
+										   (Expr *) upper_val, false);
 		fix_opfuncids((Node *) test_expr);
 		test_exprstate = ExecInitExpr(test_expr, NULL);
 		test_result = ExecEvalExprSwitchContext(test_exprstate,
@@ -1617,7 +1863,7 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 		/* Equal, so generate keyCol = lower_val expression */
 		result = lappend(result,
 						 make_partition_op_expr(key, i, BTEqualStrategyNumber,
-												keyCol, (Expr *) lower_val));
+										 keyCol, (Expr *) lower_val, false));
 
 		i++;
 	}
@@ -1675,7 +1921,8 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 											make_partition_op_expr(key, j,
 																   strategy,
 																   keyCol,
-														(Expr *) lower_val));
+														  (Expr *) lower_val,
+																   false));
 			}
 
 			if (need_next_upper_arm && upper_val)
@@ -1697,7 +1944,8 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 											make_partition_op_expr(key, j,
 																   strategy,
 																   keyCol,
-														(Expr *) upper_val));
+														  (Expr *) upper_val,
+																   false));
 
 			}
 
@@ -2033,10 +2281,26 @@ get_partition_for_tuple(PartitionDispatch *pd,
 		 */
 		if (cur_index < 0)
 		{
-			result = -1;
-			*failed_at = parent;
-			*failed_slot = slot;
-			break;
+			/*
+			 * If partitioned table has a default partition, return its
+			 * sequence number.
+			 */
+			if (partition_bound_has_default(partdesc->boundinfo))
+			{
+				result = parent->indexes[partdesc->boundinfo->default_index];
+
+				if (result >= 0)
+					break;
+				else
+					parent = pd[-result];
+			}
+			else
+			{
+				result = -1;
+				*failed_at = parent;
+				*failed_slot = slot;
+				break;
+			}
 		}
 		else if (parent->indexes[cur_index] >= 0)
 		{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4a899f1..dae592a 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -92,11 +92,6 @@ static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid,
 						  Bitmapset *modifiedCols,
 						  AclMode requiredPerms);
 static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
-static char *ExecBuildSlotValueDescription(Oid reloid,
-							  TupleTableSlot *slot,
-							  TupleDesc tupdesc,
-							  Bitmapset *modifiedCols,
-							  int maxfieldlen);
 static char *ExecBuildSlotPartitionKeyDescription(Relation rel,
 									 Datum *values,
 									 bool *isnull,
@@ -2174,7 +2169,7 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
  * column involved, that subset will be returned with a key identifying which
  * columns they are.
  */
-static char *
+char *
 ExecBuildSlotValueDescription(Oid reloid,
 							  TupleTableSlot *slot,
 							  TupleDesc tupdesc,
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 2822331..bb59bfc 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2676,6 +2676,23 @@ ForValues:
 
 					$$ = (Node *) n;
 				}
+
+			/*
+			 * A default partition, that can be partition of either LIST or RANGE
+			 * partitioned table.
+			 * Currently this is supported only for LIST partition.
+			 */
+			| DEFAULT
+				{
+					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+					n->listdatums =
+						list_make1(makeDefElem("default_partition", NULL, @1));
+					n->location = @1;
+
+					$$ = (Node *) n;
+				}
+
 		;
 
 partbound_datum:
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index beb0995..bbd95d6 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -3310,19 +3311,38 @@ transformPartitionBound(ParseState *pstate, Relation parent, Node *bound)
 										 false, false);
 
 		if (spec->strategy != PARTITION_STRATEGY_LIST)
-			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->listdatums) == 1) &&
+				isDefaultPartitionBound((Node *) linitial(spec->listdatums)))
+				result_spec->strategy = PARTITION_STRATEGY_LIST;
+			else
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				  errmsg("invalid bound specification for a list partition"),
-					 parser_errposition(pstate, exprLocation(bound))));
+						 parser_errposition(pstate, exprLocation(bound))));
+		}
 
 		result_spec->listdatums = NIL;
 		foreach(cell, spec->listdatums)
 		{
 			A_Const    *con = (A_Const *) lfirst(cell);
-			Node	   *value;
+			Node	   *value = lfirst(cell);
 			ListCell   *cell2;
 			bool		duplicate;
 
+			/* Perform the transformation only for non default partition. */
+			if (isDefaultPartitionBound(value))
+			{
+				result_spec->listdatums = lappend(result_spec->listdatums,
+												  value);
+				continue;
+			}
+
 			value = (Node *) make_const(pstate, &con->val, con->location);
 			value = coerce_to_target_type(pstate,
 										  value, exprType(value),
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 9234bc2..fffeb4f 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8652,6 +8652,18 @@ get_rule_expr(Node *node, deparse_context *context,
 					case PARTITION_STRATEGY_LIST:
 						Assert(spec->listdatums != NIL);
 
+						/*
+						 * If the boundspec is of Default partition, it does
+						 * not have list of datums, but has only one node to
+						 * indicate its a default partition.
+						 */
+						if (isDefaultPartitionBound(
+										(Node *) linitial(spec->listdatums)))
+						{
+							appendStringInfoString(buf, "DEFAULT");
+							break;
+						}
+
 						appendStringInfoString(buf, "FOR VALUES");
 						appendStringInfoString(buf, " IN (");
 						sep = "";
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 2abd087..b01ba07 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2044,7 +2044,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("ATTACH", "PARTITION", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 	else if (TailMatches2("FOR", "VALUES"))
 		COMPLETE_WITH_LIST2("FROM (", "IN (");
 
@@ -2483,7 +2483,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("PARTITION", "OF", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 
 /* CREATE TABLESPACE */
 	else if (Matches3("CREATE", "TABLESPACE", MatchAny))
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 25fb0a0..081c1e4 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -81,6 +81,7 @@ extern List *map_partition_varattnos(List *expr, int target_varno,
 						Relation partrel, Relation parent);
 extern List *RelationGetPartitionQual(Relation rel);
 extern Expr *get_partition_qual_relid(Oid relid);
+extern bool isDefaultPartitionBound(Node *value);
 
 /* For tuple routing */
 extern PartitionDispatch *RelationGetPartitionDispatchInfo(Relation rel,
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 8cc5f3a..8bd574c 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -537,5 +537,10 @@ extern void CheckCmdReplicaIdentity(Relation rel, CmdType cmd);
 
 extern void CheckSubscriptionRelkind(char relkind, const char *nspname,
 						 const char *relname);
+char *ExecBuildSlotValueDescription(Oid reloid,
+							  TupleTableSlot *slot,
+							  TupleDesc tupdesc,
+							  Bitmapset *modifiedCols,
+							  int maxfieldlen);
 
 #endif   /* EXECUTOR_H  */
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index c88fd76..c31db25 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3224,6 +3224,11 @@ SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::reg
 CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
 ERROR:  partition "fail_part" would overlap partition "part_1"
+-- attaching default partition overlaps if a default partition already exists
+CREATE TABLE part_def1 PARTITION OF list_parted DEFAULT;
+CREATE TABLE part_def2 (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION part_def2 DEFAULT;
+ERROR:  partition "part_def2" would overlap partition "part_def1"
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
 	a int,
@@ -3237,6 +3242,16 @@ ERROR:  partition constraint is violated by some row
 -- should be ok after deleting the bad row
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+-- check partition cannot be attached if default has some row for its values
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
+INSERT INTO list_parted2_def VALUES (11, 'z');
+CREATE TABLE part_3 (LIKE list_parted2);
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+ERROR:  new default partition constraint is violated by some row
+DETAIL:  Violating row contains (11, z).
+-- should be ok after deleting the bad row
+DELETE FROM list_parted2_def WHERE a = 11;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
 CREATE TABLE part_3_4 (
@@ -3289,9 +3304,22 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
 ERROR:  partition constraint is violated by some row
 -- delete the faulting row and also add a constraint to skip the scan
 DELETE FROM part_5_a WHERE a NOT IN (3);
-ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT NULL;
-ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
+ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5, 55)), ALTER a SET NOT NULL;
+ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5, 55);
 INFO:  partition constraint for table "part_5" is implied by existing constraints
+-- check that leaf partitons of default partition are scanned when
+-- attaching a partitioned table.
+ALTER TABLE part_5 DROP CONSTRAINT check_a;
+CREATE TABLE part5_def PARTITION OF part_5 DEFAULT PARTITION BY LIST(a);
+CREATE TABLE part5_def_p1 PARTITION OF part5_def FOR VALUES IN (44, 55);
+INSERT INTO part5_def_p1 VALUES (55, 'y');
+CREATE TABLE part5_p1 (LIKE part_5);
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+ERROR:  new default partition constraint is violated by some row
+DETAIL:  Violating row contains (55, y).
+-- should be ok after deleting the bad row
+DELETE FROM part5_def_p1 WHERE b = 'y';
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
 -- check that the table being attached is not already a partition
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 ERROR:  "part_2" is already a partition
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 39edf04..2588497 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -449,6 +449,7 @@ CREATE TABLE list_parted (
 CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
 CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
 CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
 ERROR:  syntax error at or near "int"
 LINE 1: ... fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
@@ -457,6 +458,8 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
 ERROR:  syntax error at or near "::"
 LINE 1: ...fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
                                                                 ^
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
+ERROR:  partition "fail_default_part" would overlap partition "part_default"
 -- syntax does not allow empty list of values for list partitions
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
 ERROR:  syntax error at or near ")"
@@ -493,6 +496,11 @@ 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);
@@ -544,10 +552,16 @@ CREATE TABLE list_parted2 (
 ) PARTITION BY LIST (a);
 CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
 CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
 ERROR:  partition "fail_part" would overlap partition "part_null_z"
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
 ERROR:  partition "fail_part" would overlap partition "part_ab"
+-- check default partition overlap
+INSERT INTO list_parted2 VALUES('X');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y');
+ERROR:  new default partition constraint is violated by some row
+DETAIL:  Violating row contains (X).
 CREATE TABLE range_parted2 (
 	a int
 ) PARTITION BY RANGE (a);
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 8b0752a..841f7c3 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -202,6 +202,7 @@ create table list_parted (
 create table part_aa_bb partition of list_parted FOR VALUES IN ('aa', 'bb');
 create table part_cc_dd partition of list_parted FOR VALUES IN ('cc', 'dd');
 create table part_null partition of list_parted FOR VALUES IN (null);
+create table part_default partition of list_parted default;
 -- fail
 insert into part_aa_bb values ('cc', 1);
 ERROR:  new row for relation "part_aa_bb" violates partition constraint
@@ -212,24 +213,46 @@ DETAIL:  Failing row contains (AAa, 1).
 insert into part_aa_bb values (null);
 ERROR:  new row for relation "part_aa_bb" violates partition constraint
 DETAIL:  Failing row contains (null, null).
+insert into part_default values ('aa', 2);
+ERROR:  new row for relation "part_default" violates partition constraint
+DETAIL:  Failing row contains (aa, 2).
+insert into part_default values (null, 2);
+ERROR:  new row for relation "part_default" violates partition constraint
+DETAIL:  Failing row contains (null, 2).
 -- ok
 insert into part_cc_dd values ('cC', 1);
 insert into part_null values (null, 0);
+insert into part_default values ('Zz', 2);
 -- check in case of multi-level partitioned table
 create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b);
 create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
 create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
+-- check in case of multi-level default partitioned table
+drop table part_default;
+create table part_default partition of list_parted default partition by range(b);
+create table part_default_p1 partition of part_default for values from (20) to (30);
+create table part_default_p2 partition of part_default for values from (30) to (40);
 -- fail
 insert into part_ee_ff1 values ('EE', 11);
 ERROR:  new row for relation "part_ee_ff1" violates partition constraint
 DETAIL:  Failing row contains (EE, 11).
+insert into part_default_p2 values ('gg', 43);
+ERROR:  new row for relation "part_default_p2" violates partition constraint
+DETAIL:  Failing row contains (gg, 43).
 -- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
 insert into part_ee_ff1 values ('cc', 1);
 ERROR:  new row for relation "part_ee_ff1" violates partition constraint
 DETAIL:  Failing row contains (cc, 1).
+insert into part_default values ('gg', 43);
+ERROR:  no partition of relation "part_default" found for row
+DETAIL:  Partition key of the failing row contains (b) = (43).
 -- ok
 insert into part_ee_ff1 values ('ff', 1);
 insert into part_ee_ff2 values ('ff', 11);
+insert into part_default_p1 values ('cd', 25);
+insert into part_default_p2 values ('gg', 35);
+insert into list_parted values ('ab', 21);
+drop table part_default;
 -- Check tuple routing for partitioned tables
 -- fail
 insert into range_parted values ('a', 0);
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index 9366f04..0f41e7e 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -218,5 +218,24 @@ 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;
+create table list_parted (
+	a text,
+	b int
+) partition by list (a);
+create table list_part1  partition of list_parted for values in ('a', 'b');
+create table list_default partition of list_parted default;
+insert into list_part1 values ('a', 1);
+insert into list_default values ('d', 10);
+-- fail
+update list_part1 set a = 'c' where a = 'a';
+ERROR:  new row for relation "list_part1" violates partition constraint
+DETAIL:  Failing row contains (c, 1).
+update list_default set a = 'a' where a = 'd';
+ERROR:  new row for relation "list_default" violates partition constraint
+DETAIL:  Failing row contains (a, 10).
+-- ok
+update list_part1 set a = 'b' where a = 'a';
+update list_default set a = 'x' where a = 'd';
 -- cleanup
 drop table range_parted;
+drop table list_parted;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index c0e2972..dab3cfa 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2075,6 +2075,10 @@ SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::reg
 -- check that the new partition won't overlap with an existing partition
 CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+-- attaching default partition overlaps if a default partition already exists
+CREATE TABLE part_def1 PARTITION OF list_parted DEFAULT;
+CREATE TABLE part_def2 (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION part_def2 DEFAULT;
 
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
@@ -2091,6 +2095,15 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 
+-- check partition cannot be attached if default has some row for its values
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
+INSERT INTO list_parted2_def VALUES (11, 'z');
+CREATE TABLE part_3 (LIKE list_parted2);
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+-- should be ok after deleting the bad row
+DELETE FROM list_parted2_def WHERE a = 11;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
 CREATE TABLE part_3_4 (
@@ -2149,9 +2162,20 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
 
 -- delete the faulting row and also add a constraint to skip the scan
 DELETE FROM part_5_a WHERE a NOT IN (3);
-ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT NULL;
-ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
-
+ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5, 55)), ALTER a SET NOT NULL;
+ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5, 55);
+
+-- check that leaf partitons of default partition are scanned when
+-- attaching a partitioned table.
+ALTER TABLE part_5 DROP CONSTRAINT check_a;
+CREATE TABLE part5_def PARTITION OF part_5 DEFAULT PARTITION BY LIST(a);
+CREATE TABLE part5_def_p1 PARTITION OF part5_def FOR VALUES IN (44, 55);
+INSERT INTO part5_def_p1 VALUES (55, 'y');
+CREATE TABLE part5_p1 (LIKE part_5);
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+-- should be ok after deleting the bad row
+DELETE FROM part5_def_p1 WHERE b = 'y';
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
 
 -- check that the table being attached is not already a partition
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 5a27743..31cbab1 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -439,8 +439,10 @@ CREATE TABLE list_parted (
 CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
 CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
 CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
 
 -- syntax does not allow empty list of values for list partitions
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
@@ -467,6 +469,8 @@ 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);
@@ -512,9 +516,13 @@ CREATE TABLE list_parted2 (
 ) PARTITION BY LIST (a);
 CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
 CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+-- check default partition overlap
+INSERT INTO list_parted2 VALUES('X');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y');
 
 CREATE TABLE range_parted2 (
 	a int
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index db8967b..a9f2289 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -118,27 +118,42 @@ create table list_parted (
 create table part_aa_bb partition of list_parted FOR VALUES IN ('aa', 'bb');
 create table part_cc_dd partition of list_parted FOR VALUES IN ('cc', 'dd');
 create table part_null partition of list_parted FOR VALUES IN (null);
+create table part_default partition of list_parted default;
 
 -- fail
 insert into part_aa_bb values ('cc', 1);
 insert into part_aa_bb values ('AAa', 1);
 insert into part_aa_bb values (null);
+insert into part_default values ('aa', 2);
+insert into part_default values (null, 2);
 -- ok
 insert into part_cc_dd values ('cC', 1);
 insert into part_null values (null, 0);
+insert into part_default values ('Zz', 2);
 
 -- check in case of multi-level partitioned table
 create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b);
 create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
 create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
+-- check in case of multi-level default partitioned table
+drop table part_default;
+create table part_default partition of list_parted default partition by range(b);
+create table part_default_p1 partition of part_default for values from (20) to (30);
+create table part_default_p2 partition of part_default for values from (30) to (40);
 
 -- fail
 insert into part_ee_ff1 values ('EE', 11);
+insert into part_default_p2 values ('gg', 43);
 -- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
 insert into part_ee_ff1 values ('cc', 1);
+insert into part_default values ('gg', 43);
 -- ok
 insert into part_ee_ff1 values ('ff', 1);
 insert into part_ee_ff2 values ('ff', 11);
+insert into part_default_p1 values ('cd', 25);
+insert into part_default_p2 values ('gg', 35);
+insert into list_parted values ('ab', 21);
+drop table part_default;
 
 -- Check tuple routing for partitioned tables
 
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 6637119..02840a4 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -125,5 +125,22 @@ update range_parted set b = b - 1 where b = 10;
 -- ok
 update range_parted set b = b + 1 where b = 10;
 
+create table list_parted (
+	a text,
+	b int
+) partition by list (a);
+create table list_part1  partition of list_parted for values in ('a', 'b');
+create table list_default partition of list_parted default;
+insert into list_part1 values ('a', 1);
+insert into list_default values ('d', 10);
+
+-- fail
+update list_part1 set a = 'c' where a = 'a';
+update list_default set a = 'a' where a = 'd';
+-- ok
+update list_part1 set a = 'b' where a = 'a';
+update list_default set a = 'x' where a = 'd';
+
 -- cleanup
 drop table range_parted;
+drop table list_parted;
#98Beena Emerson
memissemerson@gmail.com
In reply to: Jeevan Ladhe (#97)
Re: Adding support for Default partition in partitioning

On Thu, May 25, 2017 at 3:03 PM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

Forgot to attach the patch.
PFA.

On Thu, May 25, 2017 at 3:02 PM, Jeevan Ladhe <jeevan.ladhe@enterprisedb.com> wrote:

Hi Rajkumar,

postgres=# CREATE TEMP TABLE temp_list_part (a int) PARTITION BY LIST (a);
CREATE TABLE
postgres=# CREATE TEMP TABLE temp_def_part (a int);
CREATE TABLE
postgres=# ALTER TABLE temp_list_part ATTACH PARTITION temp_def_part DEFAULT;
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 for reporting.
PFA patch that fixes above issue.

The existing comment is not valid
/*
* A null partition key is only acceptable if null-accepting list
* partition exists.
*/
as we allow NULL to be stored in default. It should be updated.

DROP TABLE list1;
CREATE TABLE list1 ( a int) PARTITION BY LIST (a);
CREATE TABLE list1_1 (LIKE list1);
ALTER TABLE list1 ATTACH PARTITION list1_1 FOR VALUES IN (2);
CREATE TABLE list1_def PARTITION OF list1 DEFAULT;
INSERT INTO list1 VALUES (NULL);
SELECT * FROM list1_def;
a
---

(1 row)

--

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

#99Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Beena Emerson (#98)
Re: Adding support for Default partition in partitioning

This patch needs a rebase on recent commits, and also a fix[1] that is
posted for get_qual_for_list().

I am working on both of these tasks. Will update the patch once I am done
with this.

Regards,

Jeevan Ladhe

On Mon, May 29, 2017 at 12:25 PM, Beena Emerson <memissemerson@gmail.com>
wrote:

Show quoted text

On Thu, May 25, 2017 at 3:03 PM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

Forgot to attach the patch.
PFA.

On Thu, May 25, 2017 at 3:02 PM, Jeevan Ladhe <

jeevan.ladhe@enterprisedb.com> wrote:

Hi Rajkumar,

postgres=# CREATE TEMP TABLE temp_list_part (a int) PARTITION BY LIST

(a);

CREATE TABLE
postgres=# CREATE TEMP TABLE temp_def_part (a int);
CREATE TABLE
postgres=# ALTER TABLE temp_list_part ATTACH PARTITION temp_def_part

DEFAULT;

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 for reporting.
PFA patch that fixes above issue.

The existing comment is not valid
/*
* A null partition key is only acceptable if null-accepting
list
* partition exists.
*/
as we allow NULL to be stored in default. It should be updated.

DROP TABLE list1;
CREATE TABLE list1 ( a int) PARTITION BY LIST (a);
CREATE TABLE list1_1 (LIKE list1);
ALTER TABLE list1 ATTACH PARTITION list1_1 FOR VALUES IN (2);
CREATE TABLE list1_def PARTITION OF list1 DEFAULT;
INSERT INTO list1 VALUES (NULL);
SELECT * FROM list1_def;
a
---

(1 row)

--

Beena Emerson

EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#100Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Beena Emerson (#98)
Re: Adding support for Default partition in partitioning

The existing comment is not valid
/*
* A null partition key is only acceptable if null-accepting
list
* partition exists.
*/
as we allow NULL to be stored in default. It should be updated.

Sure Beena, as stated earlier will update this on my next version of patch.

Regards,
Jeevan Ladhe

#101Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Jeevan Ladhe (#100)
1 attachment(s)
Re: Adding support for Default partition in partitioning

Hi,

I have rebased the patch on latest commit with few cosmetic changes.

The patch fix_listdatums_get_qual_for_list_v3.patch [1]http://www.mail-archive.com/pgsql-hackers@postgresql.org/msg315490.html
<http://www.mail-archive.com/pgsql-hackers@postgresql.org/msg315490.html&gt;
needs to be applied
before applying this patch.

[1]: http://www.mail-archive.com/pgsql-hackers@postgresql.org/msg315490.html

Regards,
Jeevan Ladhe

On Mon, May 29, 2017 at 2:28 PM, Jeevan Ladhe <jeevan.ladhe@enterprisedb.com

Show quoted text

wrote:

The existing comment is not valid
/*
* A null partition key is only acceptable if null-accepting
list
* partition exists.
*/
as we allow NULL to be stored in default. It should be updated.

Sure Beena, as stated earlier will update this on my next version of patch.

Regards,
Jeevan Ladhe

Attachments:

default_partition_v15.patchapplication/octet-stream; name=default_partition_v15.patchDownload
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 393d44f..56df4b5 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -35,6 +35,7 @@
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planmain.h"
+#include "optimizer/prep.h"
 #include "optimizer/var.h"
 #include "rewrite/rewriteManip.h"
 #include "storage/lmgr.h"
@@ -88,9 +89,12 @@ typedef struct PartitionBoundInfoData
 								 * partitioned table) */
 	int			null_index;		/* Index of the null-accepting partition; -1
 								 * if there isn't one */
+	int			default_index;	/* Index of the default partition if any; -1
+								 * if there isn't one */
 } 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
@@ -121,14 +125,15 @@ static int32 qsort_partition_rbound_cmp(const void *a, const void *b,
 static Oid get_partition_operator(PartitionKey key, int col,
 					   StrategyNumber strategy, bool *need_relabel);
 static Expr *make_partition_op_expr(PartitionKey key, int keynum,
-					   uint16 strategy, Expr *arg1, Expr *arg2);
+					   uint16 strategy, Expr *arg1, Expr *arg2,
+					   bool is_default);
 static void get_range_key_properties(PartitionKey key, int keynum,
 						 PartitionRangeDatum *ldatum,
 						 PartitionRangeDatum *udatum,
 						 ListCell **partexprs_item,
 						 Expr **keyCol,
 						 Const **lower_val, Const **upper_val);
-static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_list(Relation parent, PartitionBoundSpec *spec);
 static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
 static List *generate_partition_qual(Relation rel);
 
@@ -147,6 +152,8 @@ static int32 partition_bound_cmp(PartitionKey key,
 static int partition_bound_bsearch(PartitionKey key,
 						PartitionBoundInfo boundinfo,
 						void *probe, bool probe_is_bound, bool *is_equal);
+static void check_default_allows_bound(Relation parent,
+						   PartitionBoundSpec *new_spec);
 
 /*
  * RelationBuildPartitionDesc
@@ -174,6 +181,7 @@ RelationBuildPartitionDesc(Relation rel)
 	/* List partitioning specific */
 	PartitionListValue **all_values = NULL;
 	int			null_index = -1;
+	int			default_index = -1;
 
 	/* Range partitioning specific */
 	PartitionRangeBound **rbounds = NULL;
@@ -254,6 +262,19 @@ RelationBuildPartitionDesc(Relation rel)
 				if (spec->strategy != PARTITION_STRATEGY_LIST)
 					elog(ERROR, "invalid strategy in partition bound spec");
 
+				/*
+				 * In case of default partition, just note the index, we do not
+				 * add this to non_null_values list.
+				 */
+				c = list_head(spec->listdatums);
+				if ((list_length(spec->listdatums) == 1) &&
+					isDefaultPartitionBound((Node *) lfirst(c)))
+				{
+					default_index = i;
+					i++;
+					continue;
+				}
+
 				foreach(c, spec->listdatums)
 				{
 					Const	   *val = castNode(Const, lfirst(c));
@@ -506,6 +527,17 @@ RelationBuildPartitionDesc(Relation rel)
 					else
 						boundinfo->null_index = -1;
 
+					/* Assign mapping index for 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];
+					}
+					else
+						boundinfo->default_index = -1;
+
 					/* All partition must now have a valid mapping */
 					Assert(next_index == nparts);
 					break;
@@ -561,6 +593,12 @@ RelationBuildPartitionDesc(Relation rel)
 						}
 					}
 					boundinfo->indexes[i] = -1;
+
+					/*
+					 * Currently range partition do not have default partition
+					 * support.
+					 */
+					boundinfo->default_index = -1;
 					break;
 				}
 
@@ -610,6 +648,9 @@ partition_bounds_equal(PartitionKey key,
 	if (b1->null_index != b2->null_index)
 		return false;
 
+	if (b1->default_index != b2->default_index)
+		return false;
+
 	for (i = 0; i < b1->ndatums; i++)
 	{
 		int			j;
@@ -672,6 +713,7 @@ check_new_partition_bound(char *relname, Relation parent,
 {
 	PartitionKey key = RelationGetPartitionKey(parent);
 	PartitionDesc partdesc = RelationGetPartitionDesc(parent);
+	PartitionBoundInfo boundinfo = partdesc->boundinfo;
 	ParseState *pstate = make_parsestate(NULL);
 	int			with = -1;
 	bool		overlap = false;
@@ -684,13 +726,29 @@ check_new_partition_bound(char *relname, Relation parent,
 
 				if (partdesc->nparts > 0)
 				{
-					PartitionBoundInfo boundinfo = partdesc->boundinfo;
 					ListCell   *cell;
 
 					Assert(boundinfo &&
 						   boundinfo->strategy == PARTITION_STRATEGY_LIST &&
 						   (boundinfo->ndatums > 0 ||
-							partition_bound_accepts_nulls(boundinfo)));
+							partition_bound_accepts_nulls(boundinfo) ||
+							partition_bound_has_default(boundinfo)));
+
+					/*
+					 * Default partition cannot be added if there already
+					 * exists one.
+					 */
+					cell = list_head(spec->listdatums);
+					if (cell && isDefaultPartitionBound((Node *) lfirst(cell)))
+					{
+						if (partition_bound_has_default(boundinfo))
+						{
+							overlap = true;
+							with = boundinfo->default_index;
+						}
+
+						break;
+					}
 
 					foreach(cell, spec->listdatums)
 					{
@@ -838,6 +896,139 @@ check_new_partition_bound(char *relname, Relation parent,
 						relname, get_rel_name(partdesc->oids[with])),
 				 parser_errposition(pstate, spec->location)));
 	}
+
+	/*
+	 * If a default partition exists, it's partition constraint will change
+	 * after the addition of this new partition such that it won't allow any
+	 * row that qualifies for this new partition. So, check if the existing
+	 * data in the default partition satisfies this *would be* default
+	 * partition constraint.
+	 */
+	if (partdesc->nparts > 0 && partition_bound_has_default(boundinfo))
+		check_default_allows_bound(parent, spec);
+}
+
+/*
+ * check_default_allows_bound
+ *
+ * Checks if there exists any row in default partition that passes the check
+ * for constraints of new partition, if any reports an error.
+ */
+static void
+check_default_allows_bound(Relation parent, PartitionBoundSpec *new_spec)
+{
+	Relation	default_rel;
+	int			default_index;
+	List	   *new_part_constraints = NIL;
+	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 = (List *) eval_const_expressions(NULL,
+											  (Node *) new_part_constraints);
+	new_part_constraints =
+		(List *) canonicalize_qual((Expr *) new_part_constraints);
+	new_part_constraints = list_make1(make_ands_explicit(new_part_constraints));
+
+	/* Generate the constraint and default execution states. */
+	default_index = parent->rd_partdesc->boundinfo->default_index;
+	default_rel = heap_open(parent->rd_partdesc->oids[default_index],
+							AccessExclusiveLock);
+
+	if (default_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		all_parts = find_all_inheritors(RelationGetRelid(default_rel),
+										AccessExclusiveLock, NULL);
+	else
+		all_parts = list_make1_oid(RelationGetRelid(default_rel));
+
+	foreach(lc, all_parts)
+	{
+		Oid			part_relid = lfirst_oid(lc);
+		Relation	part_rel;
+		Expr	   *constr;
+		Expr	   *partition_constraint;
+		EState	   *estate;
+		HeapTuple	tuple;
+		ExprState  *partqualstate = NULL;
+		Snapshot	snapshot;
+		TupleDesc	tupdesc;
+		ExprContext *econtext;
+		HeapScanDesc scan;
+		MemoryContext oldCxt;
+		TupleTableSlot *tupslot;
+
+		/* Lock already taken above. */
+		if (part_relid != RelationGetRelid(default_rel))
+			part_rel = heap_open(part_relid, NoLock);
+		else
+			part_rel = default_rel;
+
+		/*
+		 * Skip if it's a partitioned table. Only RELKIND_RELATION relations
+		 * (ie, leaf partitions) need to be scanned.
+		 */
+		if (part_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			heap_close(part_rel, NoLock);
+			continue;
+		}
+
+		tupdesc = CreateTupleDescCopy(RelationGetDescr(part_rel));
+		constr = linitial(new_part_constraints);
+		partition_constraint = (Expr *) map_partition_varattnos((List *) constr,
+														1, part_rel, parent);
+		estate = CreateExecutorState();
+
+		/* Build expression execution states for partition check quals */
+		partqualstate = ExecPrepareExpr(partition_constraint, estate);
+
+		econtext = GetPerTupleExprContext(estate);
+		snapshot = RegisterSnapshot(GetLatestSnapshot());
+		scan = heap_beginscan(part_rel, snapshot, 0, NULL);
+		tupslot = MakeSingleTupleTableSlot(tupdesc);
+
+		/*
+		 * Switch to per-tuple memory context and reset it for each tuple
+		 * produced, so we don't leak memory.
+		 */
+		oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		{
+			char	   *val_desc;
+
+			ExecStoreTuple(tuple, tupslot, InvalidBuffer, false);
+			econtext->ecxt_scantuple = tupslot;
+
+			if (partqualstate && ExecCheck(partqualstate, econtext))
+			{
+				val_desc =
+					ExecBuildSlotValueDescription(RelationGetRelid(part_rel),
+												  tupslot, tupdesc, NULL, 64);
+
+				ereport(ERROR,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("new default partition constraint is violated by some row"),
+						 val_desc ?
+					 errdetail("Violating row contains %s.", val_desc) : 0));
+			}
+
+			ResetExprContext(econtext);
+			CHECK_FOR_INTERRUPTS();
+		}
+
+		CacheInvalidateRelcache(part_rel);
+		MemoryContextSwitchTo(oldCxt);
+		heap_endscan(scan);
+		UnregisterSnapshot(snapshot);
+		ExecDropSingleTupleTableSlot(tupslot);
+		FreeExecutorState(estate);
+		heap_close(part_rel, NoLock);
+	}
 }
 
 /*
@@ -886,6 +1077,16 @@ get_partition_parent(Oid relid)
 }
 
 /*
+ * Returns true if the partition bound is default.
+ */
+bool
+isDefaultPartitionBound(Node *value)
+{
+	return (IsA(value, DefElem) &&
+			!strcmp(castNode(DefElem, value)->defname, "default_partition"));
+}
+
+/*
  * get_qual_from_partbound
  *		Given a parser node for partition bound, return the list of executable
  *		expressions as partition constraint
@@ -903,7 +1104,7 @@ get_qual_from_partbound(Relation rel, Relation parent,
 	{
 		case PARTITION_STRATEGY_LIST:
 			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
-			my_qual = get_qual_for_list(key, spec);
+			my_qual = get_qual_for_list(parent, spec);
 			break;
 
 		case PARTITION_STRATEGY_RANGE:
@@ -1233,7 +1434,7 @@ get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
  */
 static Expr *
 make_partition_op_expr(PartitionKey key, int keynum,
-					   uint16 strategy, Expr *arg1, Expr *arg2)
+					uint16 strategy, Expr *arg1, Expr *arg2, bool is_default)
 {
 	Oid			operoid;
 	bool		need_relabel = false;
@@ -1263,11 +1464,17 @@ make_partition_op_expr(PartitionKey key, int keynum,
 			{
 				ScalarArrayOpExpr *saopexpr;
 
+				if (is_default &&
+					((operoid = get_negator(operoid)) == InvalidOid))
+					ereport(ERROR, (errcode(ERRCODE_RESTRICT_VIOLATION),
+									errmsg("DEFAULT partition cannot be used without negator of operator  %s",
+										   get_opname(operoid))));
+
 				/* Build leftop = ANY (rightop) */
 				saopexpr = makeNode(ScalarArrayOpExpr);
 				saopexpr->opno = operoid;
 				saopexpr->opfuncid = get_opcode(operoid);
-				saopexpr->useOr = true;
+				saopexpr->useOr = !is_default;
 				saopexpr->inputcollid = key->partcollation[keynum];
 				saopexpr->args = list_make2(arg1, arg2);
 				saopexpr->location = -1;
@@ -1299,10 +1506,11 @@ make_partition_op_expr(PartitionKey key, int keynum,
  * Returns a list of expressions to use as a list partition's constraint.
  */
 static List *
-get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
+get_qual_for_list(Relation parent, PartitionBoundSpec *spec)
 {
+	PartitionKey key = RelationGetPartitionKey(parent);
 	List	   *result;
-	List       *datums = NIL;
+	List	   *datums = NIL;
 	ArrayExpr  *arr;
 	Expr	   *opexpr;
 	ListCell   *cell;
@@ -1310,6 +1518,8 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 	bool		list_has_null = false;
 	NullTest   *nulltest1 = NULL,
 			   *nulltest2 = NULL;
+	bool		is_default =
+		isDefaultPartitionBound((Node *) linitial(spec->listdatums));
 
 	/* Left operand is either a simple Var or arbitrary expression */
 	if (key->partattrs[0] != 0)
@@ -1323,20 +1533,63 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 		keyCol = (Expr *) copyObject(linitial(key->partexprs));
 
 	/*
-	 * Create a list of datums without NULL; as we handle it separately
-	 * below.
+	 * In case of default partition for list, the partition constraint is
+	 * basically any value that is not equal to any of the values in
+	 * boundinfo->datums array. So, construct a list of constants from
+	 * boundinfo->datums to pass to function make_partition_op_expr via
+	 * ArrayExpr, which would return an negated expression for default
+	 * partition.
 	 */
-	foreach (cell, spec->listdatums)
+	if (is_default)
 	{
-		Const	   *val = castNode(Const, lfirst(cell));
+		int         i;
+		int         ndatums = 0;
+		MemoryContext oldcxt;
+		PartitionDesc pdesc = RelationGetPartitionDesc(parent);
+		PartitionBoundInfo boundinfo = pdesc->boundinfo;
+
+		ndatums = (pdesc->nparts > 0) ? boundinfo->ndatums : 0;
+		oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+
+		for (i = 0; i < ndatums; i++)
+		{
+			Const      *val;
+
+			/* Construct const from datum */
+			val = makeConst(key->parttypid[0],
+							key->parttypmod[0],
+							key->parttypcoll[0],
+							key->parttyplen[0],
+							*boundinfo->datums[i],
+							false,      /* isnull */
+							true /* byval */ );
+
+			datums = lappend(datums, val);
+		}
 
-		if (val->constisnull)
+		if (boundinfo && partition_bound_accepts_nulls(boundinfo))
 			list_has_null = true;
-		else
-			datums = lappend(datums, lfirst(cell));
+
+		MemoryContextSwitchTo(oldcxt);
+	}
+	else
+	{
+		/*
+		 * Create a list of datums without NULL; as we handle it separately
+		 * below.
+		 */
+		foreach (cell, spec->listdatums)
+		{
+			Const	   *val = castNode(Const, lfirst(cell));
+
+			if (val->constisnull)
+				list_has_null = true;
+			else
+				datums = lappend(datums, lfirst(cell));
+		}
 	}
 
-	if (!list_has_null)
+	if ((!list_has_null && !is_default) || (list_has_null && is_default))
 	{
 		/*
 		 * Gin up a col IS NOT NULL test that will be AND'd with other
@@ -1373,7 +1626,7 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 
 	/* Generate the main expression, i.e., keyCol = ANY (arr) */
 	opexpr = make_partition_op_expr(key, 0, BTEqualStrategyNumber,
-									keyCol, (Expr *) arr);
+									keyCol, (Expr *) arr, is_default);
 
 	if (nulltest1)
 		result = list_make2(nulltest1, opexpr);
@@ -1587,7 +1840,7 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 		oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
 		test_expr = make_partition_op_expr(key, i, BTEqualStrategyNumber,
 										   (Expr *) lower_val,
-										   (Expr *) upper_val);
+										   (Expr *) upper_val, false);
 		fix_opfuncids((Node *) test_expr);
 		test_exprstate = ExecInitExpr(test_expr, NULL);
 		test_result = ExecEvalExprSwitchContext(test_exprstate,
@@ -1611,7 +1864,7 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 		/* Equal, so generate keyCol = lower_val expression */
 		result = lappend(result,
 						 make_partition_op_expr(key, i, BTEqualStrategyNumber,
-												keyCol, (Expr *) lower_val));
+										 keyCol, (Expr *) lower_val, false));
 
 		i++;
 	}
@@ -1671,7 +1924,8 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 											make_partition_op_expr(key, j,
 																   strategy,
 																   keyCol,
-														(Expr *) lower_val));
+														  (Expr *) lower_val,
+																   false));
 			}
 
 			if (need_next_upper_arm && upper_val)
@@ -1693,7 +1947,8 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 											make_partition_op_expr(key, j,
 																   strategy,
 																   keyCol,
-														(Expr *) upper_val));
+														  (Expr *) upper_val,
+																   false));
 
 			}
 
@@ -1983,11 +2238,13 @@ get_partition_for_tuple(PartitionDispatch *pd,
 			}
 		}
 
+		cur_index = -1;
 		/*
-		 * A null partition key is only acceptable if null-accepting list
-		 * partition exists.
+		 * A null partition key is acceptable if null-accepting list partition
+		 * or a default partition exists. Check if there exists a null
+		 * accepting partition, else this will be handled later by default
+		 * partition if it exists.
 		 */
-		cur_index = -1;
 		if (isnull[0] && partition_bound_accepts_nulls(partdesc->boundinfo))
 			cur_index = partdesc->boundinfo->null_index;
 		else if (!isnull[0])
@@ -2023,16 +2280,28 @@ get_partition_for_tuple(PartitionDispatch *pd,
 		}
 
 		/*
-		 * cur_index < 0 means we failed to find a partition of this parent.
-		 * cur_index >= 0 means we either found the leaf partition, or the
-		 * next parent to find a partition of.
+		 * cur_index < 0 means we could not find a non-default partition of this
+		 * parent. cur_index >= 0 means we either found the leaf partition, or
+		 * the next parent to find a partition of.
 		 */
 		if (cur_index < 0)
 		{
-			result = -1;
-			*failed_at = parent;
-			*failed_slot = slot;
-			break;
+			if (partition_bound_has_default(partdesc->boundinfo))
+			{
+				result = parent->indexes[partdesc->boundinfo->default_index];
+
+				if (result >= 0)
+					break;
+				else
+					parent = pd[-result];
+			}
+			else
+			{
+				result = -1;
+				*failed_at = parent;
+				*failed_slot = slot;
+				break;
+			}
 		}
 		else if (parent->indexes[cur_index] >= 0)
 		{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4a899f1..dae592a 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -92,11 +92,6 @@ static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid,
 						  Bitmapset *modifiedCols,
 						  AclMode requiredPerms);
 static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
-static char *ExecBuildSlotValueDescription(Oid reloid,
-							  TupleTableSlot *slot,
-							  TupleDesc tupdesc,
-							  Bitmapset *modifiedCols,
-							  int maxfieldlen);
 static char *ExecBuildSlotPartitionKeyDescription(Relation rel,
 									 Datum *values,
 									 bool *isnull,
@@ -2174,7 +2169,7 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
  * column involved, that subset will be returned with a key identifying which
  * columns they are.
  */
-static char *
+char *
 ExecBuildSlotValueDescription(Oid reloid,
 							  TupleTableSlot *slot,
 							  TupleDesc tupdesc,
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ff5b1be..c394c90 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2675,6 +2675,23 @@ ForValues:
 
 					$$ = n;
 				}
+
+			/*
+			 * A default partition, that can be partition of either LIST or RANGE
+			 * partitioned table.
+			 * Currently this is supported only for LIST partition.
+			 */
+			| DEFAULT
+				{
+					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+					n->listdatums =
+						list_make1(makeDefElem("default_partition", NULL, @1));
+					n->location = @1;
+
+					$$ = n;
+				}
+
 		;
 
 partbound_datum:
@@ -2716,6 +2733,7 @@ PartitionRangeDatum:
 
 					$$ = (Node *) n;
 				}
+
 		;
 
 /*****************************************************************************
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 9134fb9..bbc0dd6 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -3291,10 +3292,29 @@ transformPartitionBound(ParseState *pstate, Relation parent,
 		int32		coltypmod;
 
 		if (spec->strategy != PARTITION_STRATEGY_LIST)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
-				  errmsg("invalid bound specification for a list partition"),
-				   parser_errposition(pstate, exprLocation((Node *) spec))));
+		{
+			/*
+			 * 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->listdatums) == 1) &&
+				isDefaultPartitionBound((Node *) linitial(spec->listdatums)))
+			{
+				result_spec->strategy = PARTITION_STRATEGY_LIST;
+
+				/*
+				 * 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 list partition"),
+					parser_errposition(pstate, exprLocation((Node *) spec))));
+		}
 
 		/* Get the only column's name in case we need to output an error */
 		if (key->partattrs[0] != 0)
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 824d757..47810ae 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8652,6 +8652,18 @@ get_rule_expr(Node *node, deparse_context *context,
 					case PARTITION_STRATEGY_LIST:
 						Assert(spec->listdatums != NIL);
 
+						/*
+						 * If the boundspec is of Default partition, it does
+						 * not have list of datums, but has only one node to
+						 * indicate its a default partition.
+						 */
+						if (isDefaultPartitionBound(
+										(Node *) linitial(spec->listdatums)))
+						{
+							appendStringInfoString(buf, "DEFAULT");
+							break;
+						}
+
 						appendStringInfoString(buf, "FOR VALUES IN (");
 						sep = "";
 						foreach(cell, spec->listdatums)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 2abd087..b01ba07 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2044,7 +2044,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("ATTACH", "PARTITION", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 	else if (TailMatches2("FOR", "VALUES"))
 		COMPLETE_WITH_LIST2("FROM (", "IN (");
 
@@ -2483,7 +2483,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("PARTITION", "OF", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 
 /* CREATE TABLESPACE */
 	else if (Matches3("CREATE", "TABLESPACE", MatchAny))
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 0a1e468..3a80bd9 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -83,6 +83,7 @@ extern List *map_partition_varattnos(List *expr, int target_varno,
 						Relation partrel, Relation parent);
 extern List *RelationGetPartitionQual(Relation rel);
 extern Expr *get_partition_qual_relid(Oid relid);
+extern bool isDefaultPartitionBound(Node *value);
 
 /* For tuple routing */
 extern PartitionDispatch *RelationGetPartitionDispatchInfo(Relation rel,
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 8cc5f3a..8bd574c 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -537,5 +537,10 @@ extern void CheckCmdReplicaIdentity(Relation rel, CmdType cmd);
 
 extern void CheckSubscriptionRelkind(char relkind, const char *nspname,
 						 const char *relname);
+char *ExecBuildSlotValueDescription(Oid reloid,
+							  TupleTableSlot *slot,
+							  TupleDesc tupdesc,
+							  Bitmapset *modifiedCols,
+							  int maxfieldlen);
 
 #endif   /* EXECUTOR_H  */
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 8aadbb8..b800173 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3273,6 +3273,11 @@ SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::reg
 CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
 ERROR:  partition "fail_part" would overlap partition "part_1"
+-- attaching default partition overlaps if a default partition already exists
+CREATE TABLE part_def1 PARTITION OF list_parted DEFAULT;
+CREATE TABLE part_def2 (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION part_def2 DEFAULT;
+ERROR:  partition "part_def2" would overlap partition "part_def1"
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
 	a int,
@@ -3286,6 +3291,16 @@ ERROR:  partition constraint is violated by some row
 -- should be ok after deleting the bad row
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+-- check partition cannot be attached if default has some row for its values
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
+INSERT INTO list_parted2_def VALUES (11, 'z');
+CREATE TABLE part_3 (LIKE list_parted2);
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+ERROR:  new default partition constraint is violated by some row
+DETAIL:  Violating row contains (11, z).
+-- should be ok after deleting the bad row
+DELETE FROM list_parted2_def WHERE a = 11;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
 CREATE TABLE part_3_4 (
@@ -3338,9 +3353,22 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
 ERROR:  partition constraint is violated by some row
 -- delete the faulting row and also add a constraint to skip the scan
 DELETE FROM part_5_a WHERE a NOT IN (3);
-ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT NULL;
-ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
+ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5, 55)), ALTER a SET NOT NULL;
+ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5, 55);
 INFO:  partition constraint for table "part_5" is implied by existing constraints
+-- check that leaf partitons of default partition are scanned when
+-- attaching a partitioned table.
+ALTER TABLE part_5 DROP CONSTRAINT check_a;
+CREATE TABLE part5_def PARTITION OF part_5 DEFAULT PARTITION BY LIST(a);
+CREATE TABLE part5_def_p1 PARTITION OF part5_def FOR VALUES IN (44, 55);
+INSERT INTO part5_def_p1 VALUES (55, 'y');
+CREATE TABLE part5_p1 (LIKE part_5);
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+ERROR:  new default partition constraint is violated by some row
+DETAIL:  Violating row contains (55, y).
+-- should be ok after deleting the bad row
+DELETE FROM part5_def_p1 WHERE b = 'y';
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
 -- check that the table being attached is not already a partition
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 ERROR:  "part_2" is already a partition
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 5136506..92068ee 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -449,6 +449,7 @@ CREATE TABLE list_parted (
 CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
 CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
 CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
 ERROR:  syntax error at or near "int"
 LINE 1: ... fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
@@ -457,6 +458,8 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
 ERROR:  syntax error at or near "::"
 LINE 1: ...fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
                                                                 ^
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
+ERROR:  partition "fail_default_part" would overlap partition "part_default"
 -- syntax does not allow empty list of values for list partitions
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
 ERROR:  syntax error at or near ")"
@@ -514,6 +517,11 @@ 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);
@@ -565,10 +573,16 @@ CREATE TABLE list_parted2 (
 ) PARTITION BY LIST (a);
 CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
 CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
 ERROR:  partition "fail_part" would overlap partition "part_null_z"
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
 ERROR:  partition "fail_part" would overlap partition "part_ab"
+-- check default partition overlap
+INSERT INTO list_parted2 VALUES('X');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y');
+ERROR:  new default partition constraint is violated by some row
+DETAIL:  Violating row contains (X).
 CREATE TABLE range_parted2 (
 	a int
 ) PARTITION BY RANGE (a);
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 8b0752a..841f7c3 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -202,6 +202,7 @@ create table list_parted (
 create table part_aa_bb partition of list_parted FOR VALUES IN ('aa', 'bb');
 create table part_cc_dd partition of list_parted FOR VALUES IN ('cc', 'dd');
 create table part_null partition of list_parted FOR VALUES IN (null);
+create table part_default partition of list_parted default;
 -- fail
 insert into part_aa_bb values ('cc', 1);
 ERROR:  new row for relation "part_aa_bb" violates partition constraint
@@ -212,24 +213,46 @@ DETAIL:  Failing row contains (AAa, 1).
 insert into part_aa_bb values (null);
 ERROR:  new row for relation "part_aa_bb" violates partition constraint
 DETAIL:  Failing row contains (null, null).
+insert into part_default values ('aa', 2);
+ERROR:  new row for relation "part_default" violates partition constraint
+DETAIL:  Failing row contains (aa, 2).
+insert into part_default values (null, 2);
+ERROR:  new row for relation "part_default" violates partition constraint
+DETAIL:  Failing row contains (null, 2).
 -- ok
 insert into part_cc_dd values ('cC', 1);
 insert into part_null values (null, 0);
+insert into part_default values ('Zz', 2);
 -- check in case of multi-level partitioned table
 create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b);
 create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
 create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
+-- check in case of multi-level default partitioned table
+drop table part_default;
+create table part_default partition of list_parted default partition by range(b);
+create table part_default_p1 partition of part_default for values from (20) to (30);
+create table part_default_p2 partition of part_default for values from (30) to (40);
 -- fail
 insert into part_ee_ff1 values ('EE', 11);
 ERROR:  new row for relation "part_ee_ff1" violates partition constraint
 DETAIL:  Failing row contains (EE, 11).
+insert into part_default_p2 values ('gg', 43);
+ERROR:  new row for relation "part_default_p2" violates partition constraint
+DETAIL:  Failing row contains (gg, 43).
 -- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
 insert into part_ee_ff1 values ('cc', 1);
 ERROR:  new row for relation "part_ee_ff1" violates partition constraint
 DETAIL:  Failing row contains (cc, 1).
+insert into part_default values ('gg', 43);
+ERROR:  no partition of relation "part_default" found for row
+DETAIL:  Partition key of the failing row contains (b) = (43).
 -- ok
 insert into part_ee_ff1 values ('ff', 1);
 insert into part_ee_ff2 values ('ff', 11);
+insert into part_default_p1 values ('cd', 25);
+insert into part_default_p2 values ('gg', 35);
+insert into list_parted values ('ab', 21);
+drop table part_default;
 -- Check tuple routing for partitioned tables
 -- fail
 insert into range_parted values ('a', 0);
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index 9366f04..0f41e7e 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -218,5 +218,24 @@ 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;
+create table list_parted (
+	a text,
+	b int
+) partition by list (a);
+create table list_part1  partition of list_parted for values in ('a', 'b');
+create table list_default partition of list_parted default;
+insert into list_part1 values ('a', 1);
+insert into list_default values ('d', 10);
+-- fail
+update list_part1 set a = 'c' where a = 'a';
+ERROR:  new row for relation "list_part1" violates partition constraint
+DETAIL:  Failing row contains (c, 1).
+update list_default set a = 'a' where a = 'd';
+ERROR:  new row for relation "list_default" violates partition constraint
+DETAIL:  Failing row contains (a, 10).
+-- ok
+update list_part1 set a = 'b' where a = 'a';
+update list_default set a = 'x' where a = 'd';
 -- cleanup
 drop table range_parted;
+drop table list_parted;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index c41b487..aa1a4e3 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2095,6 +2095,10 @@ SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::reg
 -- check that the new partition won't overlap with an existing partition
 CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+-- attaching default partition overlaps if a default partition already exists
+CREATE TABLE part_def1 PARTITION OF list_parted DEFAULT;
+CREATE TABLE part_def2 (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION part_def2 DEFAULT;
 
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
@@ -2111,6 +2115,15 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 
+-- check partition cannot be attached if default has some row for its values
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
+INSERT INTO list_parted2_def VALUES (11, 'z');
+CREATE TABLE part_3 (LIKE list_parted2);
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+-- should be ok after deleting the bad row
+DELETE FROM list_parted2_def WHERE a = 11;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
 CREATE TABLE part_3_4 (
@@ -2169,9 +2182,20 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
 
 -- delete the faulting row and also add a constraint to skip the scan
 DELETE FROM part_5_a WHERE a NOT IN (3);
-ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT NULL;
-ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
-
+ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5, 55)), ALTER a SET NOT NULL;
+ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5, 55);
+
+-- check that leaf partitons of default partition are scanned when
+-- attaching a partitioned table.
+ALTER TABLE part_5 DROP CONSTRAINT check_a;
+CREATE TABLE part5_def PARTITION OF part_5 DEFAULT PARTITION BY LIST(a);
+CREATE TABLE part5_def_p1 PARTITION OF part5_def FOR VALUES IN (44, 55);
+INSERT INTO part5_def_p1 VALUES (55, 'y');
+CREATE TABLE part5_p1 (LIKE part_5);
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+-- should be ok after deleting the bad row
+DELETE FROM part5_def_p1 WHERE b = 'y';
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
 
 -- check that the table being attached is not already a partition
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index cb7aa5b..f44c0e0 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -439,8 +439,10 @@ CREATE TABLE list_parted (
 CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
 CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
 CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
 
 -- syntax does not allow empty list of values for list partitions
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
@@ -484,6 +486,8 @@ 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);
@@ -529,9 +533,13 @@ CREATE TABLE list_parted2 (
 ) PARTITION BY LIST (a);
 CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
 CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+-- check default partition overlap
+INSERT INTO list_parted2 VALUES('X');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y');
 
 CREATE TABLE range_parted2 (
 	a int
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index db8967b..a9f2289 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -118,27 +118,42 @@ create table list_parted (
 create table part_aa_bb partition of list_parted FOR VALUES IN ('aa', 'bb');
 create table part_cc_dd partition of list_parted FOR VALUES IN ('cc', 'dd');
 create table part_null partition of list_parted FOR VALUES IN (null);
+create table part_default partition of list_parted default;
 
 -- fail
 insert into part_aa_bb values ('cc', 1);
 insert into part_aa_bb values ('AAa', 1);
 insert into part_aa_bb values (null);
+insert into part_default values ('aa', 2);
+insert into part_default values (null, 2);
 -- ok
 insert into part_cc_dd values ('cC', 1);
 insert into part_null values (null, 0);
+insert into part_default values ('Zz', 2);
 
 -- check in case of multi-level partitioned table
 create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b);
 create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
 create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
+-- check in case of multi-level default partitioned table
+drop table part_default;
+create table part_default partition of list_parted default partition by range(b);
+create table part_default_p1 partition of part_default for values from (20) to (30);
+create table part_default_p2 partition of part_default for values from (30) to (40);
 
 -- fail
 insert into part_ee_ff1 values ('EE', 11);
+insert into part_default_p2 values ('gg', 43);
 -- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
 insert into part_ee_ff1 values ('cc', 1);
+insert into part_default values ('gg', 43);
 -- ok
 insert into part_ee_ff1 values ('ff', 1);
 insert into part_ee_ff2 values ('ff', 11);
+insert into part_default_p1 values ('cd', 25);
+insert into part_default_p2 values ('gg', 35);
+insert into list_parted values ('ab', 21);
+drop table part_default;
 
 -- Check tuple routing for partitioned tables
 
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 6637119..02840a4 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -125,5 +125,22 @@ update range_parted set b = b - 1 where b = 10;
 -- ok
 update range_parted set b = b + 1 where b = 10;
 
+create table list_parted (
+	a text,
+	b int
+) partition by list (a);
+create table list_part1  partition of list_parted for values in ('a', 'b');
+create table list_default partition of list_parted default;
+insert into list_part1 values ('a', 1);
+insert into list_default values ('d', 10);
+
+-- fail
+update list_part1 set a = 'c' where a = 'a';
+update list_default set a = 'a' where a = 'd';
+-- ok
+update list_part1 set a = 'b' where a = 'a';
+update list_default set a = 'x' where a = 'd';
+
 -- cleanup
 drop table range_parted;
+drop table list_parted;
#102Beena Emerson
memissemerson@gmail.com
In reply to: Jeevan Ladhe (#101)
Re: Adding support for Default partition in partitioning

On Mon, May 29, 2017 at 9:33 PM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

Hi,

I have rebased the patch on latest commit with few cosmetic changes.

The patch fix_listdatums_get_qual_for_list_v3.patch [1] needs to be applied
before applying this patch.

[1] http://www.mail-archive.com/pgsql-hackers@postgresql.org/msg315490.html

This needs a rebase again.

--

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

#103Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Beena Emerson (#102)
1 attachment(s)
Re: Adding support for Default partition in partitioning

Hi,

I have rebased the patch on the latest commit.
PFA.

There exists one issue reported by Rajkumar[1]rajkumar.raghuwanshi@enterprisedb.com off-line as following, where
describing the default partition after deleting null partition, does not
show
updated constraints. I am working on fixing this issue.

create table t1 (c1 int) partition by list (c1);
create table t11 partition of t1 for values in (1,2);
create table t12 partition of t1 default;
create table t13 partition of t1 for values in (10,11);
create table t14 partition of t1 for values in (null);

postgres=# \d+ t12
Table "public.t12"
Column | Type | Collation | Nullable | Default | Storage | Stats target
| Description
--------+---------+-----------+----------+---------+---------+--------------+-------------
c1 | integer | | | | plain |
|
Partition of: t1 DEFAULT
Partition constraint: ((c1 IS NOT NULL) AND (c1 <> ALL (ARRAY[1, 2, 10,
11])))

postgres=# alter table t1 detach partition t14;
ALTER TABLE
postgres=# \d+ t12
Table "public.t12"
Column | Type | Collation | Nullable | Default | Storage | Stats target
| Description
--------+---------+-----------+----------+---------+---------+--------------+-------------
c1 | integer | | | | plain |
|
Partition of: t1 DEFAULT
Partition constraint: ((c1 IS NOT NULL) AND (c1 <> ALL (ARRAY[1, 2, 10,
11])))

postgres=# insert into t1 values(null);
INSERT 0 1

Note that the parent correctly allows the nulls to be inserted.

[1]: rajkumar.raghuwanshi@enterprisedb.com

Regards,
Jeevan Ladhe

On Tue, May 30, 2017 at 10:59 AM, Beena Emerson <memissemerson@gmail.com>
wrote:

Show quoted text

On Mon, May 29, 2017 at 9:33 PM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

Hi,

I have rebased the patch on latest commit with few cosmetic changes.

The patch fix_listdatums_get_qual_for_list_v3.patch [1] needs to be

applied

before applying this patch.

[1] http://www.mail-archive.com/pgsql-hackers@postgresql.org/

msg315490.html

This needs a rebase again.

--

Beena Emerson

EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

Attachments:

default_partition_v16.patchapplication/octet-stream; name=default_partition_v16.patchDownload
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 37fa145..dc13833 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -35,6 +35,7 @@
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planmain.h"
+#include "optimizer/prep.h"
 #include "optimizer/var.h"
 #include "rewrite/rewriteManip.h"
 #include "storage/lmgr.h"
@@ -88,9 +89,12 @@ typedef struct PartitionBoundInfoData
 								 * partitioned table) */
 	int			null_index;		/* Index of the null-accepting partition; -1
 								 * if there isn't one */
+	int			default_index;	/* Index of the default partition if any; -1
+								 * if there isn't one */
 } 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
@@ -121,14 +125,15 @@ static int32 qsort_partition_rbound_cmp(const void *a, const void *b,
 static Oid get_partition_operator(PartitionKey key, int col,
 					   StrategyNumber strategy, bool *need_relabel);
 static Expr *make_partition_op_expr(PartitionKey key, int keynum,
-					   uint16 strategy, Expr *arg1, Expr *arg2);
+					   uint16 strategy, Expr *arg1, Expr *arg2,
+					   bool is_default);
 static void get_range_key_properties(PartitionKey key, int keynum,
 						 PartitionRangeDatum *ldatum,
 						 PartitionRangeDatum *udatum,
 						 ListCell **partexprs_item,
 						 Expr **keyCol,
 						 Const **lower_val, Const **upper_val);
-static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_list(Relation parent, PartitionBoundSpec *spec);
 static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
 static List *generate_partition_qual(Relation rel);
 
@@ -147,6 +152,8 @@ static int32 partition_bound_cmp(PartitionKey key,
 static int partition_bound_bsearch(PartitionKey key,
 						PartitionBoundInfo boundinfo,
 						void *probe, bool probe_is_bound, bool *is_equal);
+static void check_default_allows_bound(Relation parent,
+						   PartitionBoundSpec *new_spec);
 
 /*
  * RelationBuildPartitionDesc
@@ -174,6 +181,7 @@ RelationBuildPartitionDesc(Relation rel)
 	/* List partitioning specific */
 	PartitionListValue **all_values = NULL;
 	int			null_index = -1;
+	int			default_index = -1;
 
 	/* Range partitioning specific */
 	PartitionRangeBound **rbounds = NULL;
@@ -254,6 +262,19 @@ RelationBuildPartitionDesc(Relation rel)
 				if (spec->strategy != PARTITION_STRATEGY_LIST)
 					elog(ERROR, "invalid strategy in partition bound spec");
 
+				/*
+				 * In case of default partition, just note the index, we do not
+				 * add this to non_null_values list.
+				 */
+				c = list_head(spec->listdatums);
+				if ((list_length(spec->listdatums) == 1) &&
+					isDefaultPartitionBound((Node *) lfirst(c)))
+				{
+					default_index = i;
+					i++;
+					continue;
+				}
+
 				foreach(c, spec->listdatums)
 				{
 					Const	   *val = castNode(Const, lfirst(c));
@@ -506,6 +527,17 @@ RelationBuildPartitionDesc(Relation rel)
 					else
 						boundinfo->null_index = -1;
 
+					/* Assign mapping index for 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];
+					}
+					else
+						boundinfo->default_index = -1;
+
 					/* All partition must now have a valid mapping */
 					Assert(next_index == nparts);
 					break;
@@ -561,6 +593,12 @@ RelationBuildPartitionDesc(Relation rel)
 						}
 					}
 					boundinfo->indexes[i] = -1;
+
+					/*
+					 * Currently range partition do not have default partition
+					 * support.
+					 */
+					boundinfo->default_index = -1;
 					break;
 				}
 
@@ -610,6 +648,9 @@ partition_bounds_equal(PartitionKey key,
 	if (b1->null_index != b2->null_index)
 		return false;
 
+	if (b1->default_index != b2->default_index)
+		return false;
+
 	for (i = 0; i < b1->ndatums; i++)
 	{
 		int			j;
@@ -672,6 +713,7 @@ check_new_partition_bound(char *relname, Relation parent,
 {
 	PartitionKey key = RelationGetPartitionKey(parent);
 	PartitionDesc partdesc = RelationGetPartitionDesc(parent);
+	PartitionBoundInfo boundinfo = partdesc->boundinfo;
 	ParseState *pstate = make_parsestate(NULL);
 	int			with = -1;
 	bool		overlap = false;
@@ -684,13 +726,29 @@ check_new_partition_bound(char *relname, Relation parent,
 
 				if (partdesc->nparts > 0)
 				{
-					PartitionBoundInfo boundinfo = partdesc->boundinfo;
 					ListCell   *cell;
 
 					Assert(boundinfo &&
 						   boundinfo->strategy == PARTITION_STRATEGY_LIST &&
 						   (boundinfo->ndatums > 0 ||
-							partition_bound_accepts_nulls(boundinfo)));
+							partition_bound_accepts_nulls(boundinfo) ||
+							partition_bound_has_default(boundinfo)));
+
+					/*
+					 * Default partition cannot be added if there already
+					 * exists one.
+					 */
+					cell = list_head(spec->listdatums);
+					if (cell && isDefaultPartitionBound((Node *) lfirst(cell)))
+					{
+						if (partition_bound_has_default(boundinfo))
+						{
+							overlap = true;
+							with = boundinfo->default_index;
+						}
+
+						break;
+					}
 
 					foreach(cell, spec->listdatums)
 					{
@@ -838,6 +896,139 @@ check_new_partition_bound(char *relname, Relation parent,
 						relname, get_rel_name(partdesc->oids[with])),
 				 parser_errposition(pstate, spec->location)));
 	}
+
+	/*
+	 * If a default partition exists, it's partition constraint will change
+	 * after the addition of this new partition such that it won't allow any
+	 * row that qualifies for this new partition. So, check if the existing
+	 * data in the default partition satisfies this *would be* default
+	 * partition constraint.
+	 */
+	if (partdesc->nparts > 0 && partition_bound_has_default(boundinfo))
+		check_default_allows_bound(parent, spec);
+}
+
+/*
+ * check_default_allows_bound
+ *
+ * Checks if there exists any row in default partition that passes the check
+ * for constraints of new partition, if any reports an error.
+ */
+static void
+check_default_allows_bound(Relation parent, PartitionBoundSpec *new_spec)
+{
+	Relation	default_rel;
+	int			default_index;
+	List	   *new_part_constraints = NIL;
+	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 = (List *) eval_const_expressions(NULL,
+											  (Node *) new_part_constraints);
+	new_part_constraints =
+		(List *) canonicalize_qual((Expr *) new_part_constraints);
+	new_part_constraints = list_make1(make_ands_explicit(new_part_constraints));
+
+	/* Generate the constraint and default execution states. */
+	default_index = parent->rd_partdesc->boundinfo->default_index;
+	default_rel = heap_open(parent->rd_partdesc->oids[default_index],
+							AccessExclusiveLock);
+
+	if (default_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		all_parts = find_all_inheritors(RelationGetRelid(default_rel),
+										AccessExclusiveLock, NULL);
+	else
+		all_parts = list_make1_oid(RelationGetRelid(default_rel));
+
+	foreach(lc, all_parts)
+	{
+		Oid			part_relid = lfirst_oid(lc);
+		Relation	part_rel;
+		Expr	   *constr;
+		Expr	   *partition_constraint;
+		EState	   *estate;
+		HeapTuple	tuple;
+		ExprState  *partqualstate = NULL;
+		Snapshot	snapshot;
+		TupleDesc	tupdesc;
+		ExprContext *econtext;
+		HeapScanDesc scan;
+		MemoryContext oldCxt;
+		TupleTableSlot *tupslot;
+
+		/* Lock already taken above. */
+		if (part_relid != RelationGetRelid(default_rel))
+			part_rel = heap_open(part_relid, NoLock);
+		else
+			part_rel = default_rel;
+
+		/*
+		 * Skip if it's a partitioned table. Only RELKIND_RELATION relations
+		 * (ie, leaf partitions) need to be scanned.
+		 */
+		if (part_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			heap_close(part_rel, NoLock);
+			continue;
+		}
+
+		tupdesc = CreateTupleDescCopy(RelationGetDescr(part_rel));
+		constr = linitial(new_part_constraints);
+		partition_constraint = (Expr *) map_partition_varattnos((List *) constr,
+														1, part_rel, parent);
+		estate = CreateExecutorState();
+
+		/* Build expression execution states for partition check quals */
+		partqualstate = ExecPrepareExpr(partition_constraint, estate);
+
+		econtext = GetPerTupleExprContext(estate);
+		snapshot = RegisterSnapshot(GetLatestSnapshot());
+		scan = heap_beginscan(part_rel, snapshot, 0, NULL);
+		tupslot = MakeSingleTupleTableSlot(tupdesc);
+
+		/*
+		 * Switch to per-tuple memory context and reset it for each tuple
+		 * produced, so we don't leak memory.
+		 */
+		oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		{
+			char	   *val_desc;
+
+			ExecStoreTuple(tuple, tupslot, InvalidBuffer, false);
+			econtext->ecxt_scantuple = tupslot;
+
+			if (partqualstate && ExecCheck(partqualstate, econtext))
+			{
+				val_desc =
+					ExecBuildSlotValueDescription(RelationGetRelid(part_rel),
+												  tupslot, tupdesc, NULL, 64);
+
+				ereport(ERROR,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("new default partition constraint is violated by some row"),
+						 val_desc ?
+					 errdetail("Violating row contains %s.", val_desc) : 0));
+			}
+
+			ResetExprContext(econtext);
+			CHECK_FOR_INTERRUPTS();
+		}
+
+		CacheInvalidateRelcache(part_rel);
+		MemoryContextSwitchTo(oldCxt);
+		heap_endscan(scan);
+		UnregisterSnapshot(snapshot);
+		ExecDropSingleTupleTableSlot(tupslot);
+		FreeExecutorState(estate);
+		heap_close(part_rel, NoLock);
+	}
 }
 
 /*
@@ -886,6 +1077,16 @@ get_partition_parent(Oid relid)
 }
 
 /*
+ * Returns true if the partition bound is default.
+ */
+bool
+isDefaultPartitionBound(Node *value)
+{
+	return (IsA(value, DefElem) &&
+			!strcmp(castNode(DefElem, value)->defname, "default_partition"));
+}
+
+/*
  * get_qual_from_partbound
  *		Given a parser node for partition bound, return the list of executable
  *		expressions as partition constraint
@@ -903,7 +1104,7 @@ get_qual_from_partbound(Relation rel, Relation parent,
 	{
 		case PARTITION_STRATEGY_LIST:
 			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
-			my_qual = get_qual_for_list(key, spec);
+			my_qual = get_qual_for_list(parent, spec);
 			break;
 
 		case PARTITION_STRATEGY_RANGE:
@@ -1233,7 +1434,7 @@ get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
  */
 static Expr *
 make_partition_op_expr(PartitionKey key, int keynum,
-					   uint16 strategy, Expr *arg1, Expr *arg2)
+					uint16 strategy, Expr *arg1, Expr *arg2, bool is_default)
 {
 	Oid			operoid;
 	bool		need_relabel = false;
@@ -1263,11 +1464,17 @@ make_partition_op_expr(PartitionKey key, int keynum,
 			{
 				ScalarArrayOpExpr *saopexpr;
 
+				if (is_default &&
+					((operoid = get_negator(operoid)) == InvalidOid))
+					ereport(ERROR, (errcode(ERRCODE_RESTRICT_VIOLATION),
+									errmsg("DEFAULT partition cannot be used without negator of operator  %s",
+										   get_opname(operoid))));
+
 				/* Build leftop = ANY (rightop) */
 				saopexpr = makeNode(ScalarArrayOpExpr);
 				saopexpr->opno = operoid;
 				saopexpr->opfuncid = get_opcode(operoid);
-				saopexpr->useOr = true;
+				saopexpr->useOr = !is_default;
 				saopexpr->inputcollid = key->partcollation[keynum];
 				saopexpr->args = list_make2(arg1, arg2);
 				saopexpr->location = -1;
@@ -1300,8 +1507,9 @@ make_partition_op_expr(PartitionKey key, int keynum,
  * constraint, given the partition key and bound structures.
  */
 static List *
-get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
+get_qual_for_list(Relation parent, PartitionBoundSpec *spec)
 {
+	PartitionKey key = RelationGetPartitionKey(parent);
 	List	   *result;
 	Expr	   *keyCol;
 	ArrayExpr  *arr;
@@ -1310,6 +1518,8 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 	ListCell   *cell;
 	List	   *arrelems = NIL;
 	bool		list_has_null = false;
+	bool		is_default =
+		isDefaultPartitionBound((Node *) linitial(spec->listdatums));
 
 	/* Construct Var or expression representing the partition column */
 	if (key->partattrs[0] != 0)
@@ -1322,15 +1532,60 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 	else
 		keyCol = (Expr *) copyObject(linitial(key->partexprs));
 
-	/* Create list of Consts for the allowed values, excluding any nulls */
-	foreach(cell, spec->listdatums)
+	/*
+	 * In case of default partition for list, the partition constraint is
+	 * basically any value that is not equal to any of the values in
+	 * boundinfo->datums array. So, construct a list of constants from
+	 * boundinfo->datums to pass to function make_partition_op_expr via
+	 * ArrayExpr, which would return an negated expression for default
+	 * partition.
+	 */
+	if (is_default)
 	{
-		Const	   *val = castNode(Const, lfirst(cell));
+		int         i;
+		int         ndatums = 0;
+		MemoryContext oldcxt;
+		PartitionDesc pdesc = RelationGetPartitionDesc(parent);
+		PartitionBoundInfo boundinfo = pdesc->boundinfo;
 
-		if (val->constisnull)
+		ndatums = (pdesc->nparts > 0) ? boundinfo->ndatums : 0;
+		oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+
+		for (i = 0; i < ndatums; i++)
+		{
+			Const      *val;
+
+			/* Construct const from datum */
+			val = makeConst(key->parttypid[0],
+							key->parttypmod[0],
+							key->parttypcoll[0],
+							key->parttyplen[0],
+							*boundinfo->datums[i],
+							false,      /* isnull */
+							true /* byval */ );
+
+			arrelems = lappend(arrelems, val);
+		}
+		if (boundinfo && partition_bound_accepts_nulls(boundinfo))
 			list_has_null = true;
-		else
-			arrelems = lappend(arrelems, copyObject(val));
+
+		MemoryContextSwitchTo(oldcxt);
+	}
+	else
+	{
+		/*
+		 * Create list of Consts for the allowed values, excluding any
+		 * nulls.
+		 */
+		foreach(cell, spec->listdatums)
+		{
+			Const	   *val = castNode(Const, lfirst(cell));
+
+			if (val->constisnull)
+				list_has_null = true;
+			else
+				arrelems = lappend(arrelems, copyObject(val));
+		}
 	}
 
 	/* Construct an ArrayExpr for the non-null partition values */
@@ -1346,9 +1601,9 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 
 	/* Generate the main expression, i.e., keyCol = ANY (arr) */
 	opexpr = make_partition_op_expr(key, 0, BTEqualStrategyNumber,
-									keyCol, (Expr *) arr);
+									keyCol, (Expr *) arr, is_default);
 
-	if (!list_has_null)
+	if ((!list_has_null && !is_default) || (list_has_null && is_default))
 	{
 		/*
 		 * Gin up a "col IS NOT NULL" test that will be AND'd with the main
@@ -1591,7 +1846,7 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 		oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
 		test_expr = make_partition_op_expr(key, i, BTEqualStrategyNumber,
 										   (Expr *) lower_val,
-										   (Expr *) upper_val);
+										   (Expr *) upper_val, false);
 		fix_opfuncids((Node *) test_expr);
 		test_exprstate = ExecInitExpr(test_expr, NULL);
 		test_result = ExecEvalExprSwitchContext(test_exprstate,
@@ -1615,7 +1870,8 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 		/* Equal, so generate keyCol = lower_val expression */
 		result = lappend(result,
 						 make_partition_op_expr(key, i, BTEqualStrategyNumber,
-												keyCol, (Expr *) lower_val));
+												keyCol, (Expr *) lower_val,
+												false));
 
 		i++;
 	}
@@ -1676,7 +1932,8 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 											make_partition_op_expr(key, j,
 																   strategy,
 																   keyCol,
-														(Expr *) lower_val));
+														(Expr *) lower_val,
+																   false));
 			}
 
 			if (need_next_upper_arm && upper_val)
@@ -1698,7 +1955,8 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 											make_partition_op_expr(key, j,
 																   strategy,
 																   keyCol,
-														(Expr *) upper_val));
+														(Expr *) upper_val,
+																   false));
 			}
 
 			/*
@@ -1987,11 +2245,13 @@ get_partition_for_tuple(PartitionDispatch *pd,
 			}
 		}
 
+		cur_index = -1;
 		/*
-		 * A null partition key is only acceptable if null-accepting list
-		 * partition exists.
+		 * A null partition key is acceptable if null-accepting list partition
+		 * or a default partition exists. Check if there exists a null
+		 * accepting partition, else this will be handled later by default
+		 * partition if it exists.
 		 */
-		cur_index = -1;
 		if (isnull[0] && partition_bound_accepts_nulls(partdesc->boundinfo))
 			cur_index = partdesc->boundinfo->null_index;
 		else if (!isnull[0])
@@ -2027,16 +2287,28 @@ get_partition_for_tuple(PartitionDispatch *pd,
 		}
 
 		/*
-		 * cur_index < 0 means we failed to find a partition of this parent.
-		 * cur_index >= 0 means we either found the leaf partition, or the
-		 * next parent to find a partition of.
+		 * cur_index < 0 means we could not find a non-default partition of this
+		 * parent. cur_index >= 0 means we either found the leaf partition, or
+		 * the next parent to find a partition of.
 		 */
 		if (cur_index < 0)
 		{
-			result = -1;
-			*failed_at = parent;
-			*failed_slot = slot;
-			break;
+			if (partition_bound_has_default(partdesc->boundinfo))
+			{
+				result = parent->indexes[partdesc->boundinfo->default_index];
+
+				if (result >= 0)
+					break;
+				else
+					parent = pd[-result];
+			}
+			else
+			{
+				result = -1;
+				*failed_at = parent;
+				*failed_slot = slot;
+				break;
+			}
 		}
 		else if (parent->indexes[cur_index] >= 0)
 		{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4a899f1..dae592a 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -92,11 +92,6 @@ static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid,
 						  Bitmapset *modifiedCols,
 						  AclMode requiredPerms);
 static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
-static char *ExecBuildSlotValueDescription(Oid reloid,
-							  TupleTableSlot *slot,
-							  TupleDesc tupdesc,
-							  Bitmapset *modifiedCols,
-							  int maxfieldlen);
 static char *ExecBuildSlotPartitionKeyDescription(Relation rel,
 									 Datum *values,
 									 bool *isnull,
@@ -2174,7 +2169,7 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
  * column involved, that subset will be returned with a key identifying which
  * columns they are.
  */
-static char *
+char *
 ExecBuildSlotValueDescription(Oid reloid,
 							  TupleTableSlot *slot,
 							  TupleDesc tupdesc,
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7e03624..d592651 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2675,6 +2675,23 @@ ForValues:
 
 					$$ = n;
 				}
+
+			/*
+			 * A default partition, that can be partition of either LIST or RANGE
+			 * partitioned table.
+			 * Currently this is supported only for LIST partition.
+			 */
+			| DEFAULT
+				{
+					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+					n->listdatums =
+						list_make1(makeDefElem("default_partition", NULL, @1));
+					n->location = @1;
+
+					$$ = n;
+				}
+
 		;
 
 partbound_datum:
@@ -2716,6 +2733,7 @@ PartitionRangeDatum:
 
 					$$ = (Node *) n;
 				}
+
 		;
 
 /*****************************************************************************
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 9134fb9..bbc0dd6 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -3291,10 +3292,29 @@ transformPartitionBound(ParseState *pstate, Relation parent,
 		int32		coltypmod;
 
 		if (spec->strategy != PARTITION_STRATEGY_LIST)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
-				  errmsg("invalid bound specification for a list partition"),
-				   parser_errposition(pstate, exprLocation((Node *) spec))));
+		{
+			/*
+			 * 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->listdatums) == 1) &&
+				isDefaultPartitionBound((Node *) linitial(spec->listdatums)))
+			{
+				result_spec->strategy = PARTITION_STRATEGY_LIST;
+
+				/*
+				 * 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 list partition"),
+					parser_errposition(pstate, exprLocation((Node *) spec))));
+		}
 
 		/* Get the only column's name in case we need to output an error */
 		if (key->partattrs[0] != 0)
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 824d757..47810ae 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8652,6 +8652,18 @@ get_rule_expr(Node *node, deparse_context *context,
 					case PARTITION_STRATEGY_LIST:
 						Assert(spec->listdatums != NIL);
 
+						/*
+						 * If the boundspec is of Default partition, it does
+						 * not have list of datums, but has only one node to
+						 * indicate its a default partition.
+						 */
+						if (isDefaultPartitionBound(
+										(Node *) linitial(spec->listdatums)))
+						{
+							appendStringInfoString(buf, "DEFAULT");
+							break;
+						}
+
 						appendStringInfoString(buf, "FOR VALUES IN (");
 						sep = "";
 						foreach(cell, spec->listdatums)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 2abd087..b01ba07 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2044,7 +2044,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("ATTACH", "PARTITION", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 	else if (TailMatches2("FOR", "VALUES"))
 		COMPLETE_WITH_LIST2("FROM (", "IN (");
 
@@ -2483,7 +2483,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("PARTITION", "OF", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 
 /* CREATE TABLESPACE */
 	else if (Matches3("CREATE", "TABLESPACE", MatchAny))
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 0a1e468..3a80bd9 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -83,6 +83,7 @@ extern List *map_partition_varattnos(List *expr, int target_varno,
 						Relation partrel, Relation parent);
 extern List *RelationGetPartitionQual(Relation rel);
 extern Expr *get_partition_qual_relid(Oid relid);
+extern bool isDefaultPartitionBound(Node *value);
 
 /* For tuple routing */
 extern PartitionDispatch *RelationGetPartitionDispatchInfo(Relation rel,
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 8cc5f3a..8bd574c 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -537,5 +537,10 @@ extern void CheckCmdReplicaIdentity(Relation rel, CmdType cmd);
 
 extern void CheckSubscriptionRelkind(char relkind, const char *nspname,
 						 const char *relname);
+char *ExecBuildSlotValueDescription(Oid reloid,
+							  TupleTableSlot *slot,
+							  TupleDesc tupdesc,
+							  Bitmapset *modifiedCols,
+							  int maxfieldlen);
 
 #endif   /* EXECUTOR_H  */
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 8aadbb8..b800173 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3273,6 +3273,11 @@ SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::reg
 CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
 ERROR:  partition "fail_part" would overlap partition "part_1"
+-- attaching default partition overlaps if a default partition already exists
+CREATE TABLE part_def1 PARTITION OF list_parted DEFAULT;
+CREATE TABLE part_def2 (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION part_def2 DEFAULT;
+ERROR:  partition "part_def2" would overlap partition "part_def1"
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
 	a int,
@@ -3286,6 +3291,16 @@ ERROR:  partition constraint is violated by some row
 -- should be ok after deleting the bad row
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+-- check partition cannot be attached if default has some row for its values
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
+INSERT INTO list_parted2_def VALUES (11, 'z');
+CREATE TABLE part_3 (LIKE list_parted2);
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+ERROR:  new default partition constraint is violated by some row
+DETAIL:  Violating row contains (11, z).
+-- should be ok after deleting the bad row
+DELETE FROM list_parted2_def WHERE a = 11;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
 CREATE TABLE part_3_4 (
@@ -3338,9 +3353,22 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
 ERROR:  partition constraint is violated by some row
 -- delete the faulting row and also add a constraint to skip the scan
 DELETE FROM part_5_a WHERE a NOT IN (3);
-ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT NULL;
-ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
+ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5, 55)), ALTER a SET NOT NULL;
+ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5, 55);
 INFO:  partition constraint for table "part_5" is implied by existing constraints
+-- check that leaf partitons of default partition are scanned when
+-- attaching a partitioned table.
+ALTER TABLE part_5 DROP CONSTRAINT check_a;
+CREATE TABLE part5_def PARTITION OF part_5 DEFAULT PARTITION BY LIST(a);
+CREATE TABLE part5_def_p1 PARTITION OF part5_def FOR VALUES IN (44, 55);
+INSERT INTO part5_def_p1 VALUES (55, 'y');
+CREATE TABLE part5_p1 (LIKE part_5);
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+ERROR:  new default partition constraint is violated by some row
+DETAIL:  Violating row contains (55, y).
+-- should be ok after deleting the bad row
+DELETE FROM part5_def_p1 WHERE b = 'y';
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
 -- check that the table being attached is not already a partition
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 ERROR:  "part_2" is already a partition
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 5136506..92068ee 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -449,6 +449,7 @@ CREATE TABLE list_parted (
 CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
 CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
 CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
 ERROR:  syntax error at or near "int"
 LINE 1: ... fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
@@ -457,6 +458,8 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
 ERROR:  syntax error at or near "::"
 LINE 1: ...fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
                                                                 ^
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
+ERROR:  partition "fail_default_part" would overlap partition "part_default"
 -- syntax does not allow empty list of values for list partitions
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
 ERROR:  syntax error at or near ")"
@@ -514,6 +517,11 @@ 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);
@@ -565,10 +573,16 @@ CREATE TABLE list_parted2 (
 ) PARTITION BY LIST (a);
 CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
 CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
 ERROR:  partition "fail_part" would overlap partition "part_null_z"
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
 ERROR:  partition "fail_part" would overlap partition "part_ab"
+-- check default partition overlap
+INSERT INTO list_parted2 VALUES('X');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y');
+ERROR:  new default partition constraint is violated by some row
+DETAIL:  Violating row contains (X).
 CREATE TABLE range_parted2 (
 	a int
 ) PARTITION BY RANGE (a);
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 8b0752a..841f7c3 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -202,6 +202,7 @@ create table list_parted (
 create table part_aa_bb partition of list_parted FOR VALUES IN ('aa', 'bb');
 create table part_cc_dd partition of list_parted FOR VALUES IN ('cc', 'dd');
 create table part_null partition of list_parted FOR VALUES IN (null);
+create table part_default partition of list_parted default;
 -- fail
 insert into part_aa_bb values ('cc', 1);
 ERROR:  new row for relation "part_aa_bb" violates partition constraint
@@ -212,24 +213,46 @@ DETAIL:  Failing row contains (AAa, 1).
 insert into part_aa_bb values (null);
 ERROR:  new row for relation "part_aa_bb" violates partition constraint
 DETAIL:  Failing row contains (null, null).
+insert into part_default values ('aa', 2);
+ERROR:  new row for relation "part_default" violates partition constraint
+DETAIL:  Failing row contains (aa, 2).
+insert into part_default values (null, 2);
+ERROR:  new row for relation "part_default" violates partition constraint
+DETAIL:  Failing row contains (null, 2).
 -- ok
 insert into part_cc_dd values ('cC', 1);
 insert into part_null values (null, 0);
+insert into part_default values ('Zz', 2);
 -- check in case of multi-level partitioned table
 create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b);
 create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
 create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
+-- check in case of multi-level default partitioned table
+drop table part_default;
+create table part_default partition of list_parted default partition by range(b);
+create table part_default_p1 partition of part_default for values from (20) to (30);
+create table part_default_p2 partition of part_default for values from (30) to (40);
 -- fail
 insert into part_ee_ff1 values ('EE', 11);
 ERROR:  new row for relation "part_ee_ff1" violates partition constraint
 DETAIL:  Failing row contains (EE, 11).
+insert into part_default_p2 values ('gg', 43);
+ERROR:  new row for relation "part_default_p2" violates partition constraint
+DETAIL:  Failing row contains (gg, 43).
 -- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
 insert into part_ee_ff1 values ('cc', 1);
 ERROR:  new row for relation "part_ee_ff1" violates partition constraint
 DETAIL:  Failing row contains (cc, 1).
+insert into part_default values ('gg', 43);
+ERROR:  no partition of relation "part_default" found for row
+DETAIL:  Partition key of the failing row contains (b) = (43).
 -- ok
 insert into part_ee_ff1 values ('ff', 1);
 insert into part_ee_ff2 values ('ff', 11);
+insert into part_default_p1 values ('cd', 25);
+insert into part_default_p2 values ('gg', 35);
+insert into list_parted values ('ab', 21);
+drop table part_default;
 -- Check tuple routing for partitioned tables
 -- fail
 insert into range_parted values ('a', 0);
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index 9366f04..0f41e7e 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -218,5 +218,24 @@ 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;
+create table list_parted (
+	a text,
+	b int
+) partition by list (a);
+create table list_part1  partition of list_parted for values in ('a', 'b');
+create table list_default partition of list_parted default;
+insert into list_part1 values ('a', 1);
+insert into list_default values ('d', 10);
+-- fail
+update list_part1 set a = 'c' where a = 'a';
+ERROR:  new row for relation "list_part1" violates partition constraint
+DETAIL:  Failing row contains (c, 1).
+update list_default set a = 'a' where a = 'd';
+ERROR:  new row for relation "list_default" violates partition constraint
+DETAIL:  Failing row contains (a, 10).
+-- ok
+update list_part1 set a = 'b' where a = 'a';
+update list_default set a = 'x' where a = 'd';
 -- cleanup
 drop table range_parted;
+drop table list_parted;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index c41b487..aa1a4e3 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2095,6 +2095,10 @@ SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::reg
 -- check that the new partition won't overlap with an existing partition
 CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+-- attaching default partition overlaps if a default partition already exists
+CREATE TABLE part_def1 PARTITION OF list_parted DEFAULT;
+CREATE TABLE part_def2 (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION part_def2 DEFAULT;
 
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
@@ -2111,6 +2115,15 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 
+-- check partition cannot be attached if default has some row for its values
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
+INSERT INTO list_parted2_def VALUES (11, 'z');
+CREATE TABLE part_3 (LIKE list_parted2);
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+-- should be ok after deleting the bad row
+DELETE FROM list_parted2_def WHERE a = 11;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
 CREATE TABLE part_3_4 (
@@ -2169,9 +2182,20 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
 
 -- delete the faulting row and also add a constraint to skip the scan
 DELETE FROM part_5_a WHERE a NOT IN (3);
-ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT NULL;
-ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
-
+ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5, 55)), ALTER a SET NOT NULL;
+ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5, 55);
+
+-- check that leaf partitons of default partition are scanned when
+-- attaching a partitioned table.
+ALTER TABLE part_5 DROP CONSTRAINT check_a;
+CREATE TABLE part5_def PARTITION OF part_5 DEFAULT PARTITION BY LIST(a);
+CREATE TABLE part5_def_p1 PARTITION OF part5_def FOR VALUES IN (44, 55);
+INSERT INTO part5_def_p1 VALUES (55, 'y');
+CREATE TABLE part5_p1 (LIKE part_5);
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+-- should be ok after deleting the bad row
+DELETE FROM part5_def_p1 WHERE b = 'y';
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
 
 -- check that the table being attached is not already a partition
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index cb7aa5b..f44c0e0 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -439,8 +439,10 @@ CREATE TABLE list_parted (
 CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
 CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
 CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
 
 -- syntax does not allow empty list of values for list partitions
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
@@ -484,6 +486,8 @@ 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);
@@ -529,9 +533,13 @@ CREATE TABLE list_parted2 (
 ) PARTITION BY LIST (a);
 CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
 CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+-- check default partition overlap
+INSERT INTO list_parted2 VALUES('X');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y');
 
 CREATE TABLE range_parted2 (
 	a int
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index db8967b..a9f2289 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -118,27 +118,42 @@ create table list_parted (
 create table part_aa_bb partition of list_parted FOR VALUES IN ('aa', 'bb');
 create table part_cc_dd partition of list_parted FOR VALUES IN ('cc', 'dd');
 create table part_null partition of list_parted FOR VALUES IN (null);
+create table part_default partition of list_parted default;
 
 -- fail
 insert into part_aa_bb values ('cc', 1);
 insert into part_aa_bb values ('AAa', 1);
 insert into part_aa_bb values (null);
+insert into part_default values ('aa', 2);
+insert into part_default values (null, 2);
 -- ok
 insert into part_cc_dd values ('cC', 1);
 insert into part_null values (null, 0);
+insert into part_default values ('Zz', 2);
 
 -- check in case of multi-level partitioned table
 create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b);
 create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
 create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
+-- check in case of multi-level default partitioned table
+drop table part_default;
+create table part_default partition of list_parted default partition by range(b);
+create table part_default_p1 partition of part_default for values from (20) to (30);
+create table part_default_p2 partition of part_default for values from (30) to (40);
 
 -- fail
 insert into part_ee_ff1 values ('EE', 11);
+insert into part_default_p2 values ('gg', 43);
 -- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
 insert into part_ee_ff1 values ('cc', 1);
+insert into part_default values ('gg', 43);
 -- ok
 insert into part_ee_ff1 values ('ff', 1);
 insert into part_ee_ff2 values ('ff', 11);
+insert into part_default_p1 values ('cd', 25);
+insert into part_default_p2 values ('gg', 35);
+insert into list_parted values ('ab', 21);
+drop table part_default;
 
 -- Check tuple routing for partitioned tables
 
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 6637119..02840a4 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -125,5 +125,22 @@ update range_parted set b = b - 1 where b = 10;
 -- ok
 update range_parted set b = b + 1 where b = 10;
 
+create table list_parted (
+	a text,
+	b int
+) partition by list (a);
+create table list_part1  partition of list_parted for values in ('a', 'b');
+create table list_default partition of list_parted default;
+insert into list_part1 values ('a', 1);
+insert into list_default values ('d', 10);
+
+-- fail
+update list_part1 set a = 'c' where a = 'a';
+update list_default set a = 'a' where a = 'd';
+-- ok
+update list_part1 set a = 'b' where a = 'a';
+update list_default set a = 'x' where a = 'd';
+
 -- cleanup
 drop table range_parted;
+drop table list_parted;
#104Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Jeevan Ladhe (#103)
1 attachment(s)
Re: Adding support for Default partition in partitioning

Hi,

I have fixed the issue related to default partition constraints not getting
updated
after detaching a partition.

PFA.

Regards,
Jeevan Ladhe

On Tue, May 30, 2017 at 1:08 PM, Jeevan Ladhe <jeevan.ladhe@enterprisedb.com

Show quoted text

wrote:

Hi,

I have rebased the patch on the latest commit.
PFA.

There exists one issue reported by Rajkumar[1] off-line as following, where
describing the default partition after deleting null partition, does not
show
updated constraints. I am working on fixing this issue.

create table t1 (c1 int) partition by list (c1);
create table t11 partition of t1 for values in (1,2);
create table t12 partition of t1 default;
create table t13 partition of t1 for values in (10,11);
create table t14 partition of t1 for values in (null);

postgres=# \d+ t12
Table "public.t12"
Column | Type | Collation | Nullable | Default | Storage | Stats
target | Description
--------+---------+-----------+----------+---------+--------
-+--------------+-------------
c1 | integer | | | | plain |
|
Partition of: t1 DEFAULT
Partition constraint: ((c1 IS NOT NULL) AND (c1 <> ALL (ARRAY[1, 2, 10,
11])))

postgres=# alter table t1 detach partition t14;
ALTER TABLE
postgres=# \d+ t12
Table "public.t12"
Column | Type | Collation | Nullable | Default | Storage | Stats
target | Description
--------+---------+-----------+----------+---------+--------
-+--------------+-------------
c1 | integer | | | | plain |
|
Partition of: t1 DEFAULT
Partition constraint: ((c1 IS NOT NULL) AND (c1 <> ALL (ARRAY[1, 2, 10,
11])))

postgres=# insert into t1 values(null);
INSERT 0 1

Note that the parent correctly allows the nulls to be inserted.

[1] rajkumar.raghuwanshi@enterprisedb.com

Regards,
Jeevan Ladhe

On Tue, May 30, 2017 at 10:59 AM, Beena Emerson <memissemerson@gmail.com>
wrote:

On Mon, May 29, 2017 at 9:33 PM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

Hi,

I have rebased the patch on latest commit with few cosmetic changes.

The patch fix_listdatums_get_qual_for_list_v3.patch [1] needs to be

applied

before applying this patch.

[1] http://www.mail-archive.com/pgsql-hackers@postgresql.org/msg

315490.html

This needs a rebase again.

--

Beena Emerson

EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

Attachments:

default_partition_v17.patchapplication/octet-stream; name=default_partition_v17.patchDownload
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 0ce94f3..494af27 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1883,6 +1883,15 @@ heap_drop_with_catalog(Oid relid)
 	if (OidIsValid(parentOid))
 	{
 		/*
+		 * Default partition constraints are constructed run-time from the
+		 * constraints of its siblings(basically by negating them), so any
+		 * change in the siblings needs to rebuild the constraints of the
+		 * default partition. So, invalidate the sibling default partition's
+		 * relcache.
+		 */
+		InvalidateDefaultPartitionRelcache(parentOid);
+
+		/*
 		 * Invalidate the parent's relcache so that the partition is no longer
 		 * included in its partition descriptor.
 		 */
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 37fa145..e774379 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -35,6 +35,7 @@
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planmain.h"
+#include "optimizer/prep.h"
 #include "optimizer/var.h"
 #include "rewrite/rewriteManip.h"
 #include "storage/lmgr.h"
@@ -88,9 +89,15 @@ typedef struct PartitionBoundInfoData
 								 * partitioned table) */
 	int			null_index;		/* Index of the null-accepting partition; -1
 								 * if there isn't one */
+	int			default_index;	/* Index of the default partition if any; -1
+								 * if there isn't one */
 } PartitionBoundInfoData;
 
 #define partition_bound_accepts_nulls(bi) ((bi)->null_index != -1)
+#define partition_bound_has_default(bi) ((bi)->default_index != -1)
+
+#define DEFAULT_PARTITION_INDEX(parent) \
+	((parent)->rd_partdesc->boundinfo->default_index)
 
 /*
  * When qsort'ing partition bounds after reading from the catalog, each bound
@@ -121,14 +128,15 @@ static int32 qsort_partition_rbound_cmp(const void *a, const void *b,
 static Oid get_partition_operator(PartitionKey key, int col,
 					   StrategyNumber strategy, bool *need_relabel);
 static Expr *make_partition_op_expr(PartitionKey key, int keynum,
-					   uint16 strategy, Expr *arg1, Expr *arg2);
+					   uint16 strategy, Expr *arg1, Expr *arg2,
+					   bool is_default);
 static void get_range_key_properties(PartitionKey key, int keynum,
 						 PartitionRangeDatum *ldatum,
 						 PartitionRangeDatum *udatum,
 						 ListCell **partexprs_item,
 						 Expr **keyCol,
 						 Const **lower_val, Const **upper_val);
-static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_list(Relation parent, PartitionBoundSpec *spec);
 static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
 static List *generate_partition_qual(Relation rel);
 
@@ -147,6 +155,8 @@ static int32 partition_bound_cmp(PartitionKey key,
 static int partition_bound_bsearch(PartitionKey key,
 						PartitionBoundInfo boundinfo,
 						void *probe, bool probe_is_bound, bool *is_equal);
+static void check_default_allows_bound(Relation parent,
+						   PartitionBoundSpec *new_spec);
 
 /*
  * RelationBuildPartitionDesc
@@ -174,6 +184,7 @@ RelationBuildPartitionDesc(Relation rel)
 	/* List partitioning specific */
 	PartitionListValue **all_values = NULL;
 	int			null_index = -1;
+	int			default_index = -1;
 
 	/* Range partitioning specific */
 	PartitionRangeBound **rbounds = NULL;
@@ -254,6 +265,19 @@ RelationBuildPartitionDesc(Relation rel)
 				if (spec->strategy != PARTITION_STRATEGY_LIST)
 					elog(ERROR, "invalid strategy in partition bound spec");
 
+				/*
+				 * In case of default partition, just note the index, we do not
+				 * add this to non_null_values list.
+				 */
+				c = list_head(spec->listdatums);
+				if ((list_length(spec->listdatums) == 1) &&
+					isDefaultPartitionBound((Node *) lfirst(c)))
+				{
+					default_index = i;
+					i++;
+					continue;
+				}
+
 				foreach(c, spec->listdatums)
 				{
 					Const	   *val = castNode(Const, lfirst(c));
@@ -506,6 +530,17 @@ RelationBuildPartitionDesc(Relation rel)
 					else
 						boundinfo->null_index = -1;
 
+					/* Assign mapping index for 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];
+					}
+					else
+						boundinfo->default_index = -1;
+
 					/* All partition must now have a valid mapping */
 					Assert(next_index == nparts);
 					break;
@@ -561,6 +596,12 @@ RelationBuildPartitionDesc(Relation rel)
 						}
 					}
 					boundinfo->indexes[i] = -1;
+
+					/*
+					 * Currently range partition do not have default partition
+					 * support.
+					 */
+					boundinfo->default_index = -1;
 					break;
 				}
 
@@ -610,6 +651,9 @@ partition_bounds_equal(PartitionKey key,
 	if (b1->null_index != b2->null_index)
 		return false;
 
+	if (b1->default_index != b2->default_index)
+		return false;
+
 	for (i = 0; i < b1->ndatums; i++)
 	{
 		int			j;
@@ -672,6 +716,7 @@ check_new_partition_bound(char *relname, Relation parent,
 {
 	PartitionKey key = RelationGetPartitionKey(parent);
 	PartitionDesc partdesc = RelationGetPartitionDesc(parent);
+	PartitionBoundInfo boundinfo = partdesc->boundinfo;
 	ParseState *pstate = make_parsestate(NULL);
 	int			with = -1;
 	bool		overlap = false;
@@ -684,13 +729,29 @@ check_new_partition_bound(char *relname, Relation parent,
 
 				if (partdesc->nparts > 0)
 				{
-					PartitionBoundInfo boundinfo = partdesc->boundinfo;
 					ListCell   *cell;
 
 					Assert(boundinfo &&
 						   boundinfo->strategy == PARTITION_STRATEGY_LIST &&
 						   (boundinfo->ndatums > 0 ||
-							partition_bound_accepts_nulls(boundinfo)));
+							partition_bound_accepts_nulls(boundinfo) ||
+							partition_bound_has_default(boundinfo)));
+
+					/*
+					 * Default partition cannot be added if there already
+					 * exists one.
+					 */
+					cell = list_head(spec->listdatums);
+					if (cell && isDefaultPartitionBound((Node *) lfirst(cell)))
+					{
+						if (partition_bound_has_default(boundinfo))
+						{
+							overlap = true;
+							with = boundinfo->default_index;
+						}
+
+						break;
+					}
 
 					foreach(cell, spec->listdatums)
 					{
@@ -838,6 +899,139 @@ check_new_partition_bound(char *relname, Relation parent,
 						relname, get_rel_name(partdesc->oids[with])),
 				 parser_errposition(pstate, spec->location)));
 	}
+
+	/*
+	 * If a default partition exists, it's partition constraint will change
+	 * after the addition of this new partition such that it won't allow any
+	 * row that qualifies for this new partition. So, check if the existing
+	 * data in the default partition satisfies this *would be* default
+	 * partition constraint.
+	 */
+	if (partdesc->nparts > 0 && partition_bound_has_default(boundinfo))
+		check_default_allows_bound(parent, spec);
+}
+
+/*
+ * check_default_allows_bound
+ *
+ * Checks if there exists any row in default partition that passes the check
+ * for constraints of new partition, if any reports an error.
+ */
+static void
+check_default_allows_bound(Relation parent, PartitionBoundSpec *new_spec)
+{
+	Relation	default_rel;
+	int			default_index;
+	List	   *new_part_constraints = NIL;
+	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 = (List *) eval_const_expressions(NULL,
+											  (Node *) new_part_constraints);
+	new_part_constraints =
+		(List *) canonicalize_qual((Expr *) new_part_constraints);
+	new_part_constraints = list_make1(make_ands_explicit(new_part_constraints));
+
+	/* Generate the constraint and default execution states. */
+	default_index = DEFAULT_PARTITION_INDEX(parent);
+	default_rel = heap_open(parent->rd_partdesc->oids[default_index],
+							AccessExclusiveLock);
+
+	if (default_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		all_parts = find_all_inheritors(RelationGetRelid(default_rel),
+										AccessExclusiveLock, NULL);
+	else
+		all_parts = list_make1_oid(RelationGetRelid(default_rel));
+
+	foreach(lc, all_parts)
+	{
+		Oid			part_relid = lfirst_oid(lc);
+		Relation	part_rel;
+		Expr	   *constr;
+		Expr	   *partition_constraint;
+		EState	   *estate;
+		HeapTuple	tuple;
+		ExprState  *partqualstate = NULL;
+		Snapshot	snapshot;
+		TupleDesc	tupdesc;
+		ExprContext *econtext;
+		HeapScanDesc scan;
+		MemoryContext oldCxt;
+		TupleTableSlot *tupslot;
+
+		/* Lock already taken above. */
+		if (part_relid != RelationGetRelid(default_rel))
+			part_rel = heap_open(part_relid, NoLock);
+		else
+			part_rel = default_rel;
+
+		/*
+		 * Skip if it's a partitioned table. Only RELKIND_RELATION relations
+		 * (ie, leaf partitions) need to be scanned.
+		 */
+		if (part_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			heap_close(part_rel, NoLock);
+			continue;
+		}
+
+		tupdesc = CreateTupleDescCopy(RelationGetDescr(part_rel));
+		constr = linitial(new_part_constraints);
+		partition_constraint = (Expr *) map_partition_varattnos((List *) constr,
+														1, part_rel, parent);
+		estate = CreateExecutorState();
+
+		/* Build expression execution states for partition check quals */
+		partqualstate = ExecPrepareExpr(partition_constraint, estate);
+
+		econtext = GetPerTupleExprContext(estate);
+		snapshot = RegisterSnapshot(GetLatestSnapshot());
+		scan = heap_beginscan(part_rel, snapshot, 0, NULL);
+		tupslot = MakeSingleTupleTableSlot(tupdesc);
+
+		/*
+		 * Switch to per-tuple memory context and reset it for each tuple
+		 * produced, so we don't leak memory.
+		 */
+		oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		{
+			char	   *val_desc;
+
+			ExecStoreTuple(tuple, tupslot, InvalidBuffer, false);
+			econtext->ecxt_scantuple = tupslot;
+
+			if (partqualstate && ExecCheck(partqualstate, econtext))
+			{
+				val_desc =
+					ExecBuildSlotValueDescription(RelationGetRelid(part_rel),
+												  tupslot, tupdesc, NULL, 64);
+
+				ereport(ERROR,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("new default partition constraint is violated by some row"),
+						 val_desc ?
+					 errdetail("Violating row contains %s.", val_desc) : 0));
+			}
+
+			ResetExprContext(econtext);
+			CHECK_FOR_INTERRUPTS();
+		}
+
+		CacheInvalidateRelcache(part_rel);
+		MemoryContextSwitchTo(oldCxt);
+		heap_endscan(scan);
+		UnregisterSnapshot(snapshot);
+		ExecDropSingleTupleTableSlot(tupslot);
+		FreeExecutorState(estate);
+		heap_close(part_rel, NoLock);
+	}
 }
 
 /*
@@ -886,6 +1080,16 @@ get_partition_parent(Oid relid)
 }
 
 /*
+ * Returns true if the partition bound is default.
+ */
+bool
+isDefaultPartitionBound(Node *value)
+{
+	return (IsA(value, DefElem) &&
+			!strcmp(castNode(DefElem, value)->defname, "default_partition"));
+}
+
+/*
  * get_qual_from_partbound
  *		Given a parser node for partition bound, return the list of executable
  *		expressions as partition constraint
@@ -903,7 +1107,7 @@ get_qual_from_partbound(Relation rel, Relation parent,
 	{
 		case PARTITION_STRATEGY_LIST:
 			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
-			my_qual = get_qual_for_list(key, spec);
+			my_qual = get_qual_for_list(parent, spec);
 			break;
 
 		case PARTITION_STRATEGY_RANGE:
@@ -1233,7 +1437,7 @@ get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
  */
 static Expr *
 make_partition_op_expr(PartitionKey key, int keynum,
-					   uint16 strategy, Expr *arg1, Expr *arg2)
+					uint16 strategy, Expr *arg1, Expr *arg2, bool is_default)
 {
 	Oid			operoid;
 	bool		need_relabel = false;
@@ -1263,11 +1467,17 @@ make_partition_op_expr(PartitionKey key, int keynum,
 			{
 				ScalarArrayOpExpr *saopexpr;
 
+				if (is_default &&
+					((operoid = get_negator(operoid)) == InvalidOid))
+					ereport(ERROR, (errcode(ERRCODE_RESTRICT_VIOLATION),
+									errmsg("DEFAULT partition cannot be used without negator of operator  %s",
+										   get_opname(operoid))));
+
 				/* Build leftop = ANY (rightop) */
 				saopexpr = makeNode(ScalarArrayOpExpr);
 				saopexpr->opno = operoid;
 				saopexpr->opfuncid = get_opcode(operoid);
-				saopexpr->useOr = true;
+				saopexpr->useOr = !is_default;
 				saopexpr->inputcollid = key->partcollation[keynum];
 				saopexpr->args = list_make2(arg1, arg2);
 				saopexpr->location = -1;
@@ -1300,8 +1510,9 @@ make_partition_op_expr(PartitionKey key, int keynum,
  * constraint, given the partition key and bound structures.
  */
 static List *
-get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
+get_qual_for_list(Relation parent, PartitionBoundSpec *spec)
 {
+	PartitionKey key = RelationGetPartitionKey(parent);
 	List	   *result;
 	Expr	   *keyCol;
 	ArrayExpr  *arr;
@@ -1310,6 +1521,8 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 	ListCell   *cell;
 	List	   *arrelems = NIL;
 	bool		list_has_null = false;
+	bool		is_default =
+		isDefaultPartitionBound((Node *) linitial(spec->listdatums));
 
 	/* Construct Var or expression representing the partition column */
 	if (key->partattrs[0] != 0)
@@ -1322,15 +1535,60 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 	else
 		keyCol = (Expr *) copyObject(linitial(key->partexprs));
 
-	/* Create list of Consts for the allowed values, excluding any nulls */
-	foreach(cell, spec->listdatums)
+	/*
+	 * In case of default partition for list, the partition constraint is
+	 * basically any value that is not equal to any of the values in
+	 * boundinfo->datums array. So, construct a list of constants from
+	 * boundinfo->datums to pass to function make_partition_op_expr via
+	 * ArrayExpr, which would return an negated expression for default
+	 * partition.
+	 */
+	if (is_default)
 	{
-		Const	   *val = castNode(Const, lfirst(cell));
+		int         i;
+		int         ndatums = 0;
+		MemoryContext oldcxt;
+		PartitionDesc pdesc = RelationGetPartitionDesc(parent);
+		PartitionBoundInfo boundinfo = pdesc->boundinfo;
+
+		ndatums = (pdesc->nparts > 0) ? boundinfo->ndatums : 0;
+		oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
 
-		if (val->constisnull)
+		for (i = 0; i < ndatums; i++)
+		{
+			Const      *val;
+
+			/* Construct const from datum */
+			val = makeConst(key->parttypid[0],
+							key->parttypmod[0],
+							key->parttypcoll[0],
+							key->parttyplen[0],
+							*boundinfo->datums[i],
+							false,      /* isnull */
+							true /* byval */ );
+
+			arrelems = lappend(arrelems, val);
+		}
+		if (boundinfo && partition_bound_accepts_nulls(boundinfo))
 			list_has_null = true;
-		else
-			arrelems = lappend(arrelems, copyObject(val));
+
+		MemoryContextSwitchTo(oldcxt);
+	}
+	else
+	{
+		/*
+		 * Create list of Consts for the allowed values, excluding any
+		 * nulls.
+		 */
+		foreach(cell, spec->listdatums)
+		{
+			Const	   *val = castNode(Const, lfirst(cell));
+
+			if (val->constisnull)
+				list_has_null = true;
+			else
+				arrelems = lappend(arrelems, copyObject(val));
+		}
 	}
 
 	/* Construct an ArrayExpr for the non-null partition values */
@@ -1346,9 +1604,9 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 
 	/* Generate the main expression, i.e., keyCol = ANY (arr) */
 	opexpr = make_partition_op_expr(key, 0, BTEqualStrategyNumber,
-									keyCol, (Expr *) arr);
+									keyCol, (Expr *) arr, is_default);
 
-	if (!list_has_null)
+	if ((!list_has_null && !is_default) || (list_has_null && is_default))
 	{
 		/*
 		 * Gin up a "col IS NOT NULL" test that will be AND'd with the main
@@ -1591,7 +1849,7 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 		oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
 		test_expr = make_partition_op_expr(key, i, BTEqualStrategyNumber,
 										   (Expr *) lower_val,
-										   (Expr *) upper_val);
+										   (Expr *) upper_val, false);
 		fix_opfuncids((Node *) test_expr);
 		test_exprstate = ExecInitExpr(test_expr, NULL);
 		test_result = ExecEvalExprSwitchContext(test_exprstate,
@@ -1615,7 +1873,8 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 		/* Equal, so generate keyCol = lower_val expression */
 		result = lappend(result,
 						 make_partition_op_expr(key, i, BTEqualStrategyNumber,
-												keyCol, (Expr *) lower_val));
+												keyCol, (Expr *) lower_val,
+												false));
 
 		i++;
 	}
@@ -1676,7 +1935,8 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 											make_partition_op_expr(key, j,
 																   strategy,
 																   keyCol,
-														(Expr *) lower_val));
+														(Expr *) lower_val,
+																   false));
 			}
 
 			if (need_next_upper_arm && upper_val)
@@ -1698,7 +1958,8 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 											make_partition_op_expr(key, j,
 																   strategy,
 																   keyCol,
-														(Expr *) upper_val));
+														(Expr *) upper_val,
+																   false));
 			}
 
 			/*
@@ -1987,11 +2248,13 @@ get_partition_for_tuple(PartitionDispatch *pd,
 			}
 		}
 
+		cur_index = -1;
 		/*
-		 * A null partition key is only acceptable if null-accepting list
-		 * partition exists.
+		 * A null partition key is acceptable if null-accepting list partition
+		 * or a default partition exists. Check if there exists a null
+		 * accepting partition, else this will be handled later by default
+		 * partition if it exists.
 		 */
-		cur_index = -1;
 		if (isnull[0] && partition_bound_accepts_nulls(partdesc->boundinfo))
 			cur_index = partdesc->boundinfo->null_index;
 		else if (!isnull[0])
@@ -2027,16 +2290,28 @@ get_partition_for_tuple(PartitionDispatch *pd,
 		}
 
 		/*
-		 * cur_index < 0 means we failed to find a partition of this parent.
-		 * cur_index >= 0 means we either found the leaf partition, or the
-		 * next parent to find a partition of.
+		 * cur_index < 0 means we could not find a non-default partition of this
+		 * parent. cur_index >= 0 means we either found the leaf partition, or
+		 * the next parent to find a partition of.
 		 */
 		if (cur_index < 0)
 		{
-			result = -1;
-			*failed_at = parent;
-			*failed_slot = slot;
-			break;
+			if (partition_bound_has_default(partdesc->boundinfo))
+			{
+				result = parent->indexes[partdesc->boundinfo->default_index];
+
+				if (result >= 0)
+					break;
+				else
+					parent = pd[-result];
+			}
+			else
+			{
+				result = -1;
+				*failed_at = parent;
+				*failed_slot = slot;
+				break;
+			}
 		}
 		else if (parent->indexes[cur_index] >= 0)
 		{
@@ -2312,3 +2587,21 @@ partition_bound_bsearch(PartitionKey key, PartitionBoundInfo boundinfo,
 
 	return lo;
 }
+
+/*
+ * InvalidateDefaultPartitionRelcache
+ *
+ * Given a parent oid, this function checks if there exists a default partition
+ * and invalidates it's relcache if it exists.
+ */
+void
+InvalidateDefaultPartitionRelcache(Oid parentOid)
+{
+	Relation parent = heap_open(parentOid, AccessShareLock);
+	Oid default_relid = parent->rd_partdesc->oids[DEFAULT_PARTITION_INDEX(parent)];
+
+	if (partition_bound_has_default(parent->rd_partdesc->boundinfo))
+		CacheInvalidateRelcacheByRelid(default_relid);
+
+	heap_close(parent, AccessShareLock);
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 7959120..5f3edfe 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13829,6 +13829,15 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 	heap_close(classRel, RowExclusiveLock);
 
 	/*
+	 * Default partition constraints are constructed run-time from the
+	 * constraints of its siblings(basically by negating them), so any
+	 * change in the siblings needs to rebuild the constraints of the
+	 * default partition. So, invalidate the sibling default partition's
+	 * relcache.
+	 */
+	InvalidateDefaultPartitionRelcache(RelationGetRelid(rel));
+
+	/*
 	 * Invalidate the parent's relcache so that the partition is no longer
 	 * included in its partition descriptor.
 	 */
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4a899f1..dae592a 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -92,11 +92,6 @@ static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid,
 						  Bitmapset *modifiedCols,
 						  AclMode requiredPerms);
 static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
-static char *ExecBuildSlotValueDescription(Oid reloid,
-							  TupleTableSlot *slot,
-							  TupleDesc tupdesc,
-							  Bitmapset *modifiedCols,
-							  int maxfieldlen);
 static char *ExecBuildSlotPartitionKeyDescription(Relation rel,
 									 Datum *values,
 									 bool *isnull,
@@ -2174,7 +2169,7 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
  * column involved, that subset will be returned with a key identifying which
  * columns they are.
  */
-static char *
+char *
 ExecBuildSlotValueDescription(Oid reloid,
 							  TupleTableSlot *slot,
 							  TupleDesc tupdesc,
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7e03624..d592651 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2675,6 +2675,23 @@ ForValues:
 
 					$$ = n;
 				}
+
+			/*
+			 * A default partition, that can be partition of either LIST or RANGE
+			 * partitioned table.
+			 * Currently this is supported only for LIST partition.
+			 */
+			| DEFAULT
+				{
+					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+					n->listdatums =
+						list_make1(makeDefElem("default_partition", NULL, @1));
+					n->location = @1;
+
+					$$ = n;
+				}
+
 		;
 
 partbound_datum:
@@ -2716,6 +2733,7 @@ PartitionRangeDatum:
 
 					$$ = (Node *) n;
 				}
+
 		;
 
 /*****************************************************************************
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 9134fb9..bbc0dd6 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -3291,10 +3292,29 @@ transformPartitionBound(ParseState *pstate, Relation parent,
 		int32		coltypmod;
 
 		if (spec->strategy != PARTITION_STRATEGY_LIST)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
-				  errmsg("invalid bound specification for a list partition"),
-				   parser_errposition(pstate, exprLocation((Node *) spec))));
+		{
+			/*
+			 * 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->listdatums) == 1) &&
+				isDefaultPartitionBound((Node *) linitial(spec->listdatums)))
+			{
+				result_spec->strategy = PARTITION_STRATEGY_LIST;
+
+				/*
+				 * 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 list partition"),
+					parser_errposition(pstate, exprLocation((Node *) spec))));
+		}
 
 		/* Get the only column's name in case we need to output an error */
 		if (key->partattrs[0] != 0)
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 824d757..47810ae 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8652,6 +8652,18 @@ get_rule_expr(Node *node, deparse_context *context,
 					case PARTITION_STRATEGY_LIST:
 						Assert(spec->listdatums != NIL);
 
+						/*
+						 * If the boundspec is of Default partition, it does
+						 * not have list of datums, but has only one node to
+						 * indicate its a default partition.
+						 */
+						if (isDefaultPartitionBound(
+										(Node *) linitial(spec->listdatums)))
+						{
+							appendStringInfoString(buf, "DEFAULT");
+							break;
+						}
+
 						appendStringInfoString(buf, "FOR VALUES IN (");
 						sep = "";
 						foreach(cell, spec->listdatums)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 2abd087..b01ba07 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2044,7 +2044,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("ATTACH", "PARTITION", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 	else if (TailMatches2("FOR", "VALUES"))
 		COMPLETE_WITH_LIST2("FROM (", "IN (");
 
@@ -2483,7 +2483,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("PARTITION", "OF", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 
 /* CREATE TABLESPACE */
 	else if (Matches3("CREATE", "TABLESPACE", MatchAny))
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 0a1e468..14f926e 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -83,6 +83,7 @@ extern List *map_partition_varattnos(List *expr, int target_varno,
 						Relation partrel, Relation parent);
 extern List *RelationGetPartitionQual(Relation rel);
 extern Expr *get_partition_qual_relid(Oid relid);
+extern bool isDefaultPartitionBound(Node *value);
 
 /* For tuple routing */
 extern PartitionDispatch *RelationGetPartitionDispatchInfo(Relation rel,
@@ -98,4 +99,5 @@ extern int get_partition_for_tuple(PartitionDispatch *pd,
 						EState *estate,
 						PartitionDispatchData **failed_at,
 						TupleTableSlot **failed_slot);
+extern void InvalidateDefaultPartitionRelcache(Oid parentOid);
 #endif   /* PARTITION_H */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 8cc5f3a..8bd574c 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -537,5 +537,10 @@ extern void CheckCmdReplicaIdentity(Relation rel, CmdType cmd);
 
 extern void CheckSubscriptionRelkind(char relkind, const char *nspname,
 						 const char *relname);
+char *ExecBuildSlotValueDescription(Oid reloid,
+							  TupleTableSlot *slot,
+							  TupleDesc tupdesc,
+							  Bitmapset *modifiedCols,
+							  int maxfieldlen);
 
 #endif   /* EXECUTOR_H  */
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 8aadbb8..b800173 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3273,6 +3273,11 @@ SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::reg
 CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
 ERROR:  partition "fail_part" would overlap partition "part_1"
+-- attaching default partition overlaps if a default partition already exists
+CREATE TABLE part_def1 PARTITION OF list_parted DEFAULT;
+CREATE TABLE part_def2 (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION part_def2 DEFAULT;
+ERROR:  partition "part_def2" would overlap partition "part_def1"
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
 	a int,
@@ -3286,6 +3291,16 @@ ERROR:  partition constraint is violated by some row
 -- should be ok after deleting the bad row
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+-- check partition cannot be attached if default has some row for its values
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
+INSERT INTO list_parted2_def VALUES (11, 'z');
+CREATE TABLE part_3 (LIKE list_parted2);
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+ERROR:  new default partition constraint is violated by some row
+DETAIL:  Violating row contains (11, z).
+-- should be ok after deleting the bad row
+DELETE FROM list_parted2_def WHERE a = 11;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
 CREATE TABLE part_3_4 (
@@ -3338,9 +3353,22 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
 ERROR:  partition constraint is violated by some row
 -- delete the faulting row and also add a constraint to skip the scan
 DELETE FROM part_5_a WHERE a NOT IN (3);
-ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT NULL;
-ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
+ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5, 55)), ALTER a SET NOT NULL;
+ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5, 55);
 INFO:  partition constraint for table "part_5" is implied by existing constraints
+-- check that leaf partitons of default partition are scanned when
+-- attaching a partitioned table.
+ALTER TABLE part_5 DROP CONSTRAINT check_a;
+CREATE TABLE part5_def PARTITION OF part_5 DEFAULT PARTITION BY LIST(a);
+CREATE TABLE part5_def_p1 PARTITION OF part5_def FOR VALUES IN (44, 55);
+INSERT INTO part5_def_p1 VALUES (55, 'y');
+CREATE TABLE part5_p1 (LIKE part_5);
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+ERROR:  new default partition constraint is violated by some row
+DETAIL:  Violating row contains (55, y).
+-- should be ok after deleting the bad row
+DELETE FROM part5_def_p1 WHERE b = 'y';
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
 -- check that the table being attached is not already a partition
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 ERROR:  "part_2" is already a partition
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 5136506..92068ee 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -449,6 +449,7 @@ CREATE TABLE list_parted (
 CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
 CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
 CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
 ERROR:  syntax error at or near "int"
 LINE 1: ... fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
@@ -457,6 +458,8 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
 ERROR:  syntax error at or near "::"
 LINE 1: ...fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
                                                                 ^
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
+ERROR:  partition "fail_default_part" would overlap partition "part_default"
 -- syntax does not allow empty list of values for list partitions
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
 ERROR:  syntax error at or near ")"
@@ -514,6 +517,11 @@ 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);
@@ -565,10 +573,16 @@ CREATE TABLE list_parted2 (
 ) PARTITION BY LIST (a);
 CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
 CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
 ERROR:  partition "fail_part" would overlap partition "part_null_z"
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
 ERROR:  partition "fail_part" would overlap partition "part_ab"
+-- check default partition overlap
+INSERT INTO list_parted2 VALUES('X');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y');
+ERROR:  new default partition constraint is violated by some row
+DETAIL:  Violating row contains (X).
 CREATE TABLE range_parted2 (
 	a int
 ) PARTITION BY RANGE (a);
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 8b0752a..841f7c3 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -202,6 +202,7 @@ create table list_parted (
 create table part_aa_bb partition of list_parted FOR VALUES IN ('aa', 'bb');
 create table part_cc_dd partition of list_parted FOR VALUES IN ('cc', 'dd');
 create table part_null partition of list_parted FOR VALUES IN (null);
+create table part_default partition of list_parted default;
 -- fail
 insert into part_aa_bb values ('cc', 1);
 ERROR:  new row for relation "part_aa_bb" violates partition constraint
@@ -212,24 +213,46 @@ DETAIL:  Failing row contains (AAa, 1).
 insert into part_aa_bb values (null);
 ERROR:  new row for relation "part_aa_bb" violates partition constraint
 DETAIL:  Failing row contains (null, null).
+insert into part_default values ('aa', 2);
+ERROR:  new row for relation "part_default" violates partition constraint
+DETAIL:  Failing row contains (aa, 2).
+insert into part_default values (null, 2);
+ERROR:  new row for relation "part_default" violates partition constraint
+DETAIL:  Failing row contains (null, 2).
 -- ok
 insert into part_cc_dd values ('cC', 1);
 insert into part_null values (null, 0);
+insert into part_default values ('Zz', 2);
 -- check in case of multi-level partitioned table
 create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b);
 create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
 create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
+-- check in case of multi-level default partitioned table
+drop table part_default;
+create table part_default partition of list_parted default partition by range(b);
+create table part_default_p1 partition of part_default for values from (20) to (30);
+create table part_default_p2 partition of part_default for values from (30) to (40);
 -- fail
 insert into part_ee_ff1 values ('EE', 11);
 ERROR:  new row for relation "part_ee_ff1" violates partition constraint
 DETAIL:  Failing row contains (EE, 11).
+insert into part_default_p2 values ('gg', 43);
+ERROR:  new row for relation "part_default_p2" violates partition constraint
+DETAIL:  Failing row contains (gg, 43).
 -- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
 insert into part_ee_ff1 values ('cc', 1);
 ERROR:  new row for relation "part_ee_ff1" violates partition constraint
 DETAIL:  Failing row contains (cc, 1).
+insert into part_default values ('gg', 43);
+ERROR:  no partition of relation "part_default" found for row
+DETAIL:  Partition key of the failing row contains (b) = (43).
 -- ok
 insert into part_ee_ff1 values ('ff', 1);
 insert into part_ee_ff2 values ('ff', 11);
+insert into part_default_p1 values ('cd', 25);
+insert into part_default_p2 values ('gg', 35);
+insert into list_parted values ('ab', 21);
+drop table part_default;
 -- Check tuple routing for partitioned tables
 -- fail
 insert into range_parted values ('a', 0);
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index 9366f04..0f41e7e 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -218,5 +218,24 @@ 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;
+create table list_parted (
+	a text,
+	b int
+) partition by list (a);
+create table list_part1  partition of list_parted for values in ('a', 'b');
+create table list_default partition of list_parted default;
+insert into list_part1 values ('a', 1);
+insert into list_default values ('d', 10);
+-- fail
+update list_part1 set a = 'c' where a = 'a';
+ERROR:  new row for relation "list_part1" violates partition constraint
+DETAIL:  Failing row contains (c, 1).
+update list_default set a = 'a' where a = 'd';
+ERROR:  new row for relation "list_default" violates partition constraint
+DETAIL:  Failing row contains (a, 10).
+-- ok
+update list_part1 set a = 'b' where a = 'a';
+update list_default set a = 'x' where a = 'd';
 -- cleanup
 drop table range_parted;
+drop table list_parted;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index c41b487..aa1a4e3 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2095,6 +2095,10 @@ SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::reg
 -- check that the new partition won't overlap with an existing partition
 CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+-- attaching default partition overlaps if a default partition already exists
+CREATE TABLE part_def1 PARTITION OF list_parted DEFAULT;
+CREATE TABLE part_def2 (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION part_def2 DEFAULT;
 
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
@@ -2111,6 +2115,15 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 
+-- check partition cannot be attached if default has some row for its values
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
+INSERT INTO list_parted2_def VALUES (11, 'z');
+CREATE TABLE part_3 (LIKE list_parted2);
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+-- should be ok after deleting the bad row
+DELETE FROM list_parted2_def WHERE a = 11;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
 CREATE TABLE part_3_4 (
@@ -2169,9 +2182,20 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
 
 -- delete the faulting row and also add a constraint to skip the scan
 DELETE FROM part_5_a WHERE a NOT IN (3);
-ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT NULL;
-ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
-
+ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5, 55)), ALTER a SET NOT NULL;
+ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5, 55);
+
+-- check that leaf partitons of default partition are scanned when
+-- attaching a partitioned table.
+ALTER TABLE part_5 DROP CONSTRAINT check_a;
+CREATE TABLE part5_def PARTITION OF part_5 DEFAULT PARTITION BY LIST(a);
+CREATE TABLE part5_def_p1 PARTITION OF part5_def FOR VALUES IN (44, 55);
+INSERT INTO part5_def_p1 VALUES (55, 'y');
+CREATE TABLE part5_p1 (LIKE part_5);
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+-- should be ok after deleting the bad row
+DELETE FROM part5_def_p1 WHERE b = 'y';
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
 
 -- check that the table being attached is not already a partition
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index cb7aa5b..f44c0e0 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -439,8 +439,10 @@ CREATE TABLE list_parted (
 CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
 CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
 CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
 
 -- syntax does not allow empty list of values for list partitions
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
@@ -484,6 +486,8 @@ 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);
@@ -529,9 +533,13 @@ CREATE TABLE list_parted2 (
 ) PARTITION BY LIST (a);
 CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
 CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+-- check default partition overlap
+INSERT INTO list_parted2 VALUES('X');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y');
 
 CREATE TABLE range_parted2 (
 	a int
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index db8967b..a9f2289 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -118,27 +118,42 @@ create table list_parted (
 create table part_aa_bb partition of list_parted FOR VALUES IN ('aa', 'bb');
 create table part_cc_dd partition of list_parted FOR VALUES IN ('cc', 'dd');
 create table part_null partition of list_parted FOR VALUES IN (null);
+create table part_default partition of list_parted default;
 
 -- fail
 insert into part_aa_bb values ('cc', 1);
 insert into part_aa_bb values ('AAa', 1);
 insert into part_aa_bb values (null);
+insert into part_default values ('aa', 2);
+insert into part_default values (null, 2);
 -- ok
 insert into part_cc_dd values ('cC', 1);
 insert into part_null values (null, 0);
+insert into part_default values ('Zz', 2);
 
 -- check in case of multi-level partitioned table
 create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b);
 create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
 create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
+-- check in case of multi-level default partitioned table
+drop table part_default;
+create table part_default partition of list_parted default partition by range(b);
+create table part_default_p1 partition of part_default for values from (20) to (30);
+create table part_default_p2 partition of part_default for values from (30) to (40);
 
 -- fail
 insert into part_ee_ff1 values ('EE', 11);
+insert into part_default_p2 values ('gg', 43);
 -- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
 insert into part_ee_ff1 values ('cc', 1);
+insert into part_default values ('gg', 43);
 -- ok
 insert into part_ee_ff1 values ('ff', 1);
 insert into part_ee_ff2 values ('ff', 11);
+insert into part_default_p1 values ('cd', 25);
+insert into part_default_p2 values ('gg', 35);
+insert into list_parted values ('ab', 21);
+drop table part_default;
 
 -- Check tuple routing for partitioned tables
 
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 6637119..02840a4 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -125,5 +125,22 @@ update range_parted set b = b - 1 where b = 10;
 -- ok
 update range_parted set b = b + 1 where b = 10;
 
+create table list_parted (
+	a text,
+	b int
+) partition by list (a);
+create table list_part1  partition of list_parted for values in ('a', 'b');
+create table list_default partition of list_parted default;
+insert into list_part1 values ('a', 1);
+insert into list_default values ('d', 10);
+
+-- fail
+update list_part1 set a = 'c' where a = 'a';
+update list_default set a = 'a' where a = 'd';
+-- ok
+update list_part1 set a = 'b' where a = 'a';
+update list_default set a = 'x' where a = 'd';
+
 -- cleanup
 drop table range_parted;
+drop table list_parted;
#105Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Jeevan Ladhe (#103)
Re: Adding support for Default partition in partitioning

On Tue, May 30, 2017 at 1:08 PM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

Hi,

I have rebased the patch on the latest commit.
PFA.

Thanks for rebasing the patch. Here are some review comments.
+                /*
+                 * In case of default partition, just note the index, we do not
+                 * add this to non_null_values list.
+                 */
We may want to rephrase it like
"Note the index of the partition bound spec for the default partition. There's
no datum to add to the list of non-null datums for this partition."

/* Assign mapping index for default partition. */
"mapping index" should be "mapped index". May be we want to use "the" before
default partition everywhere, there's only one specific default partition.

Assert(default_index >= 0 &&
mapping[default_index] == -1);
Needs some explanation for asserting mapping[default_index] == -1. Since
default partition accepts any non-specified value, it should not get a mapped
index while assigning those for non-null datums.

+ * Currently range partition do not have default partition
May be rephrased as "As of now, we do not support default range partition."

+ * ArrayExpr, which would return an negated expression for default
a negated instead of an negated.

+        cur_index = -1;
         /*
-         * A null partition key is only acceptable if null-accepting list
-         * partition exists.
+         * A null partition key is acceptable if null-accepting list partition
+         * or a default partition exists. Check if there exists a null
+         * accepting partition, else this will be handled later by default
+         * partition if it exists.
          */
-        cur_index = -1;
Why do we need to move assignment to cur_index before the comment.
The comment should probably change to "Handle NULL partition key here
if there's a
null-accepting list partition. Else it will routed to a default partition if
one exists."
+-- attaching default partition overlaps if a default partition already exists
+ERROR:  partition "part_def2" would overlap partition "part_def1"
Saying a default partition overlaps is misleading here. A default partition is
not exepected to overlap with anything. It's expected to "adjust" with the rest
of the partitions. It can "conflict" with another default partition. So the
right error message here is "a default partition "part_def1" already exists."
+CREATE TABLE part_def1 PARTITION OF list_parted DEFAULT;
+CREATE TABLE part_def2 (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION part_def2 DEFAULT;
May be you want to name part_def1 as def_part and part_def2 as fail_def_part to
be consistent with other names in the file. May be you want to test to
consecutive CREATE TABLE ... DEFAULT.
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+ERROR:  new default partition constraint is violated by some row
+DETAIL:  Violating row contains (11, z).
The error message seems to be misleading. The default partition is not new. May
be we should say, "default partition contains rows that conflict with the
partition bounds of "part_3"". I think we should use a better word instead of
"conflict", but I am not able to find one right now.

+-- check that leaf partitons of default partition are scanned when
s/partitons/partitions/

-ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a
SET NOT NULL;
-ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
+ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5, 55)), ALTER
a SET NOT NULL;
+ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5, 55);
Why do we want to change partition bounds of this one? The test is for children
of part_5 right?

+drop table part_default;
I think this is premature drop. Down the file there's a SELECT from
list_parted, which won't list the rows inserted to the default partition and we
will miss to check whether the tuples were routed to the right partition or
not.

+update list_part1 set a = 'c' where a = 'a';
+ERROR:  new row for relation "list_part1" violates partition constraint
+DETAIL:  Failing row contains (c, 1).
Why do we need this test here? It's not dealing with the default partition and
partition row movement is not in there. So the updated row may not move to the
default partition, even if it's there.

This isn't a complete review. I will continue to review this patch further.

--
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

#106Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Jeevan Ladhe (#103)
Re: Adding support for Default partition in partitioning

Hi Jeevan,

On 2017/05/30 16:38, Jeevan Ladhe wrote:

I have rebased the patch on the latest commit.
PFA.

Was looking at the patch and felt that the parse node representation of
default partition bound could be slightly different. Can you explain the
motivation behind implementing it without adding a new member to the
PartitionBoundSpec struct?

I would suggest instead adding a bool named is_default and be done with
it. It will help get rid of the public isDefaultPartitionBound() in the
proposed patch whose interface isn't quite clear and instead simply check
if (spec->is_default) in places where it's called by passing it (Node *)
linitial(spec->listdatums).

Further looking into the patch, I found a tiny problem in
check_default_allows_bound(). If the default partition that will be
scanned by it is a foreign table or a partitioned table with a foreign
leaf partition, you will get a failure like:

-- default partition is a foreign table
alter table p attach partition fp default;

-- adding a new partition will try to scan fp above
alter table p attach partition p12 for values in (1, 2);
ERROR: could not open file "base/13158/16456": No such file or directory

I think the foreign tables should be ignored here to avoid the error. The
fact that foreign default partition may contain data that satisfies the
new partition's constraint is something we cannot do much about. Also,
see the note in ATTACH PARTITION description regarding foreign tables [1]https://www.postgresql.org/docs/devel/static/sql-altertable.html
and the discussion at [2]/messages/by-id/8f89dcb2-bd15-d8dc-5f54-3e11dc6c9463@lab.ntt.co.jp.

Thanks,
Amit

[1]: https://www.postgresql.org/docs/devel/static/sql-altertable.html
[2]: /messages/by-id/8f89dcb2-bd15-d8dc-5f54-3e11dc6c9463@lab.ntt.co.jp
/messages/by-id/8f89dcb2-bd15-d8dc-5f54-3e11dc6c9463@lab.ntt.co.jp

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#107Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Amit Langote (#106)
Re: Adding support for Default partition in partitioning

Thanks Amit for your comments.

On 31-May-2017 6:03 AM, "Amit Langote" <Langote_Amit_f8@lab.ntt.co.jp>
wrote:

Hi Jeevan,

On 2017/05/30 16:38, Jeevan Ladhe wrote:

I have rebased the patch on the latest commit.
PFA.

Was looking at the patch and felt that the parse node representation of
default partition bound could be slightly different. Can you explain the
motivation behind implementing it without adding a new member to the
PartitionBoundSpec struct?

I would suggest instead adding a bool named is_default and be done with
it. It will help get rid of the public isDefaultPartitionBound() in the
proposed patch whose interface isn't quite clear and instead simply check
if (spec->is_default) in places where it's called by passing it (Node *)
linitial(spec->listdatums).

I thought of reusing the existing members of PartitionBoundSpec, but I
agree that having a bool could simplify the code. Will do the receptive
change.

Further looking into the patch, I found a tiny problem in
check_default_allows_bound(). If the default partition that will be
scanned by it is a foreign table or a partitioned table with a foreign
leaf partition, you will get a failure like:

-- default partition is a foreign table
alter table p attach partition fp default;

-- adding a new partition will try to scan fp above
alter table p attach partition p12 for values in (1, 2);
ERROR: could not open file "base/13158/16456": No such file or directory

I think the foreign tables should be ignored here to avoid the error. The
fact that foreign default partition may contain data that satisfies the
new partition's constraint is something we cannot do much about. Also,
see the note in ATTACH PARTITION description regarding foreign tables [1]
and the discussion at [2].

Will look into this.

Regards,
Jeevan Ladhe

#108Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Amit Langote (#106)
Re: Adding support for Default partition in partitioning

On 2017/05/31 9:33, Amit Langote wrote:

On 2017/05/30 16:38, Jeevan Ladhe wrote:

I have rebased the patch on the latest commit.
PFA.

Was looking at the patch

I tried creating default partition of a range-partitioned table and got
the following error:

ERROR: invalid bound specification for a range partition

I thought it would give:

ERROR: creating default partition is not supported for range partitioned
tables

Which means transformPartitionBound() should perform this check more
carefully. As I suggested in my previous email, if there were a
is_default field in the PartitionBoundSpec, then one could add the
following block of code at the beginning of transformPartitionBound:

if (spec->is_default && spec->strategy != PARTITION_STRATEGY_LIST)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("creating default partition is not supported for %s
partitioned tables", get_partition_strategy_name(key->strategy))));

Some more comments on the patch:

+ errmsg("new default partition constraint is
violated by some row"),

"new default partition constraint" may sound a bit confusing to users.
That we recompute the default partition's constraint and check the "new
constraint" against the rows it contains seems to me to be the description
of internal details. How about:

ERROR: default partition contains rows that belong to partition being created

+char *ExecBuildSlotValueDescription(Oid reloid,
+                              TupleTableSlot *slot,
+                              TupleDesc tupdesc,
+                              Bitmapset *modifiedCols,
+                              int maxfieldlen);

It seems that you made the above public to use it in
check_default_allows_bound(), which while harmless, I'm not sure if
needed. ATRewriteTable() in tablecmds.c, for example, emits the following
error messages:

errmsg("check constraint \"%s\" is violated by some row",

errmsg("partition constraint is violated by some row")));

but neither outputs the DETAIL part showing exactly what row. I think
it's fine for check_default_allows_bound() not to show the row itself and
hence no need to make ExecBuildSlotValueDescription public.

In get_rule_expr():

case PARTITION_STRATEGY_LIST:
Assert(spec->listdatums != NIL);

+                        /*
+                         * If the boundspec is of Default partition, it does
+                         * not have list of datums, but has only one node to
+                         * indicate its a default partition.
+                         */
+                        if (isDefaultPartitionBound(
+                                        (Node *) linitial(spec->listdatums)))
+                        {
+                            appendStringInfoString(buf, "DEFAULT");
+                            break;
+                        }
+

How about adding this part before the switch (key->strategy)? That way,
we won't have to come back and add this again when we add range default
partitions.

Gotta go; will provide more comments later.

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#109Beena Emerson
memissemerson@gmail.com
In reply to: Amit Langote (#108)
Re: Adding support for Default partition in partitioning

On Wed, May 31, 2017 at 8:13 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2017/05/31 9:33, Amit Langote wrote:

In get_rule_expr():

case PARTITION_STRATEGY_LIST:
Assert(spec->listdatums != NIL);

+                        /*
+                         * If the boundspec is of Default partition, it does
+                         * not have list of datums, but has only one node to
+                         * indicate its a default partition.
+                         */
+                        if (isDefaultPartitionBound(
+                                        (Node *) linitial(spec->listdatums)))
+                        {
+                            appendStringInfoString(buf, "DEFAULT");
+                            break;
+                        }
+

How about adding this part before the switch (key->strategy)? That way,
we won't have to come back and add this again when we add range default
partitions.

I think it is best that we add a bool is_default to PartitionBoundSpec
and then have a general check for both list and range. Though
listdatums, upperdatums and lowerdatums are set to default for a
DEFAULt partition, it does not seem proper that we check listdatums
for range as well.

--

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

#110Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Beena Emerson (#109)
1 attachment(s)
Re: Adding support for Default partition in partitioning

Hi,

I have addressed Ashutosh's and Amit's comments in the attached patch.

Please let me know if I have missed anything and any further comments.

PFA.

Regards,
Jeevan Ladhe

On Wed, May 31, 2017 at 9:50 AM, Beena Emerson <memissemerson@gmail.com>
wrote:

Show quoted text

On Wed, May 31, 2017 at 8:13 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2017/05/31 9:33, Amit Langote wrote:

In get_rule_expr():

case PARTITION_STRATEGY_LIST:
Assert(spec->listdatums != NIL);

+                        /*
+                         * If the boundspec is of Default partition, it

does

+ * not have list of datums, but has only one

node to

+                         * indicate its a default partition.
+                         */
+                        if (isDefaultPartitionBound(
+                                        (Node *)

linitial(spec->listdatums)))

+                        {
+                            appendStringInfoString(buf, "DEFAULT");
+                            break;
+                        }
+

How about adding this part before the switch (key->strategy)? That way,
we won't have to come back and add this again when we add range default
partitions.

I think it is best that we add a bool is_default to PartitionBoundSpec
and then have a general check for both list and range. Though
listdatums, upperdatums and lowerdatums are set to default for a
DEFAULt partition, it does not seem proper that we check listdatums
for range as well.

--

Beena Emerson

EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

Attachments:

default_partition_v18.patchapplication/octet-stream; name=default_partition_v18.patchDownload
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 0ce94f3..494af27 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1883,6 +1883,15 @@ heap_drop_with_catalog(Oid relid)
 	if (OidIsValid(parentOid))
 	{
 		/*
+		 * Default partition constraints are constructed run-time from the
+		 * constraints of its siblings(basically by negating them), so any
+		 * change in the siblings needs to rebuild the constraints of the
+		 * default partition. So, invalidate the sibling default partition's
+		 * relcache.
+		 */
+		InvalidateDefaultPartitionRelcache(parentOid);
+
+		/*
 		 * Invalidate the parent's relcache so that the partition is no longer
 		 * included in its partition descriptor.
 		 */
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 37fa145..4486989 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -35,6 +35,7 @@
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planmain.h"
+#include "optimizer/prep.h"
 #include "optimizer/var.h"
 #include "rewrite/rewriteManip.h"
 #include "storage/lmgr.h"
@@ -88,9 +89,15 @@ typedef struct PartitionBoundInfoData
 								 * partitioned table) */
 	int			null_index;		/* Index of the null-accepting partition; -1
 								 * if there isn't one */
+	int			default_index;	/* Index of the default partition if any; -1
+								 * if there isn't one */
 } PartitionBoundInfoData;
 
 #define partition_bound_accepts_nulls(bi) ((bi)->null_index != -1)
+#define partition_bound_has_default(bi) ((bi)->default_index != -1)
+
+#define DEFAULT_PARTITION_INDEX(parent) \
+	((parent)->rd_partdesc->boundinfo->default_index)
 
 /*
  * When qsort'ing partition bounds after reading from the catalog, each bound
@@ -121,14 +128,15 @@ static int32 qsort_partition_rbound_cmp(const void *a, const void *b,
 static Oid get_partition_operator(PartitionKey key, int col,
 					   StrategyNumber strategy, bool *need_relabel);
 static Expr *make_partition_op_expr(PartitionKey key, int keynum,
-					   uint16 strategy, Expr *arg1, Expr *arg2);
+					   uint16 strategy, Expr *arg1, Expr *arg2,
+					   bool is_default);
 static void get_range_key_properties(PartitionKey key, int keynum,
 						 PartitionRangeDatum *ldatum,
 						 PartitionRangeDatum *udatum,
 						 ListCell **partexprs_item,
 						 Expr **keyCol,
 						 Const **lower_val, Const **upper_val);
-static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_list(Relation parent, PartitionBoundSpec *spec);
 static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
 static List *generate_partition_qual(Relation rel);
 
@@ -147,6 +155,8 @@ static int32 partition_bound_cmp(PartitionKey key,
 static int partition_bound_bsearch(PartitionKey key,
 						PartitionBoundInfo boundinfo,
 						void *probe, bool probe_is_bound, bool *is_equal);
+static void check_default_allows_bound(Relation parent,
+						   PartitionBoundSpec *new_spec);
 
 /*
  * RelationBuildPartitionDesc
@@ -174,6 +184,7 @@ RelationBuildPartitionDesc(Relation rel)
 	/* List partitioning specific */
 	PartitionListValue **all_values = NULL;
 	int			null_index = -1;
+	int			default_index = -1;
 
 	/* Range partitioning specific */
 	PartitionRangeBound **rbounds = NULL;
@@ -254,6 +265,18 @@ RelationBuildPartitionDesc(Relation rel)
 				if (spec->strategy != PARTITION_STRATEGY_LIST)
 					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 list of non-null
+				 * datums for this partition.
+				 */
+				if (spec->is_default)
+				{
+					default_index = i;
+					i++;
+					continue;
+				}
+
 				foreach(c, spec->listdatums)
 				{
 					Const	   *val = castNode(Const, lfirst(c));
@@ -506,6 +529,22 @@ RelationBuildPartitionDesc(Relation rel)
 					else
 						boundinfo->null_index = -1;
 
+					/* Assign mapped index for the default partition. */
+					if (default_index != -1)
+					{
+						/*
+						 * The default partition accepts any non-specified
+						 * value, hence it should not get a mapped index while
+						 * assigning those for non-null datums.
+						 */
+						Assert(default_index >= 0 &&
+							   mapping[default_index] == -1);
+						mapping[default_index] = next_index++;
+						boundinfo->default_index = mapping[default_index];
+					}
+					else
+						boundinfo->default_index = -1;
+
 					/* All partition must now have a valid mapping */
 					Assert(next_index == nparts);
 					break;
@@ -561,6 +600,9 @@ RelationBuildPartitionDesc(Relation rel)
 						}
 					}
 					boundinfo->indexes[i] = -1;
+
+					/* As of now, we do not support default range partition. */
+					boundinfo->default_index = -1;
 					break;
 				}
 
@@ -610,6 +652,9 @@ partition_bounds_equal(PartitionKey key,
 	if (b1->null_index != b2->null_index)
 		return false;
 
+	if (b1->default_index != b2->default_index)
+		return false;
+
 	for (i = 0; i < b1->ndatums; i++)
 	{
 		int			j;
@@ -672,6 +717,7 @@ check_new_partition_bound(char *relname, Relation parent,
 {
 	PartitionKey key = RelationGetPartitionKey(parent);
 	PartitionDesc partdesc = RelationGetPartitionDesc(parent);
+	PartitionBoundInfo boundinfo = partdesc->boundinfo;
 	ParseState *pstate = make_parsestate(NULL);
 	int			with = -1;
 	bool		overlap = false;
@@ -684,13 +730,28 @@ check_new_partition_bound(char *relname, Relation parent,
 
 				if (partdesc->nparts > 0)
 				{
-					PartitionBoundInfo boundinfo = partdesc->boundinfo;
 					ListCell   *cell;
 
 					Assert(boundinfo &&
 						   boundinfo->strategy == PARTITION_STRATEGY_LIST &&
 						   (boundinfo->ndatums > 0 ||
-							partition_bound_accepts_nulls(boundinfo)));
+							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)
 					{
@@ -832,12 +893,144 @@ check_new_partition_bound(char *relname, Relation parent,
 	if (overlap)
 	{
 		Assert(with >= 0);
+
+		if (spec->is_default)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("a default partition \"%s\" already exists",
+							get_rel_name(partdesc->oids[with])),
+					 parser_errposition(pstate, spec->location)));
+
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 				 errmsg("partition \"%s\" would overlap partition \"%s\"",
 						relname, get_rel_name(partdesc->oids[with])),
 				 parser_errposition(pstate, spec->location)));
 	}
+
+	/*
+	 * If the default partition exists, it's partition constraint will change
+	 * after the addition of this new partition such that it won't allow any
+	 * row that qualifies for this new partition. So, check if the existing
+	 * data in the default partition satisfies this *would be* default
+	 * partition constraint.
+	 */
+	if (partdesc->nparts > 0 && partition_bound_has_default(boundinfo))
+		check_default_allows_bound(parent, spec);
+}
+
+/*
+ * check_default_allows_bound
+ *
+ * Checks if there exists any row in the default partition that passes the
+ * check for constraints of new partition, if any reports an error.
+ */
+static void
+check_default_allows_bound(Relation parent, PartitionBoundSpec *new_spec)
+{
+	Relation	default_rel;
+	int			default_index;
+	List	   *new_part_constraints = NIL;
+	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 = (List *) eval_const_expressions(NULL,
+											  (Node *) new_part_constraints);
+	new_part_constraints =
+		(List *) canonicalize_qual((Expr *) new_part_constraints);
+	new_part_constraints = list_make1(make_ands_explicit(new_part_constraints));
+
+	/* Generate the constraint and default execution states. */
+	default_index = DEFAULT_PARTITION_INDEX(parent);
+	default_rel = heap_open(parent->rd_partdesc->oids[default_index],
+							AccessExclusiveLock);
+
+	if (default_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		all_parts = find_all_inheritors(RelationGetRelid(default_rel),
+										AccessExclusiveLock, NULL);
+	else
+		all_parts = list_make1_oid(RelationGetRelid(default_rel));
+
+	foreach(lc, all_parts)
+	{
+		Oid			part_relid = lfirst_oid(lc);
+		Relation	part_rel;
+		Expr	   *constr;
+		Expr	   *partition_constraint;
+		EState	   *estate;
+		HeapTuple	tuple;
+		ExprState  *partqualstate = NULL;
+		Snapshot	snapshot;
+		TupleDesc	tupdesc;
+		ExprContext *econtext;
+		HeapScanDesc scan;
+		MemoryContext oldCxt;
+		TupleTableSlot *tupslot;
+
+		/* Lock already taken above. */
+		if (part_relid != RelationGetRelid(default_rel))
+			part_rel = heap_open(part_relid, NoLock);
+		else
+			part_rel = default_rel;
+
+		/*
+		 * Skip if it's a partitioned table. Only RELKIND_RELATION relations
+		 * (ie, leaf partitions) need to be scanned.
+		 */
+		if (part_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+			part_rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+		{
+			heap_close(part_rel, NoLock);
+			continue;
+		}
+
+		tupdesc = CreateTupleDescCopy(RelationGetDescr(part_rel));
+		constr = linitial(new_part_constraints);
+		partition_constraint = (Expr *) map_partition_varattnos((List *) constr,
+														1, part_rel, parent);
+		estate = CreateExecutorState();
+
+		/* Build expression execution states for partition check quals */
+		partqualstate = ExecPrepareExpr(partition_constraint, estate);
+
+		econtext = GetPerTupleExprContext(estate);
+		snapshot = RegisterSnapshot(GetLatestSnapshot());
+		scan = heap_beginscan(part_rel, snapshot, 0, NULL);
+		tupslot = MakeSingleTupleTableSlot(tupdesc);
+
+		/*
+		 * Switch to per-tuple memory context and reset it for each tuple
+		 * produced, so we don't leak memory.
+		 */
+		oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		{
+			ExecStoreTuple(tuple, tupslot, InvalidBuffer, false);
+			econtext->ecxt_scantuple = tupslot;
+
+			if (partqualstate && ExecCheck(partqualstate, econtext))
+				ereport(ERROR,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("default partition contains row(s) that would overlap with partition being created")));
+
+			ResetExprContext(econtext);
+			CHECK_FOR_INTERRUPTS();
+		}
+
+		CacheInvalidateRelcache(part_rel);
+		MemoryContextSwitchTo(oldCxt);
+		heap_endscan(scan);
+		UnregisterSnapshot(snapshot);
+		ExecDropSingleTupleTableSlot(tupslot);
+		FreeExecutorState(estate);
+		heap_close(part_rel, NoLock);
+	}
 }
 
 /*
@@ -903,7 +1096,7 @@ get_qual_from_partbound(Relation rel, Relation parent,
 	{
 		case PARTITION_STRATEGY_LIST:
 			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
-			my_qual = get_qual_for_list(key, spec);
+			my_qual = get_qual_for_list(parent, spec);
 			break;
 
 		case PARTITION_STRATEGY_RANGE:
@@ -1233,7 +1426,7 @@ get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
  */
 static Expr *
 make_partition_op_expr(PartitionKey key, int keynum,
-					   uint16 strategy, Expr *arg1, Expr *arg2)
+					uint16 strategy, Expr *arg1, Expr *arg2, bool is_default)
 {
 	Oid			operoid;
 	bool		need_relabel = false;
@@ -1263,11 +1456,17 @@ make_partition_op_expr(PartitionKey key, int keynum,
 			{
 				ScalarArrayOpExpr *saopexpr;
 
+				if (is_default &&
+					((operoid = get_negator(operoid)) == InvalidOid))
+					ereport(ERROR, (errcode(ERRCODE_RESTRICT_VIOLATION),
+									errmsg("DEFAULT partition cannot be used without negator of operator  %s",
+										   get_opname(operoid))));
+
 				/* Build leftop = ANY (rightop) */
 				saopexpr = makeNode(ScalarArrayOpExpr);
 				saopexpr->opno = operoid;
 				saopexpr->opfuncid = get_opcode(operoid);
-				saopexpr->useOr = true;
+				saopexpr->useOr = !is_default;
 				saopexpr->inputcollid = key->partcollation[keynum];
 				saopexpr->args = list_make2(arg1, arg2);
 				saopexpr->location = -1;
@@ -1300,8 +1499,9 @@ make_partition_op_expr(PartitionKey key, int keynum,
  * constraint, given the partition key and bound structures.
  */
 static List *
-get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
+get_qual_for_list(Relation parent, PartitionBoundSpec *spec)
 {
+	PartitionKey key = RelationGetPartitionKey(parent);
 	List	   *result;
 	Expr	   *keyCol;
 	ArrayExpr  *arr;
@@ -1322,15 +1522,60 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 	else
 		keyCol = (Expr *) copyObject(linitial(key->partexprs));
 
-	/* Create list of Consts for the allowed values, excluding any nulls */
-	foreach(cell, spec->listdatums)
+	/*
+	 * In case of the default partition for list, the partition constraint
+	 * is basically any value that is not equal to any of the values in
+	 * boundinfo->datums array. So, construct a list of constants from
+	 * boundinfo->datums to pass to function make_partition_op_expr via
+	 * ArrayExpr, which would return a negated expression for the default
+	 * partition.
+	 */
+	if (spec->is_default)
 	{
-		Const	   *val = castNode(Const, lfirst(cell));
+		int         i;
+		int         ndatums = 0;
+		MemoryContext oldcxt;
+		PartitionDesc pdesc = RelationGetPartitionDesc(parent);
+		PartitionBoundInfo boundinfo = pdesc->boundinfo;
 
-		if (val->constisnull)
+		ndatums = (pdesc->nparts > 0) ? boundinfo->ndatums : 0;
+		oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+
+		for (i = 0; i < ndatums; i++)
+		{
+			Const      *val;
+
+			/* Construct const from datum */
+			val = makeConst(key->parttypid[0],
+							key->parttypmod[0],
+							key->parttypcoll[0],
+							key->parttyplen[0],
+							*boundinfo->datums[i],
+							false,      /* isnull */
+							true /* byval */ );
+
+			arrelems = lappend(arrelems, val);
+		}
+		if (boundinfo && partition_bound_accepts_nulls(boundinfo))
 			list_has_null = true;
-		else
-			arrelems = lappend(arrelems, copyObject(val));
+
+		MemoryContextSwitchTo(oldcxt);
+	}
+	else
+	{
+		/*
+		 * Create list of Consts for the allowed values, excluding any
+		 * nulls.
+		 */
+		foreach(cell, spec->listdatums)
+		{
+			Const	   *val = castNode(Const, lfirst(cell));
+
+			if (val->constisnull)
+				list_has_null = true;
+			else
+				arrelems = lappend(arrelems, copyObject(val));
+		}
 	}
 
 	/* Construct an ArrayExpr for the non-null partition values */
@@ -1346,9 +1591,10 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 
 	/* Generate the main expression, i.e., keyCol = ANY (arr) */
 	opexpr = make_partition_op_expr(key, 0, BTEqualStrategyNumber,
-									keyCol, (Expr *) arr);
+									keyCol, (Expr *) arr, spec->is_default);
 
-	if (!list_has_null)
+	if ((!list_has_null && !spec->is_default) ||
+		(list_has_null && spec->is_default))
 	{
 		/*
 		 * Gin up a "col IS NOT NULL" test that will be AND'd with the main
@@ -1591,7 +1837,7 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 		oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
 		test_expr = make_partition_op_expr(key, i, BTEqualStrategyNumber,
 										   (Expr *) lower_val,
-										   (Expr *) upper_val);
+										   (Expr *) upper_val, false);
 		fix_opfuncids((Node *) test_expr);
 		test_exprstate = ExecInitExpr(test_expr, NULL);
 		test_result = ExecEvalExprSwitchContext(test_exprstate,
@@ -1615,7 +1861,8 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 		/* Equal, so generate keyCol = lower_val expression */
 		result = lappend(result,
 						 make_partition_op_expr(key, i, BTEqualStrategyNumber,
-												keyCol, (Expr *) lower_val));
+												keyCol, (Expr *) lower_val,
+												false));
 
 		i++;
 	}
@@ -1676,7 +1923,8 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 											make_partition_op_expr(key, j,
 																   strategy,
 																   keyCol,
-														(Expr *) lower_val));
+														(Expr *) lower_val,
+																   false));
 			}
 
 			if (need_next_upper_arm && upper_val)
@@ -1698,7 +1946,8 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 											make_partition_op_expr(key, j,
 																   strategy,
 																   keyCol,
-														(Expr *) upper_val));
+														(Expr *) upper_val,
+																   false));
 			}
 
 			/*
@@ -1988,8 +2237,9 @@ get_partition_for_tuple(PartitionDispatch *pd,
 		}
 
 		/*
-		 * A null partition key is only acceptable if null-accepting list
-		 * partition exists.
+		 * Handle NULL partition key here if there's a null-accepting list
+		 * partition. Else it will be routed to the default partition if one
+		 * exists.
 		 */
 		cur_index = -1;
 		if (isnull[0] && partition_bound_accepts_nulls(partdesc->boundinfo))
@@ -2027,16 +2277,28 @@ get_partition_for_tuple(PartitionDispatch *pd,
 		}
 
 		/*
-		 * cur_index < 0 means we failed to find a partition of this parent.
-		 * cur_index >= 0 means we either found the leaf partition, or the
-		 * next parent to find a partition of.
+		 * cur_index < 0 means we could not find a non-default partition of this
+		 * parent. cur_index >= 0 means we either found the leaf partition, or
+		 * the next parent to find a partition of.
 		 */
 		if (cur_index < 0)
 		{
-			result = -1;
-			*failed_at = parent;
-			*failed_slot = slot;
-			break;
+			if (partition_bound_has_default(partdesc->boundinfo))
+			{
+				result = parent->indexes[partdesc->boundinfo->default_index];
+
+				if (result >= 0)
+					break;
+				else
+					parent = pd[-result];
+			}
+			else
+			{
+				result = -1;
+				*failed_at = parent;
+				*failed_slot = slot;
+				break;
+			}
 		}
 		else if (parent->indexes[cur_index] >= 0)
 		{
@@ -2312,3 +2574,21 @@ partition_bound_bsearch(PartitionKey key, PartitionBoundInfo boundinfo,
 
 	return lo;
 }
+
+/*
+ * InvalidateDefaultPartitionRelcache
+ *
+ * Given a parent oid, this function checks if there exists a default partition
+ * and invalidates it's relcache if it exists.
+ */
+void
+InvalidateDefaultPartitionRelcache(Oid parentOid)
+{
+	Relation parent = heap_open(parentOid, AccessShareLock);
+	Oid default_relid = parent->rd_partdesc->oids[DEFAULT_PARTITION_INDEX(parent)];
+
+	if (partition_bound_has_default(parent->rd_partdesc->boundinfo))
+		CacheInvalidateRelcacheByRelid(default_relid);
+
+	heap_close(parent, AccessShareLock);
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 7959120..5f3edfe 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13829,6 +13829,15 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 	heap_close(classRel, RowExclusiveLock);
 
 	/*
+	 * Default partition constraints are constructed run-time from the
+	 * constraints of its siblings(basically by negating them), so any
+	 * change in the siblings needs to rebuild the constraints of the
+	 * default partition. So, invalidate the sibling default partition's
+	 * relcache.
+	 */
+	InvalidateDefaultPartitionRelcache(RelationGetRelid(rel));
+
+	/*
 	 * Invalidate the parent's relcache so that the partition is no longer
 	 * included in its partition descriptor.
 	 */
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4a899f1..dae592a 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -92,11 +92,6 @@ static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid,
 						  Bitmapset *modifiedCols,
 						  AclMode requiredPerms);
 static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
-static char *ExecBuildSlotValueDescription(Oid reloid,
-							  TupleTableSlot *slot,
-							  TupleDesc tupdesc,
-							  Bitmapset *modifiedCols,
-							  int maxfieldlen);
 static char *ExecBuildSlotPartitionKeyDescription(Relation rel,
 									 Datum *values,
 									 bool *isnull,
@@ -2174,7 +2169,7 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
  * column involved, that subset will be returned with a key identifying which
  * columns they are.
  */
-static char *
+char *
 ExecBuildSlotValueDescription(Oid reloid,
 							  TupleTableSlot *slot,
 							  TupleDesc tupdesc,
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 36bf1dc..03d2df9 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4444,6 +4444,7 @@ _copyPartitionBoundSpec(const PartitionBoundSpec *from)
 	PartitionBoundSpec *newnode = makeNode(PartitionBoundSpec);
 
 	COPY_SCALAR_FIELD(strategy);
+	COPY_SCALAR_FIELD(is_default);
 	COPY_NODE_FIELD(listdatums);
 	COPY_NODE_FIELD(lowerdatums);
 	COPY_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 5bcf031..01fd43d 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2838,6 +2838,7 @@ static bool
 _equalPartitionBoundSpec(const PartitionBoundSpec *a, const PartitionBoundSpec *b)
 {
 	COMPARE_SCALAR_FIELD(strategy);
+	COMPARE_SCALAR_FIELD(is_default);
 	COMPARE_NODE_FIELD(listdatums);
 	COMPARE_NODE_FIELD(lowerdatums);
 	COMPARE_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index c348bdc..ae66d3d 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3543,6 +3543,7 @@ _outPartitionBoundSpec(StringInfo str, const PartitionBoundSpec *node)
 	WRITE_NODE_TYPE("PARTITIONBOUNDSPEC");
 
 	WRITE_CHAR_FIELD(strategy);
+	WRITE_BOOL_FIELD(is_default);
 	WRITE_NODE_FIELD(listdatums);
 	WRITE_NODE_FIELD(lowerdatums);
 	WRITE_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 81ddfc3..290a0bb 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2373,6 +2373,7 @@ _readPartitionBoundSpec(void)
 	READ_LOCALS(PartitionBoundSpec);
 
 	READ_CHAR_FIELD(strategy);
+	READ_BOOL_FIELD(is_default);
 	READ_NODE_FIELD(listdatums);
 	READ_NODE_FIELD(lowerdatums);
 	READ_NODE_FIELD(upperdatums);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7e03624..ac99859 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2657,6 +2657,7 @@ ForValues:
 					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
 
 					n->strategy = PARTITION_STRATEGY_LIST;
+					n->is_default = false;
 					n->listdatums = $5;
 					n->location = @3;
 
@@ -2669,12 +2670,29 @@ ForValues:
 					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
 
 					n->strategy = PARTITION_STRATEGY_RANGE;
+					n->is_default = false;
 					n->lowerdatums = $5;
 					n->upperdatums = $9;
 					n->location = @3;
 
 					$$ = n;
 				}
+
+			/*
+			 * A default partition, that can be partition of either LIST or
+			 * RANGE partitioned table.
+			 * Currently this is supported only for LIST partition.
+			 */
+			| DEFAULT
+				{
+					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+					n->is_default = true;
+					n->location = @1;
+
+					$$ = n;
+				}
+
 		;
 
 partbound_datum:
@@ -2716,6 +2734,7 @@ PartitionRangeDatum:
 
 					$$ = (Node *) n;
 				}
+
 		;
 
 /*****************************************************************************
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 9134fb9..e861195 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -3283,6 +3284,27 @@ transformPartitionBound(ParseState *pstate, Relation parent,
 	/* Avoid scribbling on input */
 	result_spec = copyObject(spec);
 
+	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
+		 * partition bound spec.
+		 */
+		result_spec->strategy = strategy;
+
+		/*
+		 * The default partition bound does not have any datums to be
+		 * transformed, return the new bound.
+		 */
+		return result_spec;
+	}
+
 	if (strategy == PARTITION_STRATEGY_LIST)
 	{
 		ListCell   *cell;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 824d757..7b6bae7 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8644,10 +8644,18 @@ get_rule_expr(Node *node, deparse_context *context,
 		case T_PartitionBoundSpec:
 			{
 				PartitionBoundSpec *spec = (PartitionBoundSpec *) node;
+				char		strategy = spec->strategy;
 				ListCell   *cell;
 				char	   *sep;
 
-				switch (spec->strategy)
+				if (spec->is_default && (strategy == PARTITION_STRATEGY_LIST ||
+										 strategy == PARTITION_STRATEGY_RANGE))
+				{
+					appendStringInfoString(buf, "DEFAULT");
+					break;
+				}
+
+				switch (strategy)
 				{
 					case PARTITION_STRATEGY_LIST:
 						Assert(spec->listdatums != NIL);
@@ -8713,7 +8721,7 @@ get_rule_expr(Node *node, deparse_context *context,
 
 					default:
 						elog(ERROR, "unrecognized partition strategy: %d",
-							 (int) spec->strategy);
+							 (int) strategy);
 						break;
 				}
 			}
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 2abd087..b01ba07 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2044,7 +2044,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("ATTACH", "PARTITION", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 	else if (TailMatches2("FOR", "VALUES"))
 		COMPLETE_WITH_LIST2("FROM (", "IN (");
 
@@ -2483,7 +2483,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("PARTITION", "OF", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 
 /* CREATE TABLESPACE */
 	else if (Matches3("CREATE", "TABLESPACE", MatchAny))
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 0a1e468..4d7dd34 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -98,4 +98,5 @@ extern int get_partition_for_tuple(PartitionDispatch *pd,
 						EState *estate,
 						PartitionDispatchData **failed_at,
 						TupleTableSlot **failed_slot);
+extern void InvalidateDefaultPartitionRelcache(Oid parentOid);
 #endif   /* PARTITION_H */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 8cc5f3a..8bd574c 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -537,5 +537,10 @@ extern void CheckCmdReplicaIdentity(Relation rel, CmdType cmd);
 
 extern void CheckSubscriptionRelkind(char relkind, const char *nspname,
 						 const char *relname);
+char *ExecBuildSlotValueDescription(Oid reloid,
+							  TupleTableSlot *slot,
+							  TupleDesc tupdesc,
+							  Bitmapset *modifiedCols,
+							  int maxfieldlen);
 
 #endif   /* EXECUTOR_H  */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 8720e71..d2f3ec7 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -798,6 +798,7 @@ typedef struct PartitionBoundSpec
 	NodeTag		type;
 
 	char		strategy;		/* see PARTITION_STRATEGY codes above */
+	bool		is_default;		/* is it a default partition bound */
 
 	/* Partitioning info for LIST strategy: */
 	List	   *listdatums;		/* List of Consts (or A_Consts in raw tree) */
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 8aadbb8..75f3617 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3273,6 +3273,11 @@ SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::reg
 CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
 ERROR:  partition "fail_part" would overlap partition "part_1"
+-- attaching default partition overlaps if the default partition already exists
+CREATE TABLE def_part PARTITION OF list_parted DEFAULT;
+CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT;
+ERROR:  a default partition "def_part" already exists
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
 	a int,
@@ -3286,6 +3291,15 @@ ERROR:  partition constraint is violated by some row
 -- should be ok after deleting the bad row
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+-- check partition cannot be attached if default has some row for its values
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
+INSERT INTO list_parted2_def VALUES (11, 'z');
+CREATE TABLE part_3 (LIKE list_parted2);
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+ERROR:  default partition contains row(s) that would overlap with partition being created
+-- should be ok after deleting the bad row
+DELETE FROM list_parted2_def WHERE a = 11;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
 CREATE TABLE part_3_4 (
@@ -3341,6 +3355,18 @@ DELETE FROM part_5_a WHERE a NOT IN (3);
 ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT NULL;
 ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
 INFO:  partition constraint for table "part_5" is implied by existing constraints
+-- check that leaf partitions of default partition are scanned when
+-- attaching a partitioned table.
+ALTER TABLE part_5 DROP CONSTRAINT check_a;
+CREATE TABLE part5_def PARTITION OF part_5 DEFAULT PARTITION BY LIST(a);
+CREATE TABLE part5_def_p1 PARTITION OF part5_def FOR VALUES IN (5);
+INSERT INTO part5_def_p1 VALUES (5, 'y');
+CREATE TABLE part5_p1 (LIKE part_5);
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+ERROR:  default partition contains row(s) that would overlap with partition being created
+-- should be ok after deleting the bad row
+DELETE FROM part5_def_p1 WHERE b = 'y';
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
 -- check that the table being attached is not already a partition
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 ERROR:  "part_2" is already a partition
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 5136506..3bd663e 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -449,6 +449,7 @@ CREATE TABLE list_parted (
 CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
 CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
 CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
 ERROR:  syntax error at or near "int"
 LINE 1: ... fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
@@ -457,6 +458,8 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
 ERROR:  syntax error at or near "::"
 LINE 1: ...fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
                                                                 ^
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
+ERROR:  a default partition "part_default" already exists
 -- syntax does not allow empty list of values for list partitions
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
 ERROR:  syntax error at or near ")"
@@ -514,6 +517,9 @@ 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);
@@ -565,10 +571,15 @@ CREATE TABLE list_parted2 (
 ) PARTITION BY LIST (a);
 CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
 CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
 ERROR:  partition "fail_part" would overlap partition "part_null_z"
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
 ERROR:  partition "fail_part" would overlap partition "part_ab"
+-- check default partition overlap
+INSERT INTO list_parted2 VALUES('X');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y');
+ERROR:  default partition contains row(s) that would overlap with partition being created
 CREATE TABLE range_parted2 (
 	a int
 ) PARTITION BY RANGE (a);
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 8b0752a..f9eef3f 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -202,6 +202,7 @@ create table list_parted (
 create table part_aa_bb partition of list_parted FOR VALUES IN ('aa', 'bb');
 create table part_cc_dd partition of list_parted FOR VALUES IN ('cc', 'dd');
 create table part_null partition of list_parted FOR VALUES IN (null);
+create table part_default partition of list_parted default;
 -- fail
 insert into part_aa_bb values ('cc', 1);
 ERROR:  new row for relation "part_aa_bb" violates partition constraint
@@ -212,24 +213,45 @@ DETAIL:  Failing row contains (AAa, 1).
 insert into part_aa_bb values (null);
 ERROR:  new row for relation "part_aa_bb" violates partition constraint
 DETAIL:  Failing row contains (null, null).
+insert into part_default values ('aa', 2);
+ERROR:  new row for relation "part_default" violates partition constraint
+DETAIL:  Failing row contains (aa, 2).
+insert into part_default values (null, 2);
+ERROR:  new row for relation "part_default" violates partition constraint
+DETAIL:  Failing row contains (null, 2).
 -- ok
 insert into part_cc_dd values ('cC', 1);
 insert into part_null values (null, 0);
+insert into part_default values ('Zz', 2);
 -- check in case of multi-level partitioned table
 create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b);
 create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
 create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
+-- check in case of multi-level default partitioned table
+drop table part_default;
+create table part_default partition of list_parted default partition by range(b);
+create table part_default_p1 partition of part_default for values from (20) to (30);
+create table part_default_p2 partition of part_default for values from (30) to (40);
 -- fail
 insert into part_ee_ff1 values ('EE', 11);
 ERROR:  new row for relation "part_ee_ff1" violates partition constraint
 DETAIL:  Failing row contains (EE, 11).
+insert into part_default_p2 values ('gg', 43);
+ERROR:  new row for relation "part_default_p2" violates partition constraint
+DETAIL:  Failing row contains (gg, 43).
 -- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
 insert into part_ee_ff1 values ('cc', 1);
 ERROR:  new row for relation "part_ee_ff1" violates partition constraint
 DETAIL:  Failing row contains (cc, 1).
+insert into part_default values ('gg', 43);
+ERROR:  no partition of relation "part_default" found for row
+DETAIL:  Partition key of the failing row contains (b) = (43).
 -- ok
 insert into part_ee_ff1 values ('ff', 1);
 insert into part_ee_ff2 values ('ff', 11);
+insert into part_default_p1 values ('cd', 25);
+insert into part_default_p2 values ('de', 35);
+insert into list_parted values ('ab', 21);
 -- Check tuple routing for partitioned tables
 -- fail
 insert into range_parted values ('a', 0);
@@ -274,17 +296,20 @@ DETAIL:  Partition key of the failing row contains (b) = (0).
 insert into list_parted values ('EE', 1);
 insert into part_ee_ff values ('EE', 10);
 select tableoid::regclass, * from list_parted;
-  tableoid   | a  | b  
--------------+----+----
- part_aa_bb  | aA |   
- part_cc_dd  | cC |  1
- part_null   |    |  0
- part_null   |    |  1
- part_ee_ff1 | ff |  1
- part_ee_ff1 | EE |  1
- part_ee_ff2 | ff | 11
- part_ee_ff2 | EE | 10
-(8 rows)
+    tableoid     | a  | b  
+-----------------+----+----
+ part_aa_bb      | aA |   
+ part_cc_dd      | cC |  1
+ part_null       |    |  0
+ part_null       |    |  1
+ part_ee_ff1     | ff |  1
+ part_ee_ff1     | EE |  1
+ part_ee_ff2     | ff | 11
+ part_ee_ff2     | EE | 10
+ part_default_p1 | cd | 25
+ part_default_p1 | ab | 21
+ part_default_p2 | de | 35
+(11 rows)
 
 -- some more tests to exercise tuple-routing with multi-level partitioning
 create table part_gg partition of list_parted for values in ('gg') partition by range (b);
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index 9366f04..9912ef2 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -218,5 +218,20 @@ 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;
+create table list_parted (
+	a text,
+	b int
+) partition by list (a);
+create table list_part1  partition of list_parted for values in ('a', 'b');
+create table list_default partition of list_parted default;
+insert into list_part1 values ('a', 1);
+insert into list_default values ('d', 10);
+-- fail
+update list_default set a = 'a' where a = 'd';
+ERROR:  new row for relation "list_default" violates partition constraint
+DETAIL:  Failing row contains (a, 10).
+-- ok
+update list_default set a = 'x' where a = 'd';
 -- cleanup
 drop table range_parted;
+drop table list_parted;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index c41b487..36c56aa 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2095,6 +2095,10 @@ SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::reg
 -- check that the new partition won't overlap with an existing partition
 CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+-- attaching default partition overlaps if the default partition already exists
+CREATE TABLE def_part PARTITION OF list_parted DEFAULT;
+CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT;
 
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
@@ -2111,6 +2115,15 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 
+-- check partition cannot be attached if default has some row for its values
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
+INSERT INTO list_parted2_def VALUES (11, 'z');
+CREATE TABLE part_3 (LIKE list_parted2);
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+-- should be ok after deleting the bad row
+DELETE FROM list_parted2_def WHERE a = 11;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
 CREATE TABLE part_3_4 (
@@ -2172,6 +2185,17 @@ DELETE FROM part_5_a WHERE a NOT IN (3);
 ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT NULL;
 ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
 
+-- check that leaf partitions of default partition are scanned when
+-- attaching a partitioned table.
+ALTER TABLE part_5 DROP CONSTRAINT check_a;
+CREATE TABLE part5_def PARTITION OF part_5 DEFAULT PARTITION BY LIST(a);
+CREATE TABLE part5_def_p1 PARTITION OF part5_def FOR VALUES IN (5);
+INSERT INTO part5_def_p1 VALUES (5, 'y');
+CREATE TABLE part5_p1 (LIKE part_5);
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+-- should be ok after deleting the bad row
+DELETE FROM part5_def_p1 WHERE b = 'y';
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
 
 -- check that the table being attached is not already a partition
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index cb7aa5b..f44c0e0 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -439,8 +439,10 @@ CREATE TABLE list_parted (
 CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
 CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
 CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
 
 -- syntax does not allow empty list of values for list partitions
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
@@ -484,6 +486,8 @@ 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);
@@ -529,9 +533,13 @@ CREATE TABLE list_parted2 (
 ) PARTITION BY LIST (a);
 CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
 CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+-- check default partition overlap
+INSERT INTO list_parted2 VALUES('X');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y');
 
 CREATE TABLE range_parted2 (
 	a int
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index db8967b..c120713 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -118,27 +118,41 @@ create table list_parted (
 create table part_aa_bb partition of list_parted FOR VALUES IN ('aa', 'bb');
 create table part_cc_dd partition of list_parted FOR VALUES IN ('cc', 'dd');
 create table part_null partition of list_parted FOR VALUES IN (null);
+create table part_default partition of list_parted default;
 
 -- fail
 insert into part_aa_bb values ('cc', 1);
 insert into part_aa_bb values ('AAa', 1);
 insert into part_aa_bb values (null);
+insert into part_default values ('aa', 2);
+insert into part_default values (null, 2);
 -- ok
 insert into part_cc_dd values ('cC', 1);
 insert into part_null values (null, 0);
+insert into part_default values ('Zz', 2);
 
 -- check in case of multi-level partitioned table
 create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b);
 create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
 create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
+-- check in case of multi-level default partitioned table
+drop table part_default;
+create table part_default partition of list_parted default partition by range(b);
+create table part_default_p1 partition of part_default for values from (20) to (30);
+create table part_default_p2 partition of part_default for values from (30) to (40);
 
 -- fail
 insert into part_ee_ff1 values ('EE', 11);
+insert into part_default_p2 values ('gg', 43);
 -- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
 insert into part_ee_ff1 values ('cc', 1);
+insert into part_default values ('gg', 43);
 -- ok
 insert into part_ee_ff1 values ('ff', 1);
 insert into part_ee_ff2 values ('ff', 11);
+insert into part_default_p1 values ('cd', 25);
+insert into part_default_p2 values ('de', 35);
+insert into list_parted values ('ab', 21);
 
 -- Check tuple routing for partitioned tables
 
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 6637119..44fb0dc 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -125,5 +125,20 @@ update range_parted set b = b - 1 where b = 10;
 -- ok
 update range_parted set b = b + 1 where b = 10;
 
+create table list_parted (
+	a text,
+	b int
+) partition by list (a);
+create table list_part1  partition of list_parted for values in ('a', 'b');
+create table list_default partition of list_parted default;
+insert into list_part1 values ('a', 1);
+insert into list_default values ('d', 10);
+
+-- fail
+update list_default set a = 'a' where a = 'd';
+-- ok
+update list_default set a = 'x' where a = 'd';
+
 -- cleanup
 drop table range_parted;
+drop table list_parted;
#111Robert Haas
robertmhaas@gmail.com
In reply to: Jeevan Ladhe (#110)
Re: Adding support for Default partition in partitioning

On Thu, Jun 1, 2017 at 3:35 PM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

Please let me know if I have missed anything and any further comments.

+ errmsg("a default partition \"%s\" already exists",

I suggest: partition \"%s\" conflicts with existing default partition \"%s\"

The point is that's more similar to the message you get when overlap
&& !spec->is_default.

+ * If the default partition exists, it's partition constraint will change

it's -> its

+ errmsg("default partition contains row(s)
that would overlap with partition being created")));

It doesn't really sound right to talk about rows overlapping with a
partition. Partitions can overlap with each other, but not rows.
Also, it's not really project style to use ambiguously plural forms
like "row(s)" in error messages. Maybe something like:

new partition constraint for default partition \"%s\" would be
violated by some row

+/*
+ * InvalidateDefaultPartitionRelcache
+ *
+ * Given a parent oid, this function checks if there exists a default partition
+ * and invalidates it's relcache if it exists.
+ */
+void
+InvalidateDefaultPartitionRelcache(Oid parentOid)
+{
+    Relation parent = heap_open(parentOid, AccessShareLock);
+    Oid default_relid =
parent->rd_partdesc->oids[DEFAULT_PARTITION_INDEX(parent)];
+
+    if (partition_bound_has_default(parent->rd_partdesc->boundinfo))
+        CacheInvalidateRelcacheByRelid(default_relid);
+
+    heap_close(parent, AccessShareLock);
+}

It does not seem like a good idea to put the heap_open() call inside
this function. One of the two callers already *has* the Relation, and
we definitely want to avoid pulling the Oid out of the Relation only
to reopen it to get the Relation back. And I think
heap_drop_with_catalog could open the parent relation instead of
calling LockRelationOid().

If DETACH PARTITION and DROP PARTITION require this, why not ATTACH
PARTITION and CREATE TABLE .. PARTITION OF?

The indentation of the changes in gram.y doesn't appear to match the
nearby code. I'd remove this comment:

+ * Currently this is supported only for LIST partition.

Since nothing here is dependent on this working only for LIST
partitions, and since this will probably change, I think it would be
more future-proof to leave this out, lest somebody forget to update it
later.

-                switch (spec->strategy)
+                if (spec->is_default && (strategy == PARTITION_STRATEGY_LIST ||
+                                         strategy == PARTITION_STRATEGY_RANGE))

Checking strategy here appears pointless.

This is not a full review, but I'm out of time for today.

--
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

#112Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Jeevan Ladhe (#110)
Re: Adding support for Default partition in partitioning

Here's some detailed review of the code.

@@ -1883,6 +1883,15 @@ heap_drop_with_catalog(Oid relid)
     if (OidIsValid(parentOid))
     {
         /*
+         * Default partition constraints are constructed run-time from the
+         * constraints of its siblings(basically by negating them), so any
+         * change in the siblings needs to rebuild the constraints of the
+         * default partition. So, invalidate the sibling default partition's
+         * relcache.
+         */
+        InvalidateDefaultPartitionRelcache(parentOid);
+
Do we need a lock on the default partition for doing this? A query might be
scanning the default partition directly and we will invalidate the relcache
underneath it. What if two partitions are being dropped simultaneously and
change default constraints simultaneously. Probably the lock on the parent
helps there, but need to check it. What if the default partition cache is
invalidated because partition gets added/dropped to the default partition
itself. If we need a lock on the default partition, we will need to
check the order in which we should be obtaining the locks so as to avoid
deadlocks. This also means that we have to test PREPARED statements involving
default partition. Any addition/deletion/attach/detach of other partition
should invalidate those cached statements.
+                        if (partition_bound_has_default(boundinfo))
+                        {
+                            overlap = true;
+                            with = boundinfo->default_index;
+                        }
You could possibly rewrite this as
overlap = partition_bound_has_default(boundinfo);
with = boundinfo->default_index;
that would save one indentation and a conditional jump.
+    if (partdesc->nparts > 0 && partition_bound_has_default(boundinfo))
+        check_default_allows_bound(parent, spec);
If the table has a default partition, nparts > 0, nparts > 0 check looks
redundant. The comments above should also explain that this check doesn't
trigger when a default partition is added since we don't expect an existing
default partition in such a case.
+ * Checks if there exists any row in the default partition that passes the
+ * check for constraints of new partition, if any reports an error.
grammar two conflicting ifs in the same statement. You may want to rephrase
this as "This function checks if there exists a row in the default
partition that fits in the new
partition and throws an error if it finds one."
+    if (new_spec->strategy != PARTITION_STRATEGY_LIST)
+        return;
This should probably be an Assert. When default range partition is supported
this function would silently return, meaning there is no row in the default
partition which fits the new partition. We don't want that behavior.

The code in check_default_allows_bound() to check whether the default partition
has any rows that would fit new partition looks quite similar to the code in
ATExecAttachPartition() checking whether all rows in the table being attached
as a partition fit the partition bounds. One thing that
check_default_allows_bound() misses is, if there's already a constraint on the
default partition refutes the partition constraint on the new partition, we can
skip the scan of the default partition since it can not have rows that would
fit the new partition. ATExecAttachPartition() has code to deal with a similar
case i.e. the table being attached has a constraint which implies the partition
constraint. There may be more cases which check_default_allows_bound() does not
handle but ATExecAttachPartition() handles. So, I am wondering whether it's
better to somehow take out the common code into a function and use it. We will
have to deal with a difference through. The first one would throw an error when
finding a row that satisfies partition constraints whereas the second one would
throw an error when it doesn't find such a row. But this difference can be
handled through a flag or by negating the constraint. This would also take care
of Amit Langote's complaint about foreign partitions. There's also another
difference that the ATExecAttachPartition() queues the table for scan and the
actual scan takes place in ATRewriteTable(), but there is not such queue while
creating a table as a partition. But we should check if we can reuse the code to
scan the heap for checking a constraint.

In case of ATTACH PARTITION, probably we should schedule scan of default
partition in the alter table's work queue like what ATExecAttachPartition() is
doing for the table being attached. That would fit in the way alter table
works.

 make_partition_op_expr(PartitionKey key, int keynum,
-                       uint16 strategy, Expr *arg1, Expr *arg2)
+                    uint16 strategy, Expr *arg1, Expr *arg2, bool is_default)
Indentation
+                if (is_default &&
+                    ((operoid = get_negator(operoid)) == InvalidOid))
+                    ereport(ERROR, (errcode(ERRCODE_RESTRICT_VIOLATION),
+                                    errmsg("DEFAULT partition cannot
be used without negator of operator  %s",
+                                           get_opname(operoid))));
+
If the existence of default partition depends upon the negator, shouldn't there
be a dependency between the default partition and the negator. At the time of
creating the default partition, we will try to constuct the partition
constraint for the default partition and if the negator doesn't exist that
time, it will throw an error. But in an unlikely event when the user drops the
negator, the partitioned table will not be usable at all, as every time it will
try to create the relcache, it will try to create default partition constraint
and will throw error because of missing negator. That's not a very good
scenario. Have you tried this case? Apart from that, while restoring a dump, if
the default partition gets restored before the negator is created, restore will
fail with this error.
     /* Generate the main expression, i.e., keyCol = ANY (arr) */
     opexpr = make_partition_op_expr(key, 0, BTEqualStrategyNumber,
-                                    keyCol, (Expr *) arr);
+                                    keyCol, (Expr *) arr, spec->is_default);
                 /* Build leftop = ANY (rightop) */
                 saopexpr = makeNode(ScalarArrayOpExpr);
The comments in both the places need correction, as for default partition the
expression will be keyCol <> ALL(arr).
+    /*
+     * In case of the default partition for list, the partition constraint
+     * is basically any value that is not equal to any of the values in
+     * boundinfo->datums array. So, construct a list of constants from
+     * boundinfo->datums to pass to function make_partition_op_expr via
+     * ArrayExpr, which would return a negated expression for the default
+     * partition.
+     */
This is misleading, since the actual constraint would also have NOT NULL or IS
NULL in there depending upon the existence of a NULL partition.
I would simply rephrase this as "For default list partition, collect lists for
all the partitions. The default partition constraint should check that the
partition key is equal to none of those."
+        ndatums = (pdesc->nparts > 0) ? boundinfo->ndatums : 0;
wouldn't ndatums be simply boundinfo->ndatums? When nparts = 0, ndatums will be
0.
+        int         ndatums = 0;
This assignment looks redundant then.

+ if (boundinfo && partition_bound_accepts_nulls(boundinfo))
You have not checked existence of boundinfo when extracting ndatums out of it
and just few lines below you check that. If the later check is required then we
will get a segfault while extracting ndatums.

+    if ((!list_has_null && !spec->is_default) ||
+        (list_has_null && spec->is_default))
Need a comment explaining what's going on here. The condition is no more a
simple condition.
-            result = -1;
-            *failed_at = parent;
-            *failed_slot = slot;
-            break;
+            if (partition_bound_has_default(partdesc->boundinfo))
+            {
+                result = parent->indexes[partdesc->boundinfo->default_index];
+
+                if (result >= 0)
+                    break;
+                else
+                    parent = pd[-result];
+            }
+            else
+            {
+                result = -1;
+                *failed_at = parent;
+                *failed_slot = slot;
+                break;
+            }
The code to handle result is duplicated here and few lines below. I think it
would be better to not duplicate it by having separate condition blocks to deal
with setting result and setting parent. Basically if (cur_index < 0) ... else
would set the result breaking when setting result = -1 explicitly. A follow-on
block would adjust the parent if result < 0 or break otherwise.

Both the places where DEFAULT_PARTITION_INDEX is used, its result is used to
fetch OID of the default partition. So, instead of having this macro, may be we
should have macro to fetch OID of default partition. But even there I don't see
much value in that. Further, the macro and code using that macro fetches
rd_partdesc directly from Relation. We have RelationGetPartitionDesc() for
that. Probably we should also add Asserts to check that every pointer in the
long pointer chain is Non-null.

InvalidateDefaultPartitionRelcache() is called in case of drop and detach.
Shouldn't the constraint change when we add or attach a new partition.
Shouldn't we invalidate the cache then as well? I am not able to find that
code in your patch.

     /*
+     * Default partition constraints are constructed run-time from the
+     * constraints of its siblings(basically by negating them), so any
+     * change in the siblings needs to rebuild the constraints of the
+     * default partition. So, invalidate the sibling default partition's
+     * relcache.
+     */
May be rephrase this as "The default partition constraints depend upon the
partition bounds of other partitions. Detaching a partition invalidates the
default partition constraints. Invalidate the default partition's relcache so
that the constraints are built anew and any plans dependent on those
constraints are invalidated as well."

+ errmsg("default partition is supported only for
list partitioned table")));
for "a" list partitioned table.

+            /*
+             * A default partition, that can be partition of either LIST or
+             * RANGE partitioned table.
+             * Currently this is supported only for LIST partition.
+             */
Keep everything in single paragraph without line break.

}
+
;
unnecessary extra line.

+        /*
+         * The default partition bound does not have any datums to be
+         * transformed, return the new bound.
+         */
Probably not needed.
+                if (spec->is_default && (strategy == PARTITION_STRATEGY_LIST ||
+                                         strategy == PARTITION_STRATEGY_RANGE))
+                {
+                    appendStringInfoString(buf, "DEFAULT");
+                    break;
+                }
+
What happens if strategy is something other than RANGE or LIST. For that matter
why not just LIST? Possibly you could write this as
+                if (spec->is_default)
+                {
+                    Assert(strategy == PARTITION_STRATEGY_LIST);
+                    appendStringInfoString(buf, "DEFAULT");
+                    break;
+                }
@@ -2044,7 +2044,7 @@ psql_completion(const char *text, int start, int end)
         COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
     /* Limited completion support for partition bound specification */
     else if (TailMatches3("ATTACH", "PARTITION", MatchAny))
-        COMPLETE_WITH_CONST("FOR VALUES");
+        COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
     else if (TailMatches2("FOR", "VALUES"))
         COMPLETE_WITH_LIST2("FROM (", "IN (");
@@ -2483,7 +2483,7 @@ psql_completion(const char *text, int start, int end)
         COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_tables, "");
     /* Limited completion support for partition bound specification */
     else if (TailMatches3("PARTITION", "OF", MatchAny))
-        COMPLETE_WITH_CONST("FOR VALUES");
+        COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
Do we include psql tab completion in the main feature patch? I have not seen
this earlier. But appreciate taking care of these defails.

+char *ExecBuildSlotValueDescription(Oid reloid,
needs an "extern" declaration.

On Fri, Jun 2, 2017 at 1:05 AM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

Hi,

I have addressed Ashutosh's and Amit's comments in the attached patch.

Please let me know if I have missed anything and any further comments.

PFA.

Regards,
Jeevan Ladhe

On Wed, May 31, 2017 at 9:50 AM, Beena Emerson <memissemerson@gmail.com>
wrote:

On Wed, May 31, 2017 at 8:13 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2017/05/31 9:33, Amit Langote wrote:

In get_rule_expr():

case PARTITION_STRATEGY_LIST:
Assert(spec->listdatums != NIL);

+                        /*
+                         * If the boundspec is of Default partition, it
does
+                         * not have list of datums, but has only one
node to
+                         * indicate its a default partition.
+                         */
+                        if (isDefaultPartitionBound(
+                                        (Node *)
linitial(spec->listdatums)))
+                        {
+                            appendStringInfoString(buf, "DEFAULT");
+                            break;
+                        }
+

How about adding this part before the switch (key->strategy)? That way,
we won't have to come back and add this again when we add range default
partitions.

I think it is best that we add a bool is_default to PartitionBoundSpec
and then have a general check for both list and range. Though
listdatums, upperdatums and lowerdatums are set to default for a
DEFAULt partition, it does not seem proper that we check listdatums
for range as well.

--

Beena Emerson

EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
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

#113Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Robert Haas (#111)
Re: Adding support for Default partition in partitioning

Hi Robert,

Thanks for your comments:

If DETACH PARTITION and DROP PARTITION require this, why not ATTACH
PARTITION and CREATE TABLE .. PARTITION OF?

For CREATE and ATTACH parition the invalidation of default relation is taken
care by the following clean-up part in check_default_allows_bound():

+ ResetExprContext(econtext);
+ CHECK_FOR_INTERRUPTS();
+ }
+
+ CacheInvalidateRelcache(part_rel);
+ MemoryContextSwitchTo(oldCxt);

However, post your comment I carefully looked in the code I wrote here, and
I
see that this still explicitly needs cache invalidation in ATTACH and CREATE
command, because the above invalidation call will not happen in case the
default partition is further partitioned. Plus, I think the call to
CacheInvalidateRelcache() in check_default_allows_bound() can be completely
removed.

This code however will be rearranged, as I plan to address Ashutosh's one
of the
comment to write a function for common code of ATExecAttachPartition() and
check_default_allows_bound().

Regards,
Jeevan Ladhe

#114Beena Emerson
memissemerson@gmail.com
In reply to: Jeevan Ladhe (#110)
Re: Adding support for Default partition in partitioning

Hello,

On Fri, Jun 2, 2017 at 1:05 AM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

Hi,

I have addressed Ashutosh's and Amit's comments in the attached patch.

Please let me know if I have missed anything and any further comments.

PFA.

Regards,
Jeevan Ladhe

What is the reason the new patch does not mention of violating rows
when a new partition overlaps with default?
Is it because more than one row could be violating the condition?

--

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

#115Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Beena Emerson (#114)
Re: Adding support for Default partition in partitioning

What is the reason the new patch does not mention of violating rows
when a new partition overlaps with default?
Is it because more than one row could be violating the condition?

This is because, for reporting the violating error, I had to function
ExecBuildSlotValueDescription() public. Per Amit's comment I have
removed this change and let the overlapping error without row contains.
I think this is analogus to other functions that are throwing violation
error
but are not local to execMain.c.

Regards,
Jeevan Ladhe

#116Beena Emerson
memissemerson@gmail.com
In reply to: Jeevan Ladhe (#115)
Re: Adding support for Default partition in partitioning

On Mon, Jun 5, 2017 at 12:14 AM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

What is the reason the new patch does not mention of violating rows
when a new partition overlaps with default?
Is it because more than one row could be violating the condition?

This is because, for reporting the violating error, I had to function
ExecBuildSlotValueDescription() public. Per Amit's comment I have
removed this change and let the overlapping error without row contains.
I think this is analogus to other functions that are throwing violation
error
but are not local to execMain.c.

ok thanks.

--

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

#117Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Beena Emerson (#116)
1 attachment(s)
Re: Adding support for Default partition in partitioning

Hi Ashutosh,

Thanks for the detailed review.

Also, please find my feedback on your comments in-lined, I also addressed
the comments given by Robert in attached patch:

On Sat, Jun 3, 2017 at 5:13 PM, Ashutosh Bapat <ashutosh.bapat@
enterprisedb.com> wrote:

Here's some detailed review of the code.

@@ -1883,6 +1883,15 @@ heap_drop_with_catalog(Oid relid)
if (OidIsValid(parentOid))
{
/*
+         * Default partition constraints are constructed run-time from the
+         * constraints of its siblings(basically by negating them), so any
+         * change in the siblings needs to rebuild the constraints of the
+         * default partition. So, invalidate the sibling default
partition's
+         * relcache.
+         */
+        InvalidateDefaultPartitionRelcache(parentOid);
+
Do we need a lock on the default partition for doing this? A query might be
scanning the default partition directly and we will invalidate the relcache
underneath it. What if two partitions are being dropped simultaneously and
change default constraints simultaneously. Probably the lock on the parent
helps there, but need to check it. What if the default partition cache is
invalidated because partition gets added/dropped to the default partition
itself. If we need a lock on the default partition, we will need to
check the order in which we should be obtaining the locks so as to avoid
deadlocks.

Done. I have taken a lock on default partition after acquiring a lock on
parent
relation where ever applicable.

This also means that we have to test PREPARED statements involving
default partition. Any addition/deletion/attach/detach of other partition
should invalidate those cached statements.

Will add this in next version of patch.

+                        if (partition_bound_has_default(boundinfo))
+                        {
+                            overlap = true;
+                            with = boundinfo->default_index;
+                        }
You could possibly rewrite this as
overlap = partition_bound_has_default(boundinfo);
with = boundinfo->default_index;
that would save one indentation and a conditional jump.

Done

+    if (partdesc->nparts > 0 && partition_bound_has_default(boundinfo))
+        check_default_allows_bound(parent, spec);
If the table has a default partition, nparts > 0, nparts > 0 check looks
redundant. The comments above should also explain that this check doesn't
trigger when a default partition is added since we don't expect an existing
default partition in such a case.

The check nparts > 0, is needed to make sure that the boundinfo is non-null,
i.e. to confirm that there exists at least one partition so that
partition_bound_has_default() does not result in segmentation fault.
I have changed the condition as below to make it more intuitive:
if (boundinfo && partition_bound_has_default(boundinfo))
Also, I have updated the comment.

+ * Checks if there exists any row in the default partition that passes the
+ * check for constraints of new partition, if any reports an error.
grammar two conflicting ifs in the same statement. You may want to rephrase
this as "This function checks if there exists a row in the default
partition that fits in the new
partition and throws an error if it finds one."

Done

+    if (new_spec->strategy != PARTITION_STRATEGY_LIST)
+        return;
This should probably be an Assert. When default range partition is
supported
this function would silently return, meaning there is no row in the default
partition which fits the new partition. We don't want that behavior.

Agreed, changed.

The code in check_default_allows_bound() to check whether the default
partition
has any rows that would fit new partition looks quite similar to the code
in
ATExecAttachPartition() checking whether all rows in the table being
attached
as a partition fit the partition bounds. One thing that
check_default_allows_bound() misses is, if there's already a constraint on
the
default partition refutes the partition constraint on the new partition,
we can
skip the scan of the default partition since it can not have rows that
would
fit the new partition. ATExecAttachPartition() has code to deal with a
similar
case i.e. the table being attached has a constraint which implies the
partition
constraint. There may be more cases which check_default_allows_bound()
does not
handle but ATExecAttachPartition() handles. So, I am wondering whether it's
better to somehow take out the common code into a function and use it. We
will
have to deal with a difference through. The first one would throw an error
when
finding a row that satisfies partition constraints whereas the second one
would
throw an error when it doesn't find such a row. But this difference can be
handled through a flag or by negating the constraint. This would also take
care
of Amit Langote's complaint about foreign partitions. There's also another
difference that the ATExecAttachPartition() queues the table for scan and
the
actual scan takes place in ATRewriteTable(), but there is not such queue
while
creating a table as a partition. But we should check if we can reuse the
code to
scan the heap for checking a constraint.

In case of ATTACH PARTITION, probably we should schedule scan of default
partition in the alter table's work queue like what
ATExecAttachPartition() is
doing for the table being attached. That would fit in the way alter table
works.

I am still working on this.
But, about your comment here:
"if there's already a constraint on the default partition refutes the
partition
constraint on the new partition, we can skip the scan":
I am so far not able to imagine such a case, since default partition
constraint
can be imagined something like "minus infinity to positive infinity with
some finite set elimination", and any new non-default partition being added
would simply be a set of finite values(at-least in case of list, but I
think range
should not also differ here). Hence one cannot imply the other here.
Possibly,
I might be missing something that you had visioned when you raised the flag,
please correct me if I am missing something.

make_partition_op_expr(PartitionKey key, int keynum,
-                       uint16 strategy, Expr *arg1, Expr *arg2)
+                    uint16 strategy, Expr *arg1, Expr *arg2, bool
is_default)
Indentation

Done.

+                if (is_default &&
+                    ((operoid = get_negator(operoid)) == InvalidOid))
+                    ereport(ERROR, (errcode(ERRCODE_RESTRICT_VIOLATION),
+                                    errmsg("DEFAULT partition cannot
be used without negator of operator  %s",
+                                           get_opname(operoid))));
+
If the existence of default partition depends upon the negator, shouldn't
there
be a dependency between the default partition and the negator. At the time
of
creating the default partition, we will try to constuct the partition
constraint for the default partition and if the negator doesn't exist that
time, it will throw an error. But in an unlikely event when the user drops
the
negator, the partitioned table will not be usable at all, as every time it
will
try to create the relcache, it will try to create default partition
constraint
and will throw error because of missing negator. That's not a very good
scenario. Have you tried this case? Apart from that, while restoring a
dump, if
the default partition gets restored before the negator is created, restore
will
fail with this error.

I am looking into this.

/* Generate the main expression, i.e., keyCol = ANY (arr) */
opexpr = make_partition_op_expr(key, 0, BTEqualStrategyNumber,
-                                    keyCol, (Expr *) arr);
+                                    keyCol, (Expr *) arr,
spec->is_default);
/* Build leftop = ANY (rightop) */
saopexpr = makeNode(ScalarArrayOpExpr);
The comments in both the places need correction, as for default partition
the
expression will be keyCol <> ALL(arr).

Done.

+ /*

+     * In case of the default partition for list, the partition constraint
+     * is basically any value that is not equal to any of the values in
+     * boundinfo->datums array. So, construct a list of constants from
+     * boundinfo->datums to pass to function make_partition_op_expr via
+     * ArrayExpr, which would return a negated expression for the default
+     * partition.
+     */
This is misleading, since the actual constraint would also have NOT NULL
or IS
NULL in there depending upon the existence of a NULL partition.
I would simply rephrase this as "For default list partition, collect lists
for
all the partitions. The default partition constraint should check that the
partition key is equal to none of those."

Done.

+ ndatums = (pdesc->nparts > 0) ? boundinfo->ndatums : 0;

wouldn't ndatums be simply boundinfo->ndatums? When nparts = 0, ndatums
will be
0.

Yes, but in case the default partition is the first partition to be added
then
boundinfo will be null and the access to ndatums within it will result in
segmentation fault.
Simplified code to make this more readable.

+ int ndatums = 0;
This assignment looks redundant then.

Per change made for above comment, this is now needed.

+ if (boundinfo && partition_bound_accepts_nulls(boundinfo))
You have not checked existence of boundinfo when extracting ndatums out of
it
and just few lines below you check that. If the later check is required
then we
will get a segfault while extracting ndatums.

The code to extract ndatums is changed and now has a check now for
boundinfo,
but it would not have resulted in segmentation fault in its earlier state
also,
because there was a check for avoiding this i.e. (pdesc->nparts > 0) ?:...

+    if ((!list_has_null && !spec->is_default) ||
+        (list_has_null && spec->is_default))
Need a comment explaining what's going on here. The condition is no more a
simple condition.
-            result = -1;
-            *failed_at = parent;
-            *failed_slot = slot;
-            break;
+            if (partition_bound_has_default(partdesc->boundinfo))
+            {
+                result = parent->indexes[partdesc->boun
dinfo->default_index];
+
+                if (result >= 0)
+                    break;
+                else
+                    parent = pd[-result];
+            }
+            else
+            {
+                result = -1;
+                *failed_at = parent;
+                *failed_slot = slot;
+                break;
+            }
The code to handle result is duplicated here and few lines below. I think
it
would be better to not duplicate it by having separate condition blocks to
deal
with setting result and setting parent. Basically if (cur_index < 0) ...
else
would set the result breaking when setting result = -1 explicitly. A
follow-on
block would adjust the parent if result < 0 or break otherwise.

I have tried to simplified it in attached patch, please let me know if that
change
looks any better.

Both the places where DEFAULT_PARTITION_INDEX is used, its result is used
to
fetch OID of the default partition. So, instead of having this macro, may
be we
should have macro to fetch OID of default partition. But even there I
don't see
much value in that.

Removed the macro, and did this in place at both the places.

Further, the macro and code using that macro fetches

rd_partdesc directly from Relation.

Done this where ever applicable.

We have RelationGetPartitionDesc() for

that. Probably we should also add Asserts to check that every pointer in
the
long pointer chain is Non-null.

I am sorry, but I did not understand which chain you are trying to point
here.

InvalidateDefaultPartitionRelcache() is called in case of drop and detach.

Shouldn't the constraint change when we add or attach a new partition.
Shouldn't we invalidate the cache then as well? I am not able to find that
code in your patch.

In case of CREATE/ATTACH this was taken care by a call to
CacheInvalidateRelcache(part_rel) in check_default_allows_bound(), which
wasn't
the correct place anyway, and this had a flaw that the invalidation would
not
happen in case the default partition is further partitioned.
Now, the relcache for default partition is getting invalidated for
CREATE/DROP/ALTER commands.

/*
+     * Default partition constraints are constructed run-time from the
+     * constraints of its siblings(basically by negating them), so any
+     * change in the siblings needs to rebuild the constraints of the
+     * default partition. So, invalidate the sibling default partition's
+     * relcache.
+     */
May be rephrase this as "The default partition constraints depend upon the
partition bounds of other partitions. Detaching a partition invalidates the
default partition constraints. Invalidate the default partition's relcache
so
that the constraints are built anew and any plans dependent on those
constraints are invalidated as well."

Done!

+ errmsg("default partition is supported only for
list partitioned table")));
for "a" list partitioned table.

Done.

+            /*
+             * A default partition, that can be partition of either LIST
or
+             * RANGE partitioned table.
+             * Currently this is supported only for LIST partition.
+             */
Keep everything in single paragraph without line break.

Not applicable now, as I removed the later part of the comment.

}

+
;
unnecessary extra line.

Removed.

+        /*
+         * The default partition bound does not have any datums to be
+         * transformed, return the new bound.
+         */
Probably not needed.

Removed.

+                if (spec->is_default && (strategy ==
PARTITION_STRATEGY_LIST ||
+                                         strategy ==
PARTITION_STRATEGY_RANGE))
+                {
+                    appendStringInfoString(buf, "DEFAULT");
+                    break;
+                }
+
What happens if strategy is something other than RANGE or LIST. For that
matter
why not just LIST? Possibly you could write this as
+                if (spec->is_default)
+                {
+                    Assert(strategy == PARTITION_STRATEGY_LIST);
+                    appendStringInfoString(buf, "DEFAULT");
+                    break;
+                }

Done.

@@ -2044,7 +2044,7 @@ psql_completion(const char *text, int start, int end)
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
/* Limited completion support for partition bound specification */
else if (TailMatches3("ATTACH", "PARTITION", MatchAny))
-        COMPLETE_WITH_CONST("FOR VALUES");
+        COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
else if (TailMatches2("FOR", "VALUES"))
COMPLETE_WITH_LIST2("FROM (", "IN (");
@@ -2483,7 +2483,7 @@ psql_completion(const char *text, int start, int end)
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_tables,
"");
/* Limited completion support for partition bound specification */
else if (TailMatches3("PARTITION", "OF", MatchAny))
-        COMPLETE_WITH_CONST("FOR VALUES");
+        COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
Do we include psql tab completion in the main feature patch? I have not
seen
this earlier. But appreciate taking care of these defails.

I am not sure about this. If needed I can submit a patch to take care of
this later, but
as of now I have not removed this from the patch.

+char *ExecBuildSlotValueDescription(Oid reloid,

needs an "extern" declaration.

Per one of the comment[1]/messages/by-id/7c758a6b-107e-7c82- 0d3c-3af7965cad3f%40lab.ntt.co.jp given by Amit Langote, I have removed a call to
ExecBuildSlotValueDescription(), and this was a leftover, I cleaned it up.

[1]: /messages/by-id/7c758a6b-107e-7c82- 0d3c-3af7965cad3f%40lab.ntt.co.jp
0d3c-3af7965cad3f%40lab.ntt.co.jp

Regards,
Jeevan Ladhe

Attachments:

default_partition_v19.patchapplication/octet-stream; name=default_partition_v19.patchDownload
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 0ce94f3..a009b96 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1755,9 +1755,11 @@ RemoveAttrDefaultById(Oid attrdefId)
 void
 heap_drop_with_catalog(Oid relid)
 {
-	Relation	rel;
+	Relation	rel,
+				parentRel;
 	HeapTuple	tuple;
-	Oid			parentOid = InvalidOid;
+	Oid			parentOid = InvalidOid,
+				defaultPartOid = InvalidOid;
 
 	/*
 	 * To drop a partition safely, we must grab exclusive lock on its parent,
@@ -1772,7 +1774,15 @@ heap_drop_with_catalog(Oid relid)
 	if (((Form_pg_class) GETSTRUCT(tuple))->relispartition)
 	{
 		parentOid = get_partition_parent(relid);
-		LockRelationOid(parentOid, AccessExclusiveLock);
+		parentRel = heap_open(parentOid, AccessExclusiveLock);
+
+		/*
+		 * Need to take a lock on the default partition, refer comment for
+		 * locking the default partition in DefineRelation().
+		 */
+		defaultPartOid = RelationGetDefaultPartitionOid(parentRel);
+		if (OidIsValid(defaultPartOid))
+			LockRelationOid(defaultPartOid, AccessExclusiveLock);
 	}
 
 	ReleaseSysCache(tuple);
@@ -1883,11 +1893,19 @@ heap_drop_with_catalog(Oid relid)
 	if (OidIsValid(parentOid))
 	{
 		/*
+		 * Invalidate default partition's relcache, refer comment for
+		 * invalidating default partition relcache in StorePartitionBound().
+		 */
+		if (OidIsValid(defaultPartOid))
+			CacheInvalidateRelcacheByRelid(defaultPartOid);
+
+		/*
 		 * Invalidate the parent's relcache so that the partition is no longer
 		 * included in its partition descriptor.
 		 */
 		CacheInvalidateRelcacheByRelid(parentOid);
 		/* keep the lock */
+		heap_close(parentRel, NoLock);
 	}
 }
 
@@ -3215,8 +3233,10 @@ RemovePartitionKeyByRelId(Oid relid)
  *		Update pg_class tuple of rel to store the partition bound and set
  *		relispartition to true
  *
- * Also, invalidate the parent's relcache, so that the next rebuild will load
- * the new partition's info into its partition descriptor.
+ * Also, invalidate the parent's and a sibling default partition's relcache,
+ * so that the next rebuild will load the new partition's info into parent's
+ * partition descriptor and default partition constraints(which are dependent
+ * on other partition bounds) are built anew.
  */
 void
 StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
@@ -3227,6 +3247,7 @@ StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
 	Datum		new_val[Natts_pg_class];
 	bool		new_null[Natts_pg_class],
 				new_repl[Natts_pg_class];
+	Oid			defaultPartOid;
 
 	/* Update pg_class tuple */
 	classRel = heap_open(RelationRelationId, RowExclusiveLock);
@@ -3264,5 +3285,16 @@ StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
 	heap_freetuple(newtuple);
 	heap_close(classRel, RowExclusiveLock);
 
+	/*
+	 * The default partition constraints depend upon the partition bounds of
+	 * other partitions. Adding a new(or even removing existing) partition
+	 * would invalidate the default partition constraints. Invalidate the
+	 * default partition's relcache so that the constraints are built anew
+	 * and any plans dependent on those constraints are invalidated as well.
+	 */
+	defaultPartOid = RelationGetDefaultPartitionOid(parent);
+	if (OidIsValid(defaultPartOid))
+		CacheInvalidateRelcacheByRelid(defaultPartOid);
+
 	CacheInvalidateRelcache(parent);
 }
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 5c5a9e1..3276023 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -35,6 +35,7 @@
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planmain.h"
+#include "optimizer/prep.h"
 #include "optimizer/var.h"
 #include "rewrite/rewriteManip.h"
 #include "storage/lmgr.h"
@@ -88,9 +89,12 @@ typedef struct PartitionBoundInfoData
 								 * partitioned table) */
 	int			null_index;		/* Index of the null-accepting partition; -1
 								 * if there isn't one */
+	int			default_index;	/* Index of the default partition if any; -1
+								 * if there isn't one */
 } 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
@@ -121,14 +125,15 @@ static int32 qsort_partition_rbound_cmp(const void *a, const void *b,
 static Oid get_partition_operator(PartitionKey key, int col,
 					   StrategyNumber strategy, bool *need_relabel);
 static Expr *make_partition_op_expr(PartitionKey key, int keynum,
-					   uint16 strategy, Expr *arg1, Expr *arg2);
+					   uint16 strategy, Expr *arg1, Expr *arg2,
+					   bool is_default);
 static void get_range_key_properties(PartitionKey key, int keynum,
 						 PartitionRangeDatum *ldatum,
 						 PartitionRangeDatum *udatum,
 						 ListCell **partexprs_item,
 						 Expr **keyCol,
 						 Const **lower_val, Const **upper_val);
-static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_list(Relation parent, PartitionBoundSpec *spec);
 static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
 static List *generate_partition_qual(Relation rel);
 
@@ -147,6 +152,8 @@ static int32 partition_bound_cmp(PartitionKey key,
 static int partition_bound_bsearch(PartitionKey key,
 						PartitionBoundInfo boundinfo,
 						void *probe, bool probe_is_bound, bool *is_equal);
+static void check_default_allows_bound(Relation parent,
+						   PartitionBoundSpec *new_spec);
 
 /*
  * RelationBuildPartitionDesc
@@ -174,6 +181,7 @@ RelationBuildPartitionDesc(Relation rel)
 	/* List partitioning specific */
 	PartitionListValue **all_values = NULL;
 	int			null_index = -1;
+	int			default_index = -1;
 
 	/* Range partitioning specific */
 	PartitionRangeBound **rbounds = NULL;
@@ -254,6 +262,18 @@ RelationBuildPartitionDesc(Relation rel)
 				if (spec->strategy != PARTITION_STRATEGY_LIST)
 					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 list of non-null
+				 * datums for this partition.
+				 */
+				if (spec->is_default)
+				{
+					default_index = i;
+					i++;
+					continue;
+				}
+
 				foreach(c, spec->listdatums)
 				{
 					Const	   *val = castNode(Const, lfirst(c));
@@ -506,6 +526,22 @@ RelationBuildPartitionDesc(Relation rel)
 					else
 						boundinfo->null_index = -1;
 
+					/* Assign mapped index for the default partition. */
+					if (default_index != -1)
+					{
+						/*
+						 * The default partition accepts any non-specified
+						 * value, hence it should not get a mapped index while
+						 * assigning those for non-null datums.
+						 */
+						Assert(default_index >= 0 &&
+							   mapping[default_index] == -1);
+						mapping[default_index] = next_index++;
+						boundinfo->default_index = mapping[default_index];
+					}
+					else
+						boundinfo->default_index = -1;
+
 					/* All partition must now have a valid mapping */
 					Assert(next_index == nparts);
 					break;
@@ -561,6 +597,9 @@ RelationBuildPartitionDesc(Relation rel)
 						}
 					}
 					boundinfo->indexes[i] = -1;
+
+					/* As of now, we do not support default range partition. */
+					boundinfo->default_index = -1;
 					break;
 				}
 
@@ -610,6 +649,9 @@ partition_bounds_equal(PartitionKey key,
 	if (b1->null_index != b2->null_index)
 		return false;
 
+	if (b1->default_index != b2->default_index)
+		return false;
+
 	for (i = 0; i < b1->ndatums; i++)
 	{
 		int			j;
@@ -672,6 +714,7 @@ check_new_partition_bound(char *relname, Relation parent,
 {
 	PartitionKey key = RelationGetPartitionKey(parent);
 	PartitionDesc partdesc = RelationGetPartitionDesc(parent);
+	PartitionBoundInfo boundinfo = partdesc->boundinfo;
 	ParseState *pstate = make_parsestate(NULL);
 	int			with = -1;
 	bool		overlap = false;
@@ -684,13 +727,24 @@ check_new_partition_bound(char *relname, Relation parent,
 
 				if (partdesc->nparts > 0)
 				{
-					PartitionBoundInfo boundinfo = partdesc->boundinfo;
 					ListCell   *cell;
 
 					Assert(boundinfo &&
 						   boundinfo->strategy == PARTITION_STRATEGY_LIST &&
 						   (boundinfo->ndatums > 0 ||
-							partition_bound_accepts_nulls(boundinfo)));
+							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)
 					{
@@ -832,12 +886,150 @@ check_new_partition_bound(char *relname, Relation parent,
 	if (overlap)
 	{
 		Assert(with >= 0);
+
+		if (spec->is_default)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("partition \"%s\" conflicts with existing default partition \"%s\"",
+							relname, get_rel_name(partdesc->oids[with])),
+					 parser_errposition(pstate, spec->location)));
+
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 				 errmsg("partition \"%s\" would overlap partition \"%s\"",
 						relname, get_rel_name(partdesc->oids[with])),
 				 parser_errposition(pstate, spec->location)));
 	}
+
+	/*
+	 * If the default partition exists, its partition constraints will change
+	 * after the addition of this new partition such that it won't allow any
+	 * row that qualifies for this new partition. So, check if the existing
+	 * data in the default partition satisfies this *would be* default
+	 * partition constraint.
+	 * In case the new partition bound being checked itself is a DEFAULT
+	 * bound, this check shouldn't be triggered as there won't already exists
+	 * the default partition in such a case.
+	 */
+	if (boundinfo && partition_bound_has_default(boundinfo))
+		check_default_allows_bound(parent, spec);
+}
+
+/*
+ * check_default_allows_bound
+ *
+ * This function checks if there exists a row in the default partition that
+ * fits in the new partition and throws an error if it finds one.
+ */
+static void
+check_default_allows_bound(Relation parent, PartitionBoundSpec *new_spec)
+{
+	Relation	default_rel;
+	int			default_index;
+	List	   *new_part_constraints = NIL;
+	List	   *all_parts;
+	ListCell   *lc;
+
+	/* 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(RelationGetPartitionDesc(parent)->boundinfo != NULL);
+
+	new_part_constraints = get_qual_for_list(parent, new_spec);
+	new_part_constraints = (List *) eval_const_expressions(NULL,
+											  (Node *) new_part_constraints);
+	new_part_constraints =
+		(List *) canonicalize_qual((Expr *) new_part_constraints);
+	new_part_constraints = list_make1(make_ands_explicit(new_part_constraints));
+
+	/* Generate the constraint and default execution states. */
+	default_index = RelationGetPartitionDesc(parent)->boundinfo->default_index;
+	default_rel =
+		heap_open(RelationGetPartitionDesc(parent)->oids[default_index],
+				  AccessExclusiveLock);
+
+	if (default_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		all_parts = find_all_inheritors(RelationGetRelid(default_rel),
+										AccessExclusiveLock, NULL);
+	else
+		all_parts = list_make1_oid(RelationGetRelid(default_rel));
+
+	foreach(lc, all_parts)
+	{
+		Oid			part_relid = lfirst_oid(lc);
+		Relation	part_rel;
+		Expr	   *constr;
+		Expr	   *partition_constraint;
+		EState	   *estate;
+		HeapTuple	tuple;
+		ExprState  *partqualstate = NULL;
+		Snapshot	snapshot;
+		TupleDesc	tupdesc;
+		ExprContext *econtext;
+		HeapScanDesc scan;
+		MemoryContext oldCxt;
+		TupleTableSlot *tupslot;
+
+		/* Lock already taken above. */
+		if (part_relid != RelationGetRelid(default_rel))
+			part_rel = heap_open(part_relid, NoLock);
+		else
+			part_rel = default_rel;
+
+		/*
+		 * Skip if it's a partitioned table. Only RELKIND_RELATION relations
+		 * (ie, leaf partitions) need to be scanned.
+		 */
+		if (part_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+			part_rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+		{
+			heap_close(part_rel, NoLock);
+			continue;
+		}
+
+		tupdesc = CreateTupleDescCopy(RelationGetDescr(part_rel));
+		constr = linitial(new_part_constraints);
+		partition_constraint = (Expr *) map_partition_varattnos((List *) constr,
+														1, part_rel, parent);
+		estate = CreateExecutorState();
+
+		/* Build expression execution states for partition check quals */
+		partqualstate = ExecPrepareExpr(partition_constraint, estate);
+
+		econtext = GetPerTupleExprContext(estate);
+		snapshot = RegisterSnapshot(GetLatestSnapshot());
+		scan = heap_beginscan(part_rel, snapshot, 0, NULL);
+		tupslot = MakeSingleTupleTableSlot(tupdesc);
+
+		/*
+		 * Switch to per-tuple memory context and reset it for each tuple
+		 * produced, so we don't leak memory.
+		 */
+		oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		{
+			ExecStoreTuple(tuple, tupslot, InvalidBuffer, false);
+			econtext->ecxt_scantuple = tupslot;
+
+			if (partqualstate && ExecCheck(partqualstate, econtext))
+				ereport(ERROR,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("new partition constraint for default partition \"%s\" would be violated by some row",
+							   RelationGetRelationName(default_rel))));
+
+			ResetExprContext(econtext);
+			CHECK_FOR_INTERRUPTS();
+		}
+
+		MemoryContextSwitchTo(oldCxt);
+		heap_endscan(scan);
+		UnregisterSnapshot(snapshot);
+		ExecDropSingleTupleTableSlot(tupslot);
+		FreeExecutorState(estate);
+		heap_close(part_rel, NoLock);
+	}
 }
 
 /*
@@ -904,7 +1096,7 @@ get_qual_from_partbound(Relation rel, Relation parent,
 	{
 		case PARTITION_STRATEGY_LIST:
 			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
-			my_qual = get_qual_for_list(key, spec);
+			my_qual = get_qual_for_list(parent, spec);
 			break;
 
 		case PARTITION_STRATEGY_RANGE:
@@ -1234,7 +1426,7 @@ get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
  */
 static Expr *
 make_partition_op_expr(PartitionKey key, int keynum,
-					   uint16 strategy, Expr *arg1, Expr *arg2)
+					   uint16 strategy, Expr *arg1, Expr *arg2, bool is_default)
 {
 	Oid			operoid;
 	bool		need_relabel = false;
@@ -1264,11 +1456,21 @@ make_partition_op_expr(PartitionKey key, int keynum,
 			{
 				ScalarArrayOpExpr *saopexpr;
 
-				/* Build leftop = ANY (rightop) */
+				if (is_default &&
+					((operoid = get_negator(operoid)) == InvalidOid))
+					ereport(ERROR, (errcode(ERRCODE_RESTRICT_VIOLATION),
+									errmsg("DEFAULT partition cannot be used without negator of operator  %s",
+										   get_opname(operoid))));
+
+				/*
+				 * Build expression:
+				 * "leftop = ANY (rightop)" for a non-default partition OR
+				 * "leftop <> ALL (rightop)" for the default partition.
+				 */
 				saopexpr = makeNode(ScalarArrayOpExpr);
 				saopexpr->opno = operoid;
 				saopexpr->opfuncid = get_opcode(operoid);
-				saopexpr->useOr = true;
+				saopexpr->useOr = !is_default;
 				saopexpr->inputcollid = key->partcollation[keynum];
 				saopexpr->args = list_make2(arg1, arg2);
 				saopexpr->location = -1;
@@ -1301,8 +1503,9 @@ make_partition_op_expr(PartitionKey key, int keynum,
  * constraint, given the partition key and bound structures.
  */
 static List *
-get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
+get_qual_for_list(Relation parent, PartitionBoundSpec *spec)
 {
+	PartitionKey key = RelationGetPartitionKey(parent);
 	List	   *result;
 	Expr	   *keyCol;
 	ArrayExpr  *arr;
@@ -1323,15 +1526,60 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 	else
 		keyCol = (Expr *) copyObject(linitial(key->partexprs));
 
-	/* Create list of Consts for the allowed values, excluding any nulls */
-	foreach(cell, spec->listdatums)
+	/*
+	 * For default list partition, collect datums for all the partitions. The
+	 * default partition constraint should check that the partition key is
+	 * equal to none of those.
+	 */
+	if (spec->is_default)
 	{
-		Const	   *val = castNode(Const, lfirst(cell));
+		int         i;
+		int         ndatums = 0;
+		MemoryContext oldcxt;
+		PartitionDesc pdesc = RelationGetPartitionDesc(parent);
+		PartitionBoundInfo boundinfo = pdesc->boundinfo;
 
-		if (val->constisnull)
+		if (boundinfo)
+			ndatums = boundinfo->ndatums;
+
+		oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+
+		for (i = 0; i < ndatums; i++)
+		{
+			Const      *val;
+
+			/* Construct const from datum */
+			val = makeConst(key->parttypid[0],
+							key->parttypmod[0],
+							key->parttypcoll[0],
+							key->parttyplen[0],
+							*boundinfo->datums[i],
+							false,      /* isnull */
+							true /* byval */ );
+
+			arrelems = lappend(arrelems, val);
+		}
+
+		if (boundinfo && partition_bound_accepts_nulls(boundinfo))
 			list_has_null = true;
-		else
-			arrelems = lappend(arrelems, copyObject(val));
+
+		MemoryContextSwitchTo(oldcxt);
+	}
+	else
+	{
+		/*
+		 * Create list of Consts for the allowed values, excluding any
+		 * nulls.
+		 */
+		foreach(cell, spec->listdatums)
+		{
+			Const	   *val = castNode(Const, lfirst(cell));
+
+			if (val->constisnull)
+				list_has_null = true;
+			else
+				arrelems = lappend(arrelems, copyObject(val));
+		}
 	}
 
 	/* Construct an ArrayExpr for the non-null partition values */
@@ -1345,11 +1593,22 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 	arr->multidims = false;
 	arr->location = -1;
 
-	/* Generate the main expression, i.e., keyCol = ANY (arr) */
+	/*
+	 * Generate the main expression as below:
+	 * in case of the default partition: keyCol <> ALL (arr)
+	 * or for a non-default partition: keyCol = ANY (arr)
+	 */
 	opexpr = make_partition_op_expr(key, 0, BTEqualStrategyNumber,
-									keyCol, (Expr *) arr);
+									keyCol, (Expr *) arr, spec->is_default);
 
-	if (!list_has_null)
+	/*
+	 * If this isn't a default partition and does not accept a null or if this
+	 * is a default partition and there exists another partition accepting null;
+	 * in any of these two cases this partition cannot accept null, and we need
+	 * to build a IS_NOT_NULL constraint for it.
+	 */
+	if ((!list_has_null && !spec->is_default) ||
+		(list_has_null && spec->is_default))
 	{
 		/*
 		 * Gin up a "col IS NOT NULL" test that will be AND'd with the main
@@ -1592,7 +1851,7 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 		oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
 		test_expr = make_partition_op_expr(key, i, BTEqualStrategyNumber,
 										   (Expr *) lower_val,
-										   (Expr *) upper_val);
+										   (Expr *) upper_val, false);
 		fix_opfuncids((Node *) test_expr);
 		test_exprstate = ExecInitExpr(test_expr, NULL);
 		test_result = ExecEvalExprSwitchContext(test_exprstate,
@@ -1616,7 +1875,8 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 		/* Equal, so generate keyCol = lower_val expression */
 		result = lappend(result,
 						 make_partition_op_expr(key, i, BTEqualStrategyNumber,
-												keyCol, (Expr *) lower_val));
+												keyCol, (Expr *) lower_val,
+												false));
 
 		i++;
 	}
@@ -1677,7 +1937,8 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 											make_partition_op_expr(key, j,
 																   strategy,
 																   keyCol,
-														(Expr *) lower_val));
+														(Expr *) lower_val,
+																   false));
 			}
 
 			if (need_next_upper_arm && upper_val)
@@ -1699,7 +1960,8 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 											make_partition_op_expr(key, j,
 																   strategy,
 																   keyCol,
-														(Expr *) upper_val));
+														(Expr *) upper_val,
+																   false));
 			}
 
 			/*
@@ -1989,8 +2251,9 @@ get_partition_for_tuple(PartitionDispatch *pd,
 		}
 
 		/*
-		 * A null partition key is only acceptable if null-accepting list
-		 * partition exists.
+		 * Handle NULL partition key here if there's a null-accepting list
+		 * partition, else later it will be routed to the default partition if
+		 * one exists.
 		 */
 		cur_index = -1;
 		if (isnull[0] && partition_bound_accepts_nulls(partdesc->boundinfo))
@@ -2028,10 +2291,16 @@ get_partition_for_tuple(PartitionDispatch *pd,
 		}
 
 		/*
-		 * cur_index < 0 means we failed to find a partition of this parent.
-		 * cur_index >= 0 means we either found the leaf partition, or the
-		 * next parent to find a partition of.
+		 * cur_index < 0 means we could not find a non-default partition of this
+		 * parent. cur_index >= 0 means we either found the leaf partition, or
+		 * the next parent to find a partition of.
+		 *
+		 * If we couldn't find a non-default partition check if the default
+		 * partition exists, if it does, get its index.
 		 */
+		if (cur_index < 0 && (partition_bound_has_default(partdesc->boundinfo)))
+			cur_index = partdesc->boundinfo->default_index;
+
 		if (cur_index < 0)
 		{
 			result = -1;
@@ -2313,3 +2582,20 @@ partition_bound_bsearch(PartitionKey key, PartitionBoundInfo boundinfo,
 
 	return lo;
 }
+
+/*
+ * RelationGetDefaultPartitionOid
+ *
+ * Given the parent relation checks if it has default partition, and if it
+ * does exist returns its oid, otherwise returns InvalidOid.
+ */
+Oid
+RelationGetDefaultPartitionOid(Relation parent)
+{
+	PartitionDesc partdesc = RelationGetPartitionDesc(parent);
+
+	if (partdesc->boundinfo && partition_bound_has_default(partdesc->boundinfo))
+		return partdesc->oids[partdesc->boundinfo->default_index];
+
+	return InvalidOid;
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index b61fda9..bdce229 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -758,7 +758,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	{
 		PartitionBoundSpec *bound;
 		ParseState *pstate;
-		Oid			parentId = linitial_oid(inheritOids);
+		Oid			parentId = linitial_oid(inheritOids),
+					defaultPartOid;
 		Relation	parent;
 
 		/* Already have strong enough lock on the parent */
@@ -774,6 +775,20 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 					 errmsg("\"%s\" is not partitioned",
 							RelationGetRelationName(parent))));
 
+		/*
+		 * We need to take an exclusive lock on the default partition if it
+		 * exists, because its constraints are dependent upon other partition
+		 * bounds. It is possible that other backend might be about to execute
+		 * a query on the default partition table and the query relies on
+		 * previously cached default partition constraints; those constraints
+		 * won't stand correct after addition(or even removal) of a partition.
+		 * We must therefore take an exclusive lock to prevent all queries on
+		 * the default partition table from proceeding until we commit.
+		 */
+		defaultPartOid = RelationGetDefaultPartitionOid(parent);
+		if (OidIsValid(defaultPartOid))
+			LockRelationOid(defaultPartOid, AccessExclusiveLock);
+
 		/* Tranform the bound values */
 		pstate = make_parsestate(NULL);
 		pstate->p_sourcetext = queryString;
@@ -13798,6 +13813,7 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 				new_null[Natts_pg_class],
 				new_repl[Natts_pg_class];
 	ObjectAddress address;
+	Oid			defaultPartOid;
 
 	partRel = heap_openrv(name, AccessShareLock);
 
@@ -13830,6 +13846,14 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 	heap_close(classRel, RowExclusiveLock);
 
 	/*
+	 * Invalidate default partition's relcache, refer comment for invalidating
+	 * default partition relcache in StorePartitionBound().
+	 */
+	defaultPartOid = RelationGetDefaultPartitionOid(rel);
+	if (OidIsValid(defaultPartOid))
+		CacheInvalidateRelcacheByRelid(defaultPartOid);
+
+	/*
 	 * Invalidate the parent's relcache so that the partition is no longer
 	 * included in its partition descriptor.
 	 */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 36bf1dc..03d2df9 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4444,6 +4444,7 @@ _copyPartitionBoundSpec(const PartitionBoundSpec *from)
 	PartitionBoundSpec *newnode = makeNode(PartitionBoundSpec);
 
 	COPY_SCALAR_FIELD(strategy);
+	COPY_SCALAR_FIELD(is_default);
 	COPY_NODE_FIELD(listdatums);
 	COPY_NODE_FIELD(lowerdatums);
 	COPY_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 5bcf031..01fd43d 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2838,6 +2838,7 @@ static bool
 _equalPartitionBoundSpec(const PartitionBoundSpec *a, const PartitionBoundSpec *b)
 {
 	COMPARE_SCALAR_FIELD(strategy);
+	COMPARE_SCALAR_FIELD(is_default);
 	COMPARE_NODE_FIELD(listdatums);
 	COMPARE_NODE_FIELD(lowerdatums);
 	COMPARE_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index c348bdc..ae66d3d 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3543,6 +3543,7 @@ _outPartitionBoundSpec(StringInfo str, const PartitionBoundSpec *node)
 	WRITE_NODE_TYPE("PARTITIONBOUNDSPEC");
 
 	WRITE_CHAR_FIELD(strategy);
+	WRITE_BOOL_FIELD(is_default);
 	WRITE_NODE_FIELD(listdatums);
 	WRITE_NODE_FIELD(lowerdatums);
 	WRITE_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 81ddfc3..290a0bb 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2373,6 +2373,7 @@ _readPartitionBoundSpec(void)
 	READ_LOCALS(PartitionBoundSpec);
 
 	READ_CHAR_FIELD(strategy);
+	READ_BOOL_FIELD(is_default);
 	READ_NODE_FIELD(listdatums);
 	READ_NODE_FIELD(lowerdatums);
 	READ_NODE_FIELD(upperdatums);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7e03624..a73e506 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2657,6 +2657,7 @@ ForValues:
 					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
 
 					n->strategy = PARTITION_STRATEGY_LIST;
+					n->is_default = false;
 					n->listdatums = $5;
 					n->location = @3;
 
@@ -2669,12 +2670,27 @@ ForValues:
 					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
 
 					n->strategy = PARTITION_STRATEGY_RANGE;
+					n->is_default = false;
 					n->lowerdatums = $5;
 					n->upperdatums = $9;
 					n->location = @3;
 
 					$$ = n;
 				}
+
+			/*
+			 * A default partition, that can be partition of either
+			 * LIST or RANGE partitioned table.
+			 */
+			| DEFAULT
+				{
+					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+					n->is_default = true;
+					n->location = @1;
+
+					$$ = n;
+				}
 		;
 
 partbound_datum:
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 9134fb9..dd9d736 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -60,6 +61,7 @@
 #include "parser/parse_utilcmd.h"
 #include "parser/parser.h"
 #include "rewrite/rewriteManip.h"
+#include "storage/lmgr.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
@@ -3250,6 +3252,7 @@ static void
 transformPartitionCmd(CreateStmtContext *cxt, PartitionCmd *cmd)
 {
 	Relation	parentRel = cxt->rel;
+	Oid			defaultPartOid;
 
 	/* the table must be partitioned */
 	if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
@@ -3258,6 +3261,14 @@ transformPartitionCmd(CreateStmtContext *cxt, PartitionCmd *cmd)
 				 errmsg("\"%s\" is not partitioned",
 						RelationGetRelationName(parentRel))));
 
+	/*
+	 * Need to take a lock on the default partition, refer comment for locking
+	 * the default partition in DefineRelation().
+	 */
+	defaultPartOid = RelationGetDefaultPartitionOid(parentRel);
+	if (OidIsValid(defaultPartOid))
+		LockRelationOid(defaultPartOid, AccessExclusiveLock);
+
 	/* transform the partition bound, if any */
 	Assert(RelationGetPartitionKey(parentRel) != NULL);
 	if (cmd->bound != NULL)
@@ -3283,6 +3294,23 @@ transformPartitionBound(ParseState *pstate, Relation parent,
 	/* Avoid scribbling on input */
 	result_spec = copyObject(spec);
 
+	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
+		 * partition bound spec.
+		 */
+		result_spec->strategy = strategy;
+
+		return result_spec;
+	}
+
 	if (strategy == PARTITION_STRATEGY_LIST)
 	{
 		ListCell   *cell;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 6a0d273..0c1e72a 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8644,10 +8644,18 @@ get_rule_expr(Node *node, deparse_context *context,
 		case T_PartitionBoundSpec:
 			{
 				PartitionBoundSpec *spec = (PartitionBoundSpec *) node;
+				char		strategy = spec->strategy;
 				ListCell   *cell;
 				char	   *sep;
 
-				switch (spec->strategy)
+				if (spec->is_default)
+				{
+					Assert(strategy == PARTITION_STRATEGY_LIST);
+					appendStringInfoString(buf, "DEFAULT");
+					break;
+				}
+
+				switch (strategy)
 				{
 					case PARTITION_STRATEGY_LIST:
 						Assert(spec->listdatums != NIL);
@@ -8713,7 +8721,7 @@ get_rule_expr(Node *node, deparse_context *context,
 
 					default:
 						elog(ERROR, "unrecognized partition strategy: %d",
-							 (int) spec->strategy);
+							 (int) strategy);
 						break;
 				}
 			}
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index d4b6976..00fcbd3 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2058,7 +2058,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("ATTACH", "PARTITION", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 	else if (TailMatches2("FOR", "VALUES"))
 		COMPLETE_WITH_LIST2("FROM (", "IN (");
 
@@ -2497,7 +2497,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("PARTITION", "OF", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 
 /* CREATE TABLESPACE */
 	else if (Matches3("CREATE", "TABLESPACE", MatchAny))
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 0a1e468..e4e079b 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -98,4 +98,5 @@ extern int get_partition_for_tuple(PartitionDispatch *pd,
 						EState *estate,
 						PartitionDispatchData **failed_at,
 						TupleTableSlot **failed_slot);
+extern Oid RelationGetDefaultPartitionOid(Relation parent);
 #endif   /* PARTITION_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 8720e71..e717778 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -798,6 +798,7 @@ typedef struct PartitionBoundSpec
 	NodeTag		type;
 
 	char		strategy;		/* see PARTITION_STRATEGY codes above */
+	bool		is_default;		/* is it a default partition bound? */
 
 	/* Partitioning info for LIST strategy: */
 	List	   *listdatums;		/* List of Consts (or A_Consts in raw tree) */
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 8aadbb8..5c59028 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3273,6 +3273,11 @@ SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::reg
 CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
 ERROR:  partition "fail_part" would overlap partition "part_1"
+-- attaching default partition overlaps if the default partition already exists
+CREATE TABLE def_part PARTITION OF list_parted DEFAULT;
+CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT;
+ERROR:  partition "fail_def_part" conflicts with existing default partition "def_part"
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
 	a int,
@@ -3286,6 +3291,15 @@ ERROR:  partition constraint is violated by some row
 -- should be ok after deleting the bad row
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+-- check partition cannot be attached if default has some row for its values
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
+INSERT INTO list_parted2_def VALUES (11, 'z');
+CREATE TABLE part_3 (LIKE list_parted2);
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+ERROR:  new partition constraint for default partition "list_parted2_def" would be violated by some row
+-- should be ok after deleting the bad row
+DELETE FROM list_parted2_def WHERE a = 11;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
 CREATE TABLE part_3_4 (
@@ -3341,6 +3355,18 @@ DELETE FROM part_5_a WHERE a NOT IN (3);
 ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT NULL;
 ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
 INFO:  partition constraint for table "part_5" is implied by existing constraints
+-- check that leaf partitions of default partition are scanned when
+-- attaching a partitioned table.
+ALTER TABLE part_5 DROP CONSTRAINT check_a;
+CREATE TABLE part5_def PARTITION OF part_5 DEFAULT PARTITION BY LIST(a);
+CREATE TABLE part5_def_p1 PARTITION OF part5_def FOR VALUES IN (5);
+INSERT INTO part5_def_p1 VALUES (5, 'y');
+CREATE TABLE part5_p1 (LIKE part_5);
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+ERROR:  new partition constraint for default partition "part5_def" would be violated by some row
+-- should be ok after deleting the bad row
+DELETE FROM part5_def_p1 WHERE b = 'y';
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
 -- check that the table being attached is not already a partition
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 ERROR:  "part_2" is already a partition
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 5136506..81f45d0 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -449,6 +449,7 @@ CREATE TABLE list_parted (
 CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
 CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
 CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
 ERROR:  syntax error at or near "int"
 LINE 1: ... fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
@@ -457,6 +458,8 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
 ERROR:  syntax error at or near "::"
 LINE 1: ...fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
                                                                 ^
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
+ERROR:  partition "fail_default_part" conflicts with existing default partition "part_default"
 -- syntax does not allow empty list of values for list partitions
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
 ERROR:  syntax error at or near ")"
@@ -514,6 +517,9 @@ 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);
@@ -565,10 +571,15 @@ CREATE TABLE list_parted2 (
 ) PARTITION BY LIST (a);
 CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
 CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
 ERROR:  partition "fail_part" would overlap partition "part_null_z"
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
 ERROR:  partition "fail_part" would overlap partition "part_ab"
+-- check default partition overlap
+INSERT INTO list_parted2 VALUES('X');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y');
+ERROR:  new partition constraint for default partition "list_parted2_def" would be violated by some row
 CREATE TABLE range_parted2 (
 	a int
 ) PARTITION BY RANGE (a);
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 8b0752a..f9eef3f 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -202,6 +202,7 @@ create table list_parted (
 create table part_aa_bb partition of list_parted FOR VALUES IN ('aa', 'bb');
 create table part_cc_dd partition of list_parted FOR VALUES IN ('cc', 'dd');
 create table part_null partition of list_parted FOR VALUES IN (null);
+create table part_default partition of list_parted default;
 -- fail
 insert into part_aa_bb values ('cc', 1);
 ERROR:  new row for relation "part_aa_bb" violates partition constraint
@@ -212,24 +213,45 @@ DETAIL:  Failing row contains (AAa, 1).
 insert into part_aa_bb values (null);
 ERROR:  new row for relation "part_aa_bb" violates partition constraint
 DETAIL:  Failing row contains (null, null).
+insert into part_default values ('aa', 2);
+ERROR:  new row for relation "part_default" violates partition constraint
+DETAIL:  Failing row contains (aa, 2).
+insert into part_default values (null, 2);
+ERROR:  new row for relation "part_default" violates partition constraint
+DETAIL:  Failing row contains (null, 2).
 -- ok
 insert into part_cc_dd values ('cC', 1);
 insert into part_null values (null, 0);
+insert into part_default values ('Zz', 2);
 -- check in case of multi-level partitioned table
 create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b);
 create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
 create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
+-- check in case of multi-level default partitioned table
+drop table part_default;
+create table part_default partition of list_parted default partition by range(b);
+create table part_default_p1 partition of part_default for values from (20) to (30);
+create table part_default_p2 partition of part_default for values from (30) to (40);
 -- fail
 insert into part_ee_ff1 values ('EE', 11);
 ERROR:  new row for relation "part_ee_ff1" violates partition constraint
 DETAIL:  Failing row contains (EE, 11).
+insert into part_default_p2 values ('gg', 43);
+ERROR:  new row for relation "part_default_p2" violates partition constraint
+DETAIL:  Failing row contains (gg, 43).
 -- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
 insert into part_ee_ff1 values ('cc', 1);
 ERROR:  new row for relation "part_ee_ff1" violates partition constraint
 DETAIL:  Failing row contains (cc, 1).
+insert into part_default values ('gg', 43);
+ERROR:  no partition of relation "part_default" found for row
+DETAIL:  Partition key of the failing row contains (b) = (43).
 -- ok
 insert into part_ee_ff1 values ('ff', 1);
 insert into part_ee_ff2 values ('ff', 11);
+insert into part_default_p1 values ('cd', 25);
+insert into part_default_p2 values ('de', 35);
+insert into list_parted values ('ab', 21);
 -- Check tuple routing for partitioned tables
 -- fail
 insert into range_parted values ('a', 0);
@@ -274,17 +296,20 @@ DETAIL:  Partition key of the failing row contains (b) = (0).
 insert into list_parted values ('EE', 1);
 insert into part_ee_ff values ('EE', 10);
 select tableoid::regclass, * from list_parted;
-  tableoid   | a  | b  
--------------+----+----
- part_aa_bb  | aA |   
- part_cc_dd  | cC |  1
- part_null   |    |  0
- part_null   |    |  1
- part_ee_ff1 | ff |  1
- part_ee_ff1 | EE |  1
- part_ee_ff2 | ff | 11
- part_ee_ff2 | EE | 10
-(8 rows)
+    tableoid     | a  | b  
+-----------------+----+----
+ part_aa_bb      | aA |   
+ part_cc_dd      | cC |  1
+ part_null       |    |  0
+ part_null       |    |  1
+ part_ee_ff1     | ff |  1
+ part_ee_ff1     | EE |  1
+ part_ee_ff2     | ff | 11
+ part_ee_ff2     | EE | 10
+ part_default_p1 | cd | 25
+ part_default_p1 | ab | 21
+ part_default_p2 | de | 35
+(11 rows)
 
 -- some more tests to exercise tuple-routing with multi-level partitioning
 create table part_gg partition of list_parted for values in ('gg') partition by range (b);
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index 9366f04..9912ef2 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -218,5 +218,20 @@ 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;
+create table list_parted (
+	a text,
+	b int
+) partition by list (a);
+create table list_part1  partition of list_parted for values in ('a', 'b');
+create table list_default partition of list_parted default;
+insert into list_part1 values ('a', 1);
+insert into list_default values ('d', 10);
+-- fail
+update list_default set a = 'a' where a = 'd';
+ERROR:  new row for relation "list_default" violates partition constraint
+DETAIL:  Failing row contains (a, 10).
+-- ok
+update list_default set a = 'x' where a = 'd';
 -- cleanup
 drop table range_parted;
+drop table list_parted;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index c41b487..36c56aa 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2095,6 +2095,10 @@ SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::reg
 -- check that the new partition won't overlap with an existing partition
 CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+-- attaching default partition overlaps if the default partition already exists
+CREATE TABLE def_part PARTITION OF list_parted DEFAULT;
+CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT;
 
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
@@ -2111,6 +2115,15 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 
+-- check partition cannot be attached if default has some row for its values
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
+INSERT INTO list_parted2_def VALUES (11, 'z');
+CREATE TABLE part_3 (LIKE list_parted2);
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+-- should be ok after deleting the bad row
+DELETE FROM list_parted2_def WHERE a = 11;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
 CREATE TABLE part_3_4 (
@@ -2172,6 +2185,17 @@ DELETE FROM part_5_a WHERE a NOT IN (3);
 ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT NULL;
 ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
 
+-- check that leaf partitions of default partition are scanned when
+-- attaching a partitioned table.
+ALTER TABLE part_5 DROP CONSTRAINT check_a;
+CREATE TABLE part5_def PARTITION OF part_5 DEFAULT PARTITION BY LIST(a);
+CREATE TABLE part5_def_p1 PARTITION OF part5_def FOR VALUES IN (5);
+INSERT INTO part5_def_p1 VALUES (5, 'y');
+CREATE TABLE part5_p1 (LIKE part_5);
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+-- should be ok after deleting the bad row
+DELETE FROM part5_def_p1 WHERE b = 'y';
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
 
 -- check that the table being attached is not already a partition
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index cb7aa5b..f44c0e0 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -439,8 +439,10 @@ CREATE TABLE list_parted (
 CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
 CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
 CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
 
 -- syntax does not allow empty list of values for list partitions
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
@@ -484,6 +486,8 @@ 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);
@@ -529,9 +533,13 @@ CREATE TABLE list_parted2 (
 ) PARTITION BY LIST (a);
 CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
 CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+-- check default partition overlap
+INSERT INTO list_parted2 VALUES('X');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y');
 
 CREATE TABLE range_parted2 (
 	a int
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index db8967b..c120713 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -118,27 +118,41 @@ create table list_parted (
 create table part_aa_bb partition of list_parted FOR VALUES IN ('aa', 'bb');
 create table part_cc_dd partition of list_parted FOR VALUES IN ('cc', 'dd');
 create table part_null partition of list_parted FOR VALUES IN (null);
+create table part_default partition of list_parted default;
 
 -- fail
 insert into part_aa_bb values ('cc', 1);
 insert into part_aa_bb values ('AAa', 1);
 insert into part_aa_bb values (null);
+insert into part_default values ('aa', 2);
+insert into part_default values (null, 2);
 -- ok
 insert into part_cc_dd values ('cC', 1);
 insert into part_null values (null, 0);
+insert into part_default values ('Zz', 2);
 
 -- check in case of multi-level partitioned table
 create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b);
 create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
 create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
+-- check in case of multi-level default partitioned table
+drop table part_default;
+create table part_default partition of list_parted default partition by range(b);
+create table part_default_p1 partition of part_default for values from (20) to (30);
+create table part_default_p2 partition of part_default for values from (30) to (40);
 
 -- fail
 insert into part_ee_ff1 values ('EE', 11);
+insert into part_default_p2 values ('gg', 43);
 -- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
 insert into part_ee_ff1 values ('cc', 1);
+insert into part_default values ('gg', 43);
 -- ok
 insert into part_ee_ff1 values ('ff', 1);
 insert into part_ee_ff2 values ('ff', 11);
+insert into part_default_p1 values ('cd', 25);
+insert into part_default_p2 values ('de', 35);
+insert into list_parted values ('ab', 21);
 
 -- Check tuple routing for partitioned tables
 
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 6637119..44fb0dc 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -125,5 +125,20 @@ update range_parted set b = b - 1 where b = 10;
 -- ok
 update range_parted set b = b + 1 where b = 10;
 
+create table list_parted (
+	a text,
+	b int
+) partition by list (a);
+create table list_part1  partition of list_parted for values in ('a', 'b');
+create table list_default partition of list_parted default;
+insert into list_part1 values ('a', 1);
+insert into list_default values ('d', 10);
+
+-- fail
+update list_default set a = 'a' where a = 'd';
+-- ok
+update list_default set a = 'x' where a = 'd';
+
 -- cleanup
 drop table range_parted;
+drop table list_parted;
#118amul sul
sulamul@gmail.com
In reply to: Jeevan Ladhe (#117)
Re: Adding support for Default partition in partitioning

On Wed, Jun 7, 2017 at 2:08 AM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:
[...]

The code in check_default_allows_bound() to check whether the default
partition
has any rows that would fit new partition looks quite similar to the code
in
ATExecAttachPartition() checking whether all rows in the table being
attached
as a partition fit the partition bounds. One thing that
check_default_allows_bound() misses is, if there's already a constraint on
the
default partition refutes the partition constraint on the new partition,
we can
skip the scan of the default partition since it can not have rows that
would
fit the new partition. ATExecAttachPartition() has code to deal with a
similar
case i.e. the table being attached has a constraint which implies the
partition
constraint. There may be more cases which check_default_allows_bound()
does not
handle but ATExecAttachPartition() handles. So, I am wondering whether
it's
better to somehow take out the common code into a function and use it. We
will
have to deal with a difference through. The first one would throw an error
when
finding a row that satisfies partition constraints whereas the second one
would
throw an error when it doesn't find such a row. But this difference can be
handled through a flag or by negating the constraint. This would also take
care
of Amit Langote's complaint about foreign partitions. There's also another
difference that the ATExecAttachPartition() queues the table for scan and
the
actual scan takes place in ATRewriteTable(), but there is not such queue
while
creating a table as a partition. But we should check if we can reuse the
code to
scan the heap for checking a constraint.

In case of ATTACH PARTITION, probably we should schedule scan of default
partition in the alter table's work queue like what
ATExecAttachPartition() is
doing for the table being attached. That would fit in the way alter table
works.

I am still working on this.
But, about your comment here:
"if there's already a constraint on the default partition refutes the
partition
constraint on the new partition, we can skip the scan":
I am so far not able to imagine such a case, since default partition
constraint
can be imagined something like "minus infinity to positive infinity with
some finite set elimination", and any new non-default partition being added
would simply be a set of finite values(at-least in case of list, but I think
range
should not also differ here). Hence one cannot imply the other here.
Possibly,
I might be missing something that you had visioned when you raised the flag,
please correct me if I am missing something.

IIUC, default partition constraints is simply NOT IN (<values of all
other sibling partitions>).
If constraint on the default partition refutes the new partition's
constraints that means we have overlapping partition, and perhaps
error.

Regards,
Amul

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#119Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: amul sul (#118)
Re: Adding support for Default partition in partitioning

IIUC, default partition constraints is simply NOT IN (<values of all

other sibling partitions>).
If constraint on the default partition refutes the new partition's
constraints that means we have overlapping partition, and perhaps
error.

You are correct Amul, but this error will be thrown before we try to
check for the default partition data. So, in such cases I think we really
do not need to have logic to check if default partition refutes the new
partition contraints.

Regards,
Jeevan Ladhe

#120amul sul
sulamul@gmail.com
In reply to: Jeevan Ladhe (#119)
Re: Adding support for Default partition in partitioning

On Wed, Jun 7, 2017 at 10:30 AM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

IIUC, default partition constraints is simply NOT IN (<values of all
other sibling partitions>).
If constraint on the default partition refutes the new partition's
constraints that means we have overlapping partition, and perhaps
error.

You are correct Amul, but this error will be thrown before we try to
check for the default partition data. So, in such cases I think we really
do not need to have logic to check if default partition refutes the new
partition contraints.

But Ashutosh's suggestion make sense, we might have constraints other
than that partitioning constraint on default partition. If those
constraints refutes the new partition's constraints, we should skip
the scan.

Regards,
Amul

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#121Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Jeevan Ladhe (#117)
Re: Adding support for Default partition in partitioning

On Wed, Jun 7, 2017 at 2:08 AM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

This also means that we have to test PREPARED statements involving
default partition. Any addition/deletion/attach/detach of other partition
should invalidate those cached statements.

Will add this in next version of patch.

My earlier statement requires a clarification. By "PREPARED statements
involving default partition." I mean PREPAREd statements with direct
access to the default partition, without going through the partitioned
table.

The code in check_default_allows_bound() to check whether the default
partition
has any rows that would fit new partition looks quite similar to the code
in
ATExecAttachPartition() checking whether all rows in the table being
attached
as a partition fit the partition bounds. One thing that
check_default_allows_bound() misses is, if there's already a constraint on
the
default partition refutes the partition constraint on the new partition,
we can
skip the scan of the default partition since it can not have rows that
would
fit the new partition. ATExecAttachPartition() has code to deal with a
similar
case i.e. the table being attached has a constraint which implies the
partition
constraint. There may be more cases which check_default_allows_bound()
does not
handle but ATExecAttachPartition() handles. So, I am wondering whether
it's
better to somehow take out the common code into a function and use it. We
will
have to deal with a difference through. The first one would throw an error
when
finding a row that satisfies partition constraints whereas the second one
would
throw an error when it doesn't find such a row. But this difference can be
handled through a flag or by negating the constraint. This would also take
care
of Amit Langote's complaint about foreign partitions. There's also another
difference that the ATExecAttachPartition() queues the table for scan and
the
actual scan takes place in ATRewriteTable(), but there is not such queue
while
creating a table as a partition. But we should check if we can reuse the
code to
scan the heap for checking a constraint.

In case of ATTACH PARTITION, probably we should schedule scan of default
partition in the alter table's work queue like what
ATExecAttachPartition() is
doing for the table being attached. That would fit in the way alter table
works.

I am still working on this.
But, about your comment here:
"if there's already a constraint on the default partition refutes the
partition
constraint on the new partition, we can skip the scan":
I am so far not able to imagine such a case, since default partition
constraint
can be imagined something like "minus infinity to positive infinity with
some finite set elimination", and any new non-default partition being added
would simply be a set of finite values(at-least in case of list, but I think
range
should not also differ here). Hence one cannot imply the other here.
Possibly,
I might be missing something that you had visioned when you raised the flag,
please correct me if I am missing something.

I am hoping that this has been clarified in other mails in this thread
between you and Amul.

/* Generate the main expression, i.e., keyCol = ANY (arr) */
opexpr = make_partition_op_expr(key, 0, BTEqualStrategyNumber,
-                                    keyCol, (Expr *) arr);
+                                    keyCol, (Expr *) arr,
spec->is_default);
/* Build leftop = ANY (rightop) */
saopexpr = makeNode(ScalarArrayOpExpr);
The comments in both the places need correction, as for default partition
the
expression will be keyCol <> ALL(arr).

Done.

Please note that this changes, if you construct the constraint as
!(keycol = ANY[]).

We have RelationGetPartitionDesc() for
that. Probably we should also add Asserts to check that every pointer in
the
long pointer chain is Non-null.

I am sorry, but I did not understand which chain you are trying to point
here.

The chain of pointers: a->b->c->d is a chain of pointers.

@@ -2044,7 +2044,7 @@ psql_completion(const char *text, int start, int
end)
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
/* Limited completion support for partition bound specification */
else if (TailMatches3("ATTACH", "PARTITION", MatchAny))
-        COMPLETE_WITH_CONST("FOR VALUES");
+        COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
else if (TailMatches2("FOR", "VALUES"))
COMPLETE_WITH_LIST2("FROM (", "IN (");
@@ -2483,7 +2483,7 @@ psql_completion(const char *text, int start, int
end)
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_tables,
"");
/* Limited completion support for partition bound specification */
else if (TailMatches3("PARTITION", "OF", MatchAny))
-        COMPLETE_WITH_CONST("FOR VALUES");
+        COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
Do we include psql tab completion in the main feature patch? I have not
seen
this earlier. But appreciate taking care of these defails.

I am not sure about this. If needed I can submit a patch to take care of
this later, but
as of now I have not removed this from the patch.

I looked at Amul's patch. He has tab completion changes for HASH
partitions and those were suggested by Robert. So, keep those changes
in this patch. Sorry for misunderstanding on my part.

--
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

#122Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Robert Haas (#111)
Re: Adding support for Default partition in partitioning

On Sat, Jun 3, 2017 at 2:11 AM, Robert Haas <robertmhaas@gmail.com> wrote:

+ errmsg("default partition contains row(s)
that would overlap with partition being created")));

It doesn't really sound right to talk about rows overlapping with a
partition. Partitions can overlap with each other, but not rows.
Also, it's not really project style to use ambiguously plural forms
like "row(s)" in error messages. Maybe something like:

new partition constraint for default partition \"%s\" would be
violated by some row

Partition constraint is implementation detail here. We enforce
partition bounds through constraints and we call such constraints as
partition constraints. But a user may not necessarily understand this
term or may interpret it different. Adding "new" adds to the confusion
as the default partition is not new. My suggestion in an earlier mail
was ""default partition contains rows that conflict with the partition
bounds of "part_xyz"", with a note that we should use a better word
than "conflict". So, Jeevan seems to have used overlap, which again is
not correct. How about "default partition contains row/s which would
fit the partition "part_xyz" being created or attached." with a hint
to move those rows to the new partition's table in case of attach. I
don't think hint would be so straight forward i.e. to create the table
with SELECT INTO and then ATTACH.

What do you think?

Also, the error code ERRCODE_CHECK_VIOLATION, which is an "integrity
constraint violation" code, seems misleading. We aren't violating any
integrity here. In fact I am not able to understand, how could adding
an object violate integrity constraint. The nearest errorcode seems to
be ERRCODE_INVALID_OBJECT_DEFINITION, which is also used for
partitions with overlapping bounds.

--
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

#123Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Jeevan Ladhe (#117)
1 attachment(s)
Re: Adding support for Default partition in partitioning

On Wed, Jun 7, 2017 at 2:08 AM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

The code in check_default_allows_bound() to check whether the default
partition
has any rows that would fit new partition looks quite similar to the code
in
ATExecAttachPartition() checking whether all rows in the table being
attached
as a partition fit the partition bounds. One thing that
check_default_allows_bound() misses is, if there's already a constraint on
the
default partition refutes the partition constraint on the new partition,
we can
skip the scan of the default partition since it can not have rows that
would
fit the new partition. ATExecAttachPartition() has code to deal with a
similar
case i.e. the table being attached has a constraint which implies the
partition
constraint. There may be more cases which check_default_allows_bound()
does not
handle but ATExecAttachPartition() handles. So, I am wondering whether
it's
better to somehow take out the common code into a function and use it. We
will
have to deal with a difference through. The first one would throw an error
when
finding a row that satisfies partition constraints whereas the second one
would
throw an error when it doesn't find such a row. But this difference can be
handled through a flag or by negating the constraint. This would also take
care
of Amit Langote's complaint about foreign partitions. There's also another
difference that the ATExecAttachPartition() queues the table for scan and
the
actual scan takes place in ATRewriteTable(), but there is not such queue
while
creating a table as a partition. But we should check if we can reuse the
code to
scan the heap for checking a constraint.

In case of ATTACH PARTITION, probably we should schedule scan of default
partition in the alter table's work queue like what
ATExecAttachPartition() is
doing for the table being attached. That would fit in the way alter table
works.

I tried refactoring existing code so that it can be used for default
partitioning as well. The code to validate the partition constraints
against the table being attached in ATExecAttachPartition() is
extracted out into a set of functions. For default partition we reuse
those functions to check whether it contains any row that would fit
the partition being attached. While creating a new partition, the
function to skip validation is reused but the scan portion is
duplicated from ATRewriteTable since we are not in ALTER TABLE
context. The names of the functions, their declaration will require
some thought.

There's one test failing because for ATTACH partition the error comes
from ATRewriteTable instead of check_default_allows_bounds(). May be
we want to use same message in both places or some make ATRewriteTable
give a different message while validating default partition.

Please review the patch and let me know if the changes look good.

Attachments:

default_partition_refactor_v20.tar.gzapplication/x-gzip; name=default_partition_refactor_v20.tar.gzDownload
#124Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Ashutosh Bapat (#123)
Re: Adding support for Default partition in partitioning

On Thu, Jun 8, 2017 at 2:54 PM, Ashutosh Bapat
<ashutosh.bapat@enterprisedb.com> wrote:

On Wed, Jun 7, 2017 at 2:08 AM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

The code in check_default_allows_bound() to check whether the default
partition
has any rows that would fit new partition looks quite similar to the code
in
ATExecAttachPartition() checking whether all rows in the table being
attached
as a partition fit the partition bounds. One thing that
check_default_allows_bound() misses is, if there's already a constraint on
the
default partition refutes the partition constraint on the new partition,
we can
skip the scan of the default partition since it can not have rows that
would
fit the new partition. ATExecAttachPartition() has code to deal with a
similar
case i.e. the table being attached has a constraint which implies the
partition
constraint. There may be more cases which check_default_allows_bound()
does not
handle but ATExecAttachPartition() handles. So, I am wondering whether
it's
better to somehow take out the common code into a function and use it. We
will
have to deal with a difference through. The first one would throw an error
when
finding a row that satisfies partition constraints whereas the second one
would
throw an error when it doesn't find such a row. But this difference can be
handled through a flag or by negating the constraint. This would also take
care
of Amit Langote's complaint about foreign partitions. There's also another
difference that the ATExecAttachPartition() queues the table for scan and
the
actual scan takes place in ATRewriteTable(), but there is not such queue
while
creating a table as a partition. But we should check if we can reuse the
code to
scan the heap for checking a constraint.

In case of ATTACH PARTITION, probably we should schedule scan of default
partition in the alter table's work queue like what
ATExecAttachPartition() is
doing for the table being attached. That would fit in the way alter table
works.

I tried refactoring existing code so that it can be used for default
partitioning as well. The code to validate the partition constraints
against the table being attached in ATExecAttachPartition() is
extracted out into a set of functions. For default partition we reuse
those functions to check whether it contains any row that would fit
the partition being attached. While creating a new partition, the
function to skip validation is reused but the scan portion is
duplicated from ATRewriteTable since we are not in ALTER TABLE
context. The names of the functions, their declaration will require
some thought.

There's one test failing because for ATTACH partition the error comes
from ATRewriteTable instead of check_default_allows_bounds(). May be
we want to use same message in both places or some make ATRewriteTable
give a different message while validating default partition.

Please review the patch and let me know if the changes look good.

From the discussion on thread [1]http://www.postgresql-archive.org/A-bug-in-mapping-attributes-in-ATExecAttachPartition-td5965298.html, that having a NOT NULL constraint
embedded within an expression may cause a scan to be skipped when it
shouldn't be. For default partitions such a case may arise. If an
existing partition accepts NULL and we try to attach a default
partition, it would get a NOT NULL partition constraint but it will be
buried within an expression like !(key = any(array[1, 2, 3]) OR key is
null) where the existing partition/s accept values 1, 2, 3 and null.
We need to check whether the refactored code handles this case
correctly. v19 patch does not have this problem since it doesn't try
to skip the scan based on the constraints of the table being attached.
Please try following cases 1. a default partition accepting nulls
exists and we attach a partition to accept NULL values 2. a NULL
accepting partition exists and we try to attach a table as default
partition. In both the cases default partition should be checked for
rows with NULL partition keys. In both the cases, if the default
partition table has a NOT NULL constraint we should be able to skip
the scan and should scan the table when such a constraint does not
exist.

[1]: http://www.postgresql-archive.org/A-bug-in-mapping-attributes-in-ATExecAttachPartition-td5965298.html

--
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

#125Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Ashutosh Bapat (#124)
Re: Adding support for Default partition in partitioning

Thanks Ashutosh,

On Thu, Jun 8, 2017 at 4:04 PM, Ashutosh Bapat <
ashutosh.bapat@enterprisedb.com> wrote:

On Thu, Jun 8, 2017 at 2:54 PM, Ashutosh Bapat
<ashutosh.bapat@enterprisedb.com> wrote:

On Wed, Jun 7, 2017 at 2:08 AM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

The code in check_default_allows_bound() to check whether the default
partition
has any rows that would fit new partition looks quite similar to the

code

in
ATExecAttachPartition() checking whether all rows in the table being
attached
as a partition fit the partition bounds. One thing that
check_default_allows_bound() misses is, if there's already a

constraint on

the
default partition refutes the partition constraint on the new

partition,

we can
skip the scan of the default partition since it can not have rows that
would
fit the new partition. ATExecAttachPartition() has code to deal with a
similar
case i.e. the table being attached has a constraint which implies the
partition
constraint. There may be more cases which check_default_allows_bound()
does not
handle but ATExecAttachPartition() handles. So, I am wondering whether
it's
better to somehow take out the common code into a function and use it.

We

will
have to deal with a difference through. The first one would throw an

error

when
finding a row that satisfies partition constraints whereas the second

one

would
throw an error when it doesn't find such a row. But this difference

can be

handled through a flag or by negating the constraint. This would also

take

care
of Amit Langote's complaint about foreign partitions. There's also

another

difference that the ATExecAttachPartition() queues the table for scan

and

the
actual scan takes place in ATRewriteTable(), but there is not such

queue

while
creating a table as a partition. But we should check if we can reuse

the

code to
scan the heap for checking a constraint.

In case of ATTACH PARTITION, probably we should schedule scan of

default

partition in the alter table's work queue like what
ATExecAttachPartition() is
doing for the table being attached. That would fit in the way alter

table

works.

I tried refactoring existing code so that it can be used for default
partitioning as well. The code to validate the partition constraints
against the table being attached in ATExecAttachPartition() is
extracted out into a set of functions. For default partition we reuse
those functions to check whether it contains any row that would fit
the partition being attached. While creating a new partition, the
function to skip validation is reused but the scan portion is
duplicated from ATRewriteTable since we are not in ALTER TABLE
context. The names of the functions, their declaration will require
some thought.

There's one test failing because for ATTACH partition the error comes
from ATRewriteTable instead of check_default_allows_bounds(). May be
we want to use same message in both places or some make ATRewriteTable
give a different message while validating default partition.

Please review the patch and let me know if the changes look good.

From the discussion on thread [1], that having a NOT NULL constraint
embedded within an expression may cause a scan to be skipped when it
shouldn't be. For default partitions such a case may arise. If an
existing partition accepts NULL and we try to attach a default
partition, it would get a NOT NULL partition constraint but it will be
buried within an expression like !(key = any(array[1, 2, 3]) OR key is
null) where the existing partition/s accept values 1, 2, 3 and null.
We need to check whether the refactored code handles this case
correctly. v19 patch does not have this problem since it doesn't try
to skip the scan based on the constraints of the table being attached.
Please try following cases 1. a default partition accepting nulls
exists and we attach a partition to accept NULL values 2. a NULL
accepting partition exists and we try to attach a table as default
partition. In both the cases default partition should be checked for
rows with NULL partition keys. In both the cases, if the default
partition table has a NOT NULL constraint we should be able to skip
the scan and should scan the table when such a constraint does not
exist.

I will review your refactoring patch as well test above cases.

Regards,
Jeevan Ladhe

#126Robert Haas
robertmhaas@gmail.com
In reply to: amul sul (#120)
Re: Adding support for Default partition in partitioning

On Wed, Jun 7, 2017 at 1:59 AM, amul sul <sulamul@gmail.com> wrote:

But Ashutosh's suggestion make sense, we might have constraints other
than that partitioning constraint on default partition. If those
constraints refutes the new partition's constraints, we should skip
the scan.

Right. If the user adds a constraint to the default partition that is
identical to the new partition constraint, that should cause the scan
to be skipped.

Ideally, we could do even better. For example, if the user is
creating a new partition FOR VALUES IN (7), and the default partition
has CHECK (key != 7), we could perhaps deduce that the combination of
the existing partition constraint (which must certainly hold) and the
additional CHECK constraint (which must also hold, at least assuming
it's not marked NOT VALID) are sufficient to prove the new check
constraint. But I'm not sure whether predicate_refuted_by() is smart
enough to figure that out. However, it should definitely be smart
enough to figure out that if somebody's added the new partitioning
constraint as a CHECK constraint on the default partition, we don't
need to scan it.

The reason somebody might want to do that, just to be clear, is that
they could do this in multiple steps: first, add the new CHECK
constraint as NOT VALID. Then VALIDATE CONSTRAINT. Then add the new
non-default partition. This would result in holding an exclusive lock
for a lesser period of time than if they did it all together as one
operation.

--
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

#127Robert Haas
robertmhaas@gmail.com
In reply to: Ashutosh Bapat (#122)
Re: Adding support for Default partition in partitioning

On Wed, Jun 7, 2017 at 5:47 AM, Ashutosh Bapat
<ashutosh.bapat@enterprisedb.com> wrote:

On Sat, Jun 3, 2017 at 2:11 AM, Robert Haas <robertmhaas@gmail.com> wrote:

+ errmsg("default partition contains row(s)
that would overlap with partition being created")));

It doesn't really sound right to talk about rows overlapping with a
partition. Partitions can overlap with each other, but not rows.
Also, it's not really project style to use ambiguously plural forms
like "row(s)" in error messages. Maybe something like:

new partition constraint for default partition \"%s\" would be
violated by some row

Partition constraint is implementation detail here. We enforce
partition bounds through constraints and we call such constraints as
partition constraints. But a user may not necessarily understand this
term or may interpret it different. Adding "new" adds to the confusion
as the default partition is not new.

I see your point. We could say "updated partition constraint" instead
of "new partition constraint" to address that to some degree.

My suggestion in an earlier mail
was ""default partition contains rows that conflict with the partition
bounds of "part_xyz"", with a note that we should use a better word
than "conflict". So, Jeevan seems to have used overlap, which again is
not correct. How about "default partition contains row/s which would
fit the partition "part_xyz" being created or attached." with a hint
to move those rows to the new partition's table in case of attach. I
don't think hint would be so straight forward i.e. to create the table
with SELECT INTO and then ATTACH.

The problem is that none of these actually sound very good. Neither
conflict nor overlap nor fit actually express the underlying idea very
clearly, at least IMHO. I'm not opposed to using some wording along
these lines if we can think of a clear way to word it, but I think my
wording is better than using some unclear word for this concept. I
can't immediately think of a way to adjust your wording so that it
seems completely clear.

Also, the error code ERRCODE_CHECK_VIOLATION, which is an "integrity
constraint violation" code, seems misleading. We aren't violating any
integrity here. In fact I am not able to understand, how could adding
an object violate integrity constraint. The nearest errorcode seems to
be ERRCODE_INVALID_OBJECT_DEFINITION, which is also used for
partitions with overlapping bounds.

I think that calling a constraint failure a check violation is not too
much of a stretch, even if it's technically a partition constraint
rather than a CHECK constraint. However, your proposal also seems
reasonable. I'm happy to go with whatever most people like best.

--
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

#128Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Ashutosh Bapat (#123)
1 attachment(s)
Re: Adding support for Default partition in partitioning

Hi Ashutosh,

I tried to look into your refactoring code.
When applied all 3 patches, I got some regression failures, I have fixed
all of
them now in attached patches, attached the regression.diffs.

Moving further, I have also made following changes in attached patches:

*1. 0001-Refactor-ATExecAttachPartition.patch*

+ * There is a case in which we cannot rely on just the result of the
+ * proof
This comment seems to also exist in current code, and I am not able to
follow
which case this refers to. But, IIUC, this comment is for the case where we
are
handling the 'key IS NOT NULL' part separately, and if that is the case it
is
not needed here in the prologue of the function.
attachPartCanSkipValidation
+static bool
+ATCheckValidationSkippable(Relation scanRel, List *partConstraint,
+                          PartitionKey key)
The function name ATCheckValidationSkippable does not sound very intuitive
to me,
and also I think prefix AT is something does not fit here as the function
is not
really directly related to alter table command, instead is an auxiliary
function.
How about changing it to "attachPartitionRequiresScan" or
"canSkipPartConstraintValidation"

+ List *existConstraint = NIL;
Needs to be moved to inside if block instead.

+ bool skip_validate;
Needs to be initialized to false, otherwise it can be returned without
initialization when scanRel_constr is NULL.

+   if (scanRel_constr != NULL)
instead of this may be we can simply have:
+   if (scanRel_constr == NULL)
+ return false;
This can prevent further indentation.
+static void
+ATValidatePartitionConstraints(List **wqueue, Relation scanRel,
+                              List *partConstraint, Relation rel)
What about just validatePartitionConstraints()
+   bool        skip_validate = false;
+
+   /* Check if we can do away with having to scan the table being
attached. */
+   skip_validate = ATCheckValidationSkippable(scanRel, partConstraint,
key);

First assignment is unnecessary here.

Instead of:
/* Check if we can do away with having to scan the table being attached. */
skip_validate = ATCheckValidationSkippable(scanRel, partConstraint, key);

/* It's safe to skip the validation scan after all */
if (skip_validate)
ereport(INFO,
(errmsg("partition constraint for table \"%s\" is implied by existing
constraints",
RelationGetRelationName(scanRel))));

Following change can prevent further indentation:
if (ATCheckValidationSkippable(scanRel, partConstraint, key))
{
ereport(INFO,
(errmsg("partition constraint for table \"%s\" is implied by existing
constraints",
RelationGetRelationName(scanRel))));
return;
}
This way variable skip_validate will not be needed.

Apart from this, I see that the patch will need change depending on how the
fix
for validating partition constraints in case of embedded NOT-NULL[1]http://www.postgresql-archive.org/A-bug-in-mapping-attributes-in-ATExecAttachPartition-td5965298.html shapes
up.

*2. 0003-Refactor-default-partitioning-patch-to-re-used-code.patch*

+ * In case the new partition bound being checked itself is a DEFAULT
+ * bound, this check shouldn't be triggered as there won't already exists
+ * the default partition in such a case.
I think above comment in DefineRelation() is not applicable, as
check_default_allows_bound() is called unconditional, and the check for
existence
of default partition is now done inside the check_default_allows_bound()
function.

* This function checks if there exists a row in the default partition that
* fits in the new partition and throws an error if it finds one.
*/
Above comment for check_default_allows_bound() needs a change now, may be
something like this:
* This function checks if a default partition already exists and if it
does
* it checks if there exists a row in the default partition that fits in
the
* new partition and throws an error if it finds one.
*/

List *new_part_constraints = NIL;
List *def_part_constraints = NIL;
I think above initialization is not needed, as the further assignments are
unconditional.

+ if (OidIsValid(default_oid))
+ {
+ Relation default_rel = heap_open(default_oid, AccessExclusiveLock);
We already have taken a lock on default and here we should be using a NoLock
instead.

+ def_part_constraints =
get_default_part_validation_constraint(new_part_constraints);
exceeds 80 columns.

+ defPartConstraint =
get_default_part_validation_constraint(partBoundConstraint);
similarly, needs indentation.

+
+List *
+get_default_part_validation_constraint(List *new_part_constraints)
+{
Needs some comment. What about:
/*
 * get_default_part_validation_constraint
 *
 * Given partition constraints, this function returns *would be* default
 * partition constraint.
 */

Apart from this, I tried to address the differences in error shown in case
of
attache and create partition when rows in default partition would violate
the
updated constraints, basically I have taken a flag in AlteredTableInfo to
indicate if the relation being scanned is a default partition or a child of
default partition(which I dint like much, but I don't see a way out here).
Still
the error message does not display the default partition name in error as of
check_default_allows_bound(). May be to address this and keep the messages
exactly similar we can copy the name of parent default partition in a field
in
AlteredTableInfo structure, which looks very ugly to me. I am open to
suggestions here.

*3. changes to default_partition_v19.patch:*

The default partition constraint are no more built using the negator of the
operator, instead it is formed simply as NOT of the existing partitions:
e.g.:
if a null accepting partition already exists:
NOT ((keycol IS NULL) OR (keycol = ANY (arr)))
if a null accepting partition does not exists:
NOT ((keycol IS NOT NULL) AND (keycol = ANY (arr))), where arr is an array
of
datums in boundinfo->datums.

Added tests for prepared statment.

Renamed RelationGetDefaultPartitionOid() to get_default_partition_oid().

+ if (partqualstate && ExecCheck(partqualstate, econtext))
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("new partition constraint for default partition \"%s\" would be
violated by some row",
+   RelationGetRelationName(default_rel))));
Per Ashutosh's suggestion[2], changed the error code to
ERRCODE_INVALID_OBJECT_DEFINITION.
Also, per Robert's suggestion[3], changed following message:
"new partition constraint for default partition \"%s\" would be violated by
some row"
to
"updated partition constraint for default partition \"%s\" would be
violated by some row"

Some other cosmetic changes.

Apart from this, I am exploring the tests in relation with NOT NULL
constraint
embedded within an expression. Will update on that shortly.

[1]: http://www.postgresql-archive.org/A-bug-in-mapping-attributes-in-ATExecAttachPartition-td5965298.html
http://www.postgresql-archive.org/A-bug-in-mapping-attributes-in-ATExecAttachPartition-td5965298.html
[2]: http://www.postgresql-archive.org/Adding-support-for-Default-partition-in-partitioning-td5946868i120.html#a5965277
http://www.postgresql-archive.org/Adding-support-for-Default-partition-in-partitioning-td5946868i120.html#a5965277
[3]: http://www.postgresql-archive.org/Adding-support-for-Default-partition-in-partitioning-tp5946868p5965599.html
http://www.postgresql-archive.org/Adding-support-for-Default-partition-in-partitioning-tp5946868p5965599.html

Regards,
Jeevan Ladhe

On Thu, Jun 8, 2017 at 2:54 PM, Ashutosh Bapat <
ashutosh.bapat@enterprisedb.com> wrote:

Show quoted text

On Wed, Jun 7, 2017 at 2:08 AM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

The code in check_default_allows_bound() to check whether the default
partition
has any rows that would fit new partition looks quite similar to the

code

in
ATExecAttachPartition() checking whether all rows in the table being
attached
as a partition fit the partition bounds. One thing that
check_default_allows_bound() misses is, if there's already a constraint

on

the
default partition refutes the partition constraint on the new partition,
we can
skip the scan of the default partition since it can not have rows that
would
fit the new partition. ATExecAttachPartition() has code to deal with a
similar
case i.e. the table being attached has a constraint which implies the
partition
constraint. There may be more cases which check_default_allows_bound()
does not
handle but ATExecAttachPartition() handles. So, I am wondering whether
it's
better to somehow take out the common code into a function and use it.

We

will
have to deal with a difference through. The first one would throw an

error

when
finding a row that satisfies partition constraints whereas the second

one

would
throw an error when it doesn't find such a row. But this difference can

be

handled through a flag or by negating the constraint. This would also

take

care
of Amit Langote's complaint about foreign partitions. There's also

another

difference that the ATExecAttachPartition() queues the table for scan

and

the
actual scan takes place in ATRewriteTable(), but there is not such queue
while
creating a table as a partition. But we should check if we can reuse the
code to
scan the heap for checking a constraint.

In case of ATTACH PARTITION, probably we should schedule scan of default
partition in the alter table's work queue like what
ATExecAttachPartition() is
doing for the table being attached. That would fit in the way alter

table

works.

I tried refactoring existing code so that it can be used for default
partitioning as well. The code to validate the partition constraints
against the table being attached in ATExecAttachPartition() is
extracted out into a set of functions. For default partition we reuse
those functions to check whether it contains any row that would fit
the partition being attached. While creating a new partition, the
function to skip validation is reused but the scan portion is
duplicated from ATRewriteTable since we are not in ALTER TABLE
context. The names of the functions, their declaration will require
some thought.

There's one test failing because for ATTACH partition the error comes
from ATRewriteTable instead of check_default_allows_bounds(). May be
we want to use same message in both places or some make ATRewriteTable
give a different message while validating default partition.

Please review the patch and let me know if the changes look good.

Attachments:

default_partition_v20_wip.patchapplication/octet-stream; name=default_partition_v20_wip.patchDownload
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 4e5b79e..a01d8a0 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1755,9 +1755,11 @@ RemoveAttrDefaultById(Oid attrdefId)
 void
 heap_drop_with_catalog(Oid relid)
 {
-	Relation	rel;
+	Relation	rel,
+				parentRel;
 	HeapTuple	tuple;
-	Oid			parentOid = InvalidOid;
+	Oid			parentOid = InvalidOid,
+				defaultPartOid = InvalidOid;
 
 	/*
 	 * To drop a partition safely, we must grab exclusive lock on its parent,
@@ -1772,7 +1774,15 @@ heap_drop_with_catalog(Oid relid)
 	if (((Form_pg_class) GETSTRUCT(tuple))->relispartition)
 	{
 		parentOid = get_partition_parent(relid);
-		LockRelationOid(parentOid, AccessExclusiveLock);
+		parentRel = heap_open(parentOid, AccessExclusiveLock);
+
+		/*
+		 * Need to take a lock on the default partition, refer comment for
+		 * locking the default partition in DefineRelation().
+		 */
+		defaultPartOid = get_default_partition_oid(parentRel);
+		if (OidIsValid(defaultPartOid))
+			LockRelationOid(defaultPartOid, AccessExclusiveLock);
 	}
 
 	ReleaseSysCache(tuple);
@@ -1883,11 +1893,19 @@ heap_drop_with_catalog(Oid relid)
 	if (OidIsValid(parentOid))
 	{
 		/*
+		 * Invalidate default partition's relcache, refer comment for
+		 * invalidating default partition relcache in StorePartitionBound().
+		 */
+		if (OidIsValid(defaultPartOid))
+			CacheInvalidateRelcacheByRelid(defaultPartOid);
+
+		/*
 		 * Invalidate the parent's relcache so that the partition is no longer
 		 * included in its partition descriptor.
 		 */
 		CacheInvalidateRelcacheByRelid(parentOid);
 		/* keep the lock */
+		heap_close(parentRel, NoLock);
 	}
 }
 
@@ -3220,8 +3238,10 @@ RemovePartitionKeyByRelId(Oid relid)
  *		Update pg_class tuple of rel to store the partition bound and set
  *		relispartition to true
  *
- * Also, invalidate the parent's relcache, so that the next rebuild will load
- * the new partition's info into its partition descriptor.
+ * Also, invalidate the parent's and a sibling default partition's relcache,
+ * so that the next rebuild will load the new partition's info into parent's
+ * partition descriptor and default partition constraints(which are dependent
+ * on other partition bounds) are built anew.
  */
 void
 StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
@@ -3232,6 +3252,7 @@ StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
 	Datum		new_val[Natts_pg_class];
 	bool		new_null[Natts_pg_class],
 				new_repl[Natts_pg_class];
+	Oid			defaultPartOid;
 
 	/* Update pg_class tuple */
 	classRel = heap_open(RelationRelationId, RowExclusiveLock);
@@ -3269,5 +3290,16 @@ StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
 	heap_freetuple(newtuple);
 	heap_close(classRel, RowExclusiveLock);
 
+	/*
+	 * The default partition constraints depend upon the partition bounds of
+	 * other partitions. Adding a new(or even removing existing) partition
+	 * would invalidate the default partition constraints. Invalidate the
+	 * default partition's relcache so that the constraints are built anew and
+	 * any plans dependent on those constraints are invalidated as well.
+	 */
+	defaultPartOid = get_default_partition_oid(parent);
+	if (OidIsValid(defaultPartOid))
+		CacheInvalidateRelcacheByRelid(defaultPartOid);
+
 	CacheInvalidateRelcache(parent);
 }
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 5c5a9e1..01a2010 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -35,6 +35,7 @@
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planmain.h"
+#include "optimizer/prep.h"
 #include "optimizer/var.h"
 #include "rewrite/rewriteManip.h"
 #include "storage/lmgr.h"
@@ -88,9 +89,12 @@ typedef struct PartitionBoundInfoData
 								 * partitioned table) */
 	int			null_index;		/* Index of the null-accepting partition; -1
 								 * if there isn't one */
+	int			default_index;	/* Index of the default partition if any; -1
+								 * if there isn't one */
 } 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
@@ -128,7 +132,7 @@ static void get_range_key_properties(PartitionKey key, int keynum,
 						 ListCell **partexprs_item,
 						 Expr **keyCol,
 						 Const **lower_val, Const **upper_val);
-static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_list(Relation parent, PartitionBoundSpec *spec);
 static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
 static List *generate_partition_qual(Relation rel);
 
@@ -147,6 +151,8 @@ static int32 partition_bound_cmp(PartitionKey key,
 static int partition_bound_bsearch(PartitionKey key,
 						PartitionBoundInfo boundinfo,
 						void *probe, bool probe_is_bound, bool *is_equal);
+static void check_default_allows_bound(Relation parent,
+						   PartitionBoundSpec *new_spec);
 
 /*
  * RelationBuildPartitionDesc
@@ -174,6 +180,7 @@ RelationBuildPartitionDesc(Relation rel)
 	/* List partitioning specific */
 	PartitionListValue **all_values = NULL;
 	int			null_index = -1;
+	int			default_index = -1;
 
 	/* Range partitioning specific */
 	PartitionRangeBound **rbounds = NULL;
@@ -254,6 +261,18 @@ RelationBuildPartitionDesc(Relation rel)
 				if (spec->strategy != PARTITION_STRATEGY_LIST)
 					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 list of non-null
+				 * datums for this partition.
+				 */
+				if (spec->is_default)
+				{
+					default_index = i;
+					i++;
+					continue;
+				}
+
 				foreach(c, spec->listdatums)
 				{
 					Const	   *val = castNode(Const, lfirst(c));
@@ -453,6 +472,7 @@ RelationBuildPartitionDesc(Relation rel)
 		boundinfo = (PartitionBoundInfoData *)
 			palloc0(sizeof(PartitionBoundInfoData));
 		boundinfo->strategy = key->strategy;
+		boundinfo->default_index = -1;
 		boundinfo->ndatums = ndatums;
 		boundinfo->datums = (Datum **) palloc0(ndatums * sizeof(Datum *));
 
@@ -506,6 +526,20 @@ RelationBuildPartitionDesc(Relation rel)
 					else
 						boundinfo->null_index = -1;
 
+					/* Assign mapped index for the default partition. */
+					if (default_index != -1)
+					{
+						/*
+						 * The default partition accepts any non-specified
+						 * value, hence it should not get a mapped index while
+						 * assigning those for non-null datums.
+						 */
+						Assert(default_index >= 0 &&
+							   mapping[default_index] == -1);
+						mapping[default_index] = next_index++;
+						boundinfo->default_index = mapping[default_index];
+					}
+
 					/* All partition must now have a valid mapping */
 					Assert(next_index == nparts);
 					break;
@@ -610,6 +644,9 @@ partition_bounds_equal(PartitionKey key,
 	if (b1->null_index != b2->null_index)
 		return false;
 
+	if (b1->default_index != b2->default_index)
+		return false;
+
 	for (i = 0; i < b1->ndatums; i++)
 	{
 		int			j;
@@ -672,6 +709,7 @@ check_new_partition_bound(char *relname, Relation parent,
 {
 	PartitionKey key = RelationGetPartitionKey(parent);
 	PartitionDesc partdesc = RelationGetPartitionDesc(parent);
+	PartitionBoundInfo boundinfo = partdesc->boundinfo;
 	ParseState *pstate = make_parsestate(NULL);
 	int			with = -1;
 	bool		overlap = false;
@@ -684,13 +722,24 @@ check_new_partition_bound(char *relname, Relation parent,
 
 				if (partdesc->nparts > 0)
 				{
-					PartitionBoundInfo boundinfo = partdesc->boundinfo;
 					ListCell   *cell;
 
 					Assert(boundinfo &&
 						   boundinfo->strategy == PARTITION_STRATEGY_LIST &&
 						   (boundinfo->ndatums > 0 ||
-							partition_bound_accepts_nulls(boundinfo)));
+							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)
 					{
@@ -832,12 +881,151 @@ check_new_partition_bound(char *relname, Relation parent,
 	if (overlap)
 	{
 		Assert(with >= 0);
+
+		if (spec->is_default)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("partition \"%s\" conflicts with existing default partition \"%s\"",
+							relname, get_rel_name(partdesc->oids[with])),
+					 parser_errposition(pstate, spec->location)));
+
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 				 errmsg("partition \"%s\" would overlap partition \"%s\"",
 						relname, get_rel_name(partdesc->oids[with])),
 				 parser_errposition(pstate, spec->location)));
 	}
+
+	/*
+	 * If the default partition exists, its partition constraints will change
+	 * after the addition of this new partition such that it won't allow any
+	 * row that qualifies for this new partition. So, check if the existing
+	 * data in the default partition satisfies this *would be* default
+	 * partition constraint. In case the new partition bound being checked
+	 * itself is a DEFAULT bound, this check shouldn't be triggered as there
+	 * won't already exists the default partition in such a case.
+	 */
+	if (boundinfo && partition_bound_has_default(boundinfo))
+		check_default_allows_bound(parent, spec);
+}
+
+/*
+ * check_default_allows_bound
+ *
+ * This function checks if there exists a row in the default partition that
+ * fits in the new partition and throws an error if it finds one.
+ */
+static void
+check_default_allows_bound(Relation parent, PartitionBoundSpec *new_spec)
+{
+	Relation	default_rel;
+	List	   *new_part_constraints = NIL;
+	List	   *all_parts;
+	ListCell   *lc;
+	PartitionDescData *part_desc = RelationGetPartitionDesc(parent);
+
+	/* 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 = (List *) eval_const_expressions(NULL,
+											  (Node *) new_part_constraints);
+	new_part_constraints =
+		(List *) canonicalize_qual((Expr *) new_part_constraints);
+	new_part_constraints = list_make1(make_ands_explicit(new_part_constraints));
+
+	/*
+	 * Generate the constraint and default execution states.
+	 *
+	 * The default partition must be already having an AccessExclusiveLock.
+	 */
+	default_rel = heap_open(get_default_partition_oid(parent), NoLock);
+
+	if (default_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		all_parts = find_all_inheritors(RelationGetRelid(default_rel),
+										AccessExclusiveLock, NULL);
+	else
+		all_parts = list_make1_oid(RelationGetRelid(default_rel));
+
+	foreach(lc, all_parts)
+	{
+		Oid			part_relid = lfirst_oid(lc);
+		Relation	part_rel;
+		Expr	   *constr;
+		Expr	   *partition_constraint;
+		EState	   *estate;
+		HeapTuple	tuple;
+		ExprState  *partqualstate = NULL;
+		Snapshot	snapshot;
+		TupleDesc	tupdesc;
+		ExprContext *econtext;
+		HeapScanDesc scan;
+		MemoryContext oldCxt;
+		TupleTableSlot *tupslot;
+
+		/* Lock already taken above. */
+		if (part_relid != RelationGetRelid(default_rel))
+			part_rel = heap_open(part_relid, NoLock);
+		else
+			part_rel = default_rel;
+
+		/*
+		 * Skip if it's a partitioned table. Only RELKIND_RELATION relations
+		 * (ie, leaf partitions) need to be scanned.
+		 */
+		if (part_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+			part_rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+		{
+			heap_close(part_rel, NoLock);
+			continue;
+		}
+
+		tupdesc = CreateTupleDescCopy(RelationGetDescr(part_rel));
+		constr = linitial(new_part_constraints);
+		partition_constraint = (Expr *) map_partition_varattnos((List *) constr,
+														1, part_rel, parent);
+		estate = CreateExecutorState();
+
+		/* Build expression execution states for partition check quals */
+		partqualstate = ExecPrepareExpr(partition_constraint, estate);
+
+		econtext = GetPerTupleExprContext(estate);
+		snapshot = RegisterSnapshot(GetLatestSnapshot());
+		scan = heap_beginscan(part_rel, snapshot, 0, NULL);
+		tupslot = MakeSingleTupleTableSlot(tupdesc);
+
+		/*
+		 * Switch to per-tuple memory context and reset it for each tuple
+		 * produced, so we don't leak memory.
+		 */
+		oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		{
+			ExecStoreTuple(tuple, tupslot, InvalidBuffer, false);
+			econtext->ecxt_scantuple = tupslot;
+
+			if (partqualstate && ExecCheck(partqualstate, econtext))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						 errmsg("updated partition constraint for default partition \"%s\" would be violated by some row",
+								RelationGetRelationName(default_rel))));
+
+			ResetExprContext(econtext);
+			CHECK_FOR_INTERRUPTS();
+		}
+
+		MemoryContextSwitchTo(oldCxt);
+		heap_endscan(scan);
+		UnregisterSnapshot(snapshot);
+		ExecDropSingleTupleTableSlot(tupslot);
+		FreeExecutorState(estate);
+		/* keep our lock until commit */
+		heap_close(part_rel, NoLock);
+	}
 }
 
 /*
@@ -904,7 +1092,7 @@ get_qual_from_partbound(Relation rel, Relation parent,
 	{
 		case PARTITION_STRATEGY_LIST:
 			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
-			my_qual = get_qual_for_list(key, spec);
+			my_qual = get_qual_for_list(parent, spec);
 			break;
 
 		case PARTITION_STRATEGY_RANGE:
@@ -1301,8 +1489,9 @@ make_partition_op_expr(PartitionKey key, int keynum,
  * constraint, given the partition key and bound structures.
  */
 static List *
-get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
+get_qual_for_list(Relation parent, PartitionBoundSpec *spec)
 {
+	PartitionKey key = RelationGetPartitionKey(parent);
 	List	   *result;
 	Expr	   *keyCol;
 	ArrayExpr  *arr;
@@ -1323,15 +1512,59 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 	else
 		keyCol = (Expr *) copyObject(linitial(key->partexprs));
 
-	/* Create list of Consts for the allowed values, excluding any nulls */
-	foreach(cell, spec->listdatums)
+	/*
+	 * For default list partition, collect datums for all the partitions. The
+	 * default partition constraint should check that the partition key is
+	 * equal to none of those.
+	 */
+	if (spec->is_default)
 	{
-		Const	   *val = castNode(Const, lfirst(cell));
+		int			i;
+		int			ndatums = 0;
+		MemoryContext oldcxt;
+		PartitionDesc pdesc = RelationGetPartitionDesc(parent);
+		PartitionBoundInfo boundinfo = pdesc->boundinfo;
+
+		if (boundinfo)
+			ndatums = boundinfo->ndatums;
+
+		oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
 
-		if (val->constisnull)
+		for (i = 0; i < ndatums; i++)
+		{
+			Const	   *val;
+
+			/* Construct const from datum */
+			val = makeConst(key->parttypid[0],
+							key->parttypmod[0],
+							key->parttypcoll[0],
+							key->parttyplen[0],
+							*boundinfo->datums[i],
+							false,		/* isnull */
+							true /* byval */ );
+
+			arrelems = lappend(arrelems, val);
+		}
+
+		if (boundinfo && partition_bound_accepts_nulls(boundinfo))
 			list_has_null = true;
-		else
-			arrelems = lappend(arrelems, copyObject(val));
+
+		MemoryContextSwitchTo(oldcxt);
+	}
+	else
+	{
+		/*
+		 * Create list of Consts for the allowed values, excluding any nulls.
+		 */
+		foreach(cell, spec->listdatums)
+		{
+			Const	   *val = castNode(Const, lfirst(cell));
+
+			if (val->constisnull)
+				list_has_null = true;
+			else
+				arrelems = lappend(arrelems, copyObject(val));
+		}
 	}
 
 	/* Construct an ArrayExpr for the non-null partition values */
@@ -1382,6 +1615,19 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 		result = list_make1(or);
 	}
 
+	/*
+	 * In case of the default partition, the constraint is of the form
+	 * "!(result)" i.e. one of the following two forms:
+	 * 1. NOT ((keycol IS NULL) OR (keycol = ANY (arr)))
+	 * 2. NOT ((keycol IS NOT NULL) AND (keycol = ANY (arr))), where arr is an
+	 * array of datums in boundinfo->datums.
+	 */
+	if (spec->is_default)
+	{
+		result = list_make1(make_ands_explicit(result));
+		result = list_make1(makeBoolExpr(NOT_EXPR, result, -1));
+	}
+
 	return result;
 }
 
@@ -1989,8 +2235,9 @@ get_partition_for_tuple(PartitionDispatch *pd,
 		}
 
 		/*
-		 * A null partition key is only acceptable if null-accepting list
-		 * partition exists.
+		 * Handle NULL partition key here if there's a null-accepting list
+		 * partition, else later it will be routed to the default partition if
+		 * one exists.
 		 */
 		cur_index = -1;
 		if (isnull[0] && partition_bound_accepts_nulls(partdesc->boundinfo))
@@ -2028,10 +2275,16 @@ get_partition_for_tuple(PartitionDispatch *pd,
 		}
 
 		/*
-		 * cur_index < 0 means we failed to find a partition of this parent.
-		 * cur_index >= 0 means we either found the leaf partition, or the
-		 * next parent to find a partition of.
+		 * cur_index < 0 means we could not find a non-default partition of
+		 * this parent. cur_index >= 0 means we either found the leaf
+		 * partition, or the next parent to find a partition of.
+		 *
+		 * If we couldn't find a non-default partition check if the default
+		 * partition exists, if it does, get its index.
 		 */
+		if (cur_index < 0 && (partition_bound_has_default(partdesc->boundinfo)))
+			cur_index = partdesc->boundinfo->default_index;
+
 		if (cur_index < 0)
 		{
 			result = -1;
@@ -2313,3 +2566,20 @@ partition_bound_bsearch(PartitionKey key, PartitionBoundInfo boundinfo,
 
 	return lo;
 }
+
+/*
+ * get_default_partition_oid
+ *
+ * Given the parent relation checks if it has default partition, and if it
+ * does exist returns its oid, otherwise returns InvalidOid.
+ */
+Oid
+get_default_partition_oid(Relation parent)
+{
+	PartitionDesc partdesc = RelationGetPartitionDesc(parent);
+
+	if (partdesc->boundinfo && partition_bound_has_default(partdesc->boundinfo))
+		return partdesc->oids[partdesc->boundinfo->default_index];
+
+	return InvalidOid;
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index b61fda9..e36bc48 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -758,7 +758,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	{
 		PartitionBoundSpec *bound;
 		ParseState *pstate;
-		Oid			parentId = linitial_oid(inheritOids);
+		Oid			parentId = linitial_oid(inheritOids),
+					defaultPartOid;
 		Relation	parent;
 
 		/* Already have strong enough lock on the parent */
@@ -774,6 +775,20 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 					 errmsg("\"%s\" is not partitioned",
 							RelationGetRelationName(parent))));
 
+		/*
+		 * We need to take an exclusive lock on the default partition if it
+		 * exists, because its constraints are dependent upon other partition
+		 * bounds. It is possible that other backend might be about to execute
+		 * a query on the default partition table and the query relies on
+		 * previously cached default partition constraints; those constraints
+		 * won't stand correct after addition(or even removal) of a partition.
+		 * We must therefore take an exclusive lock to prevent all queries on
+		 * the default partition table from proceeding until we commit.
+		 */
+		defaultPartOid = get_default_partition_oid(parent);
+		if (OidIsValid(defaultPartOid))
+			LockRelationOid(defaultPartOid, AccessExclusiveLock);
+
 		/* Tranform the bound values */
 		pstate = make_parsestate(NULL);
 		pstate->p_sourcetext = queryString;
@@ -13798,6 +13813,7 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 				new_null[Natts_pg_class],
 				new_repl[Natts_pg_class];
 	ObjectAddress address;
+	Oid			defaultPartOid;
 
 	partRel = heap_openrv(name, AccessShareLock);
 
@@ -13830,6 +13846,14 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 	heap_close(classRel, RowExclusiveLock);
 
 	/*
+	 * Invalidate default partition's relcache, refer comment for invalidating
+	 * default partition relcache in StorePartitionBound().
+	 */
+	defaultPartOid = get_default_partition_oid(rel);
+	if (OidIsValid(defaultPartOid))
+		CacheInvalidateRelcacheByRelid(defaultPartOid);
+
+	/*
 	 * Invalidate the parent's relcache so that the partition is no longer
 	 * included in its partition descriptor.
 	 */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 36bf1dc..03d2df9 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4444,6 +4444,7 @@ _copyPartitionBoundSpec(const PartitionBoundSpec *from)
 	PartitionBoundSpec *newnode = makeNode(PartitionBoundSpec);
 
 	COPY_SCALAR_FIELD(strategy);
+	COPY_SCALAR_FIELD(is_default);
 	COPY_NODE_FIELD(listdatums);
 	COPY_NODE_FIELD(lowerdatums);
 	COPY_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 5bcf031..01fd43d 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2838,6 +2838,7 @@ static bool
 _equalPartitionBoundSpec(const PartitionBoundSpec *a, const PartitionBoundSpec *b)
 {
 	COMPARE_SCALAR_FIELD(strategy);
+	COMPARE_SCALAR_FIELD(is_default);
 	COMPARE_NODE_FIELD(listdatums);
 	COMPARE_NODE_FIELD(lowerdatums);
 	COMPARE_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index c348bdc..ae66d3d 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3543,6 +3543,7 @@ _outPartitionBoundSpec(StringInfo str, const PartitionBoundSpec *node)
 	WRITE_NODE_TYPE("PARTITIONBOUNDSPEC");
 
 	WRITE_CHAR_FIELD(strategy);
+	WRITE_BOOL_FIELD(is_default);
 	WRITE_NODE_FIELD(listdatums);
 	WRITE_NODE_FIELD(lowerdatums);
 	WRITE_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 81ddfc3..290a0bb 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2373,6 +2373,7 @@ _readPartitionBoundSpec(void)
 	READ_LOCALS(PartitionBoundSpec);
 
 	READ_CHAR_FIELD(strategy);
+	READ_BOOL_FIELD(is_default);
 	READ_NODE_FIELD(listdatums);
 	READ_NODE_FIELD(lowerdatums);
 	READ_NODE_FIELD(upperdatums);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ada95e5..299381f 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2657,6 +2657,7 @@ ForValues:
 					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
 
 					n->strategy = PARTITION_STRATEGY_LIST;
+					n->is_default = false;
 					n->listdatums = $5;
 					n->location = @3;
 
@@ -2669,12 +2670,24 @@ ForValues:
 					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
 
 					n->strategy = PARTITION_STRATEGY_RANGE;
+					n->is_default = false;
 					n->lowerdatums = $5;
 					n->upperdatums = $9;
 					n->location = @3;
 
 					$$ = n;
 				}
+
+			/* a DEFAULT partition */
+			| DEFAULT
+				{
+					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+					n->is_default = true;
+					n->location = @1;
+
+					$$ = n;
+				}
 		;
 
 partbound_datum:
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 9134fb9..e0fd559 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -60,6 +61,7 @@
 #include "parser/parse_utilcmd.h"
 #include "parser/parser.h"
 #include "rewrite/rewriteManip.h"
+#include "storage/lmgr.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
@@ -3250,6 +3252,7 @@ static void
 transformPartitionCmd(CreateStmtContext *cxt, PartitionCmd *cmd)
 {
 	Relation	parentRel = cxt->rel;
+	Oid			defaultPartOid;
 
 	/* the table must be partitioned */
 	if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
@@ -3258,6 +3261,14 @@ transformPartitionCmd(CreateStmtContext *cxt, PartitionCmd *cmd)
 				 errmsg("\"%s\" is not partitioned",
 						RelationGetRelationName(parentRel))));
 
+	/*
+	 * Need to take a lock on the default partition, refer comment for locking
+	 * the default partition in DefineRelation().
+	 */
+	defaultPartOid = get_default_partition_oid(parentRel);
+	if (OidIsValid(defaultPartOid))
+		LockRelationOid(defaultPartOid, AccessExclusiveLock);
+
 	/* transform the partition bound, if any */
 	Assert(RelationGetPartitionKey(parentRel) != NULL);
 	if (cmd->bound != NULL)
@@ -3283,6 +3294,23 @@ transformPartitionBound(ParseState *pstate, Relation parent,
 	/* Avoid scribbling on input */
 	result_spec = copyObject(spec);
 
+	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
+		 * partition bound spec.
+		 */
+		result_spec->strategy = strategy;
+
+		return result_spec;
+	}
+
 	if (strategy == PARTITION_STRATEGY_LIST)
 	{
 		ListCell   *cell;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 6a0d273..0c1e72a 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8644,10 +8644,18 @@ get_rule_expr(Node *node, deparse_context *context,
 		case T_PartitionBoundSpec:
 			{
 				PartitionBoundSpec *spec = (PartitionBoundSpec *) node;
+				char		strategy = spec->strategy;
 				ListCell   *cell;
 				char	   *sep;
 
-				switch (spec->strategy)
+				if (spec->is_default)
+				{
+					Assert(strategy == PARTITION_STRATEGY_LIST);
+					appendStringInfoString(buf, "DEFAULT");
+					break;
+				}
+
+				switch (strategy)
 				{
 					case PARTITION_STRATEGY_LIST:
 						Assert(spec->listdatums != NIL);
@@ -8713,7 +8721,7 @@ get_rule_expr(Node *node, deparse_context *context,
 
 					default:
 						elog(ERROR, "unrecognized partition strategy: %d",
-							 (int) spec->strategy);
+							 (int) strategy);
 						break;
 				}
 			}
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 92cc8aa..e3a64df 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2070,7 +2070,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("ATTACH", "PARTITION", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 	else if (TailMatches2("FOR", "VALUES"))
 		COMPLETE_WITH_LIST2("FROM (", "IN (");
 
@@ -2509,7 +2509,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("PARTITION", "OF", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 
 /* CREATE TABLESPACE */
 	else if (Matches3("CREATE", "TABLESPACE", MatchAny))
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 0a1e468..51f5b01 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -98,4 +98,5 @@ extern int get_partition_for_tuple(PartitionDispatch *pd,
 						EState *estate,
 						PartitionDispatchData **failed_at,
 						TupleTableSlot **failed_slot);
+extern Oid	get_default_partition_oid(Relation parent);
 #endif   /* PARTITION_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 2d2e2c0..9c4a82d 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -798,6 +798,7 @@ typedef struct PartitionBoundSpec
 	NodeTag		type;
 
 	char		strategy;		/* see PARTITION_STRATEGY codes above */
+	bool		is_default;		/* is it a default partition bound? */
 
 	/* Partitioning info for LIST strategy: */
 	List	   *listdatums;		/* List of Consts (or A_Consts in raw tree) */
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 8aadbb8..4ccbe14 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3273,6 +3273,11 @@ SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::reg
 CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
 ERROR:  partition "fail_part" would overlap partition "part_1"
+-- attaching default partition overlaps if the default partition already exists
+CREATE TABLE def_part PARTITION OF list_parted DEFAULT;
+CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT;
+ERROR:  partition "fail_def_part" conflicts with existing default partition "def_part"
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
 	a int,
@@ -3286,6 +3291,15 @@ ERROR:  partition constraint is violated by some row
 -- should be ok after deleting the bad row
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+-- check partition cannot be attached if default has some row for its values
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
+INSERT INTO list_parted2_def VALUES (11, 'z');
+CREATE TABLE part_3 (LIKE list_parted2);
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+ERROR:  updated partition constraint for default partition "list_parted2_def" would be violated by some row
+-- should be ok after deleting the bad row
+DELETE FROM list_parted2_def WHERE a = 11;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
 CREATE TABLE part_3_4 (
@@ -3341,6 +3355,18 @@ DELETE FROM part_5_a WHERE a NOT IN (3);
 ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT NULL;
 ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
 INFO:  partition constraint for table "part_5" is implied by existing constraints
+-- check that leaf partitions of default partition are scanned when
+-- attaching a partitioned table.
+ALTER TABLE part_5 DROP CONSTRAINT check_a;
+CREATE TABLE part5_def PARTITION OF part_5 DEFAULT PARTITION BY LIST(a);
+CREATE TABLE part5_def_p1 PARTITION OF part5_def FOR VALUES IN (5);
+INSERT INTO part5_def_p1 VALUES (5, 'y');
+CREATE TABLE part5_p1 (LIKE part_5);
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+ERROR:  updated partition constraint for default partition "part5_def" would be violated by some row
+-- should be ok after deleting the bad row
+DELETE FROM part5_def_p1 WHERE b = 'y';
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
 -- check that the table being attached is not already a partition
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 ERROR:  "part_2" is already a partition
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 5136506..e51a53a 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -449,6 +449,7 @@ CREATE TABLE list_parted (
 CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
 CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
 CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
 ERROR:  syntax error at or near "int"
 LINE 1: ... fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
@@ -457,6 +458,8 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
 ERROR:  syntax error at or near "::"
 LINE 1: ...fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
                                                                 ^
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
+ERROR:  partition "fail_default_part" conflicts with existing default partition "part_default"
 -- syntax does not allow empty list of values for list partitions
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
 ERROR:  syntax error at or near ")"
@@ -514,6 +517,9 @@ 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);
@@ -565,10 +571,15 @@ CREATE TABLE list_parted2 (
 ) PARTITION BY LIST (a);
 CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
 CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
 ERROR:  partition "fail_part" would overlap partition "part_null_z"
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
 ERROR:  partition "fail_part" would overlap partition "part_ab"
+-- check default partition overlap
+INSERT INTO list_parted2 VALUES('X');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y');
+ERROR:  updated partition constraint for default partition "list_parted2_def" would be violated by some row
 CREATE TABLE range_parted2 (
 	a int
 ) PARTITION BY RANGE (a);
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index d1153f4..6d00d4b 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -202,6 +202,7 @@ create table list_parted (
 create table part_aa_bb partition of list_parted FOR VALUES IN ('aa', 'bb');
 create table part_cc_dd partition of list_parted FOR VALUES IN ('cc', 'dd');
 create table part_null partition of list_parted FOR VALUES IN (null);
+create table part_default partition of list_parted default;
 -- fail
 insert into part_aa_bb values ('cc', 1);
 ERROR:  new row for relation "part_aa_bb" violates partition constraint
@@ -212,24 +213,45 @@ DETAIL:  Failing row contains (AAa, 1).
 insert into part_aa_bb values (null);
 ERROR:  new row for relation "part_aa_bb" violates partition constraint
 DETAIL:  Failing row contains (null, null).
+insert into part_default values ('aa', 2);
+ERROR:  new row for relation "part_default" violates partition constraint
+DETAIL:  Failing row contains (aa, 2).
+insert into part_default values (null, 2);
+ERROR:  new row for relation "part_default" violates partition constraint
+DETAIL:  Failing row contains (null, 2).
 -- ok
 insert into part_cc_dd values ('cC', 1);
 insert into part_null values (null, 0);
+insert into part_default values ('Zz', 2);
 -- check in case of multi-level partitioned table
 create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b);
 create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
 create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
+-- check in case of multi-level default partitioned table
+drop table part_default;
+create table part_default partition of list_parted default partition by range(b);
+create table part_default_p1 partition of part_default for values from (20) to (30);
+create table part_default_p2 partition of part_default for values from (30) to (40);
 -- fail
 insert into part_ee_ff1 values ('EE', 11);
 ERROR:  new row for relation "part_ee_ff1" violates partition constraint
 DETAIL:  Failing row contains (EE, 11).
+insert into part_default_p2 values ('gg', 43);
+ERROR:  new row for relation "part_default_p2" violates partition constraint
+DETAIL:  Failing row contains (gg, 43).
 -- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
 insert into part_ee_ff1 values ('cc', 1);
 ERROR:  new row for relation "part_ee_ff1" violates partition constraint
 DETAIL:  Failing row contains (cc, 1).
+insert into part_default values ('gg', 43);
+ERROR:  no partition of relation "part_default" found for row
+DETAIL:  Partition key of the failing row contains (b) = (43).
 -- ok
 insert into part_ee_ff1 values ('ff', 1);
 insert into part_ee_ff2 values ('ff', 11);
+insert into part_default_p1 values ('cd', 25);
+insert into part_default_p2 values ('de', 35);
+insert into list_parted values ('ab', 21);
 -- Check tuple routing for partitioned tables
 -- fail
 insert into range_parted values ('a', 0);
@@ -274,17 +296,20 @@ DETAIL:  Partition key of the failing row contains (b) = (0).
 insert into list_parted values ('EE', 1);
 insert into part_ee_ff values ('EE', 10);
 select tableoid::regclass, * from list_parted;
-  tableoid   | a  | b  
--------------+----+----
- part_aa_bb  | aA |   
- part_cc_dd  | cC |  1
- part_null   |    |  0
- part_null   |    |  1
- part_ee_ff1 | ff |  1
- part_ee_ff1 | EE |  1
- part_ee_ff2 | ff | 11
- part_ee_ff2 | EE | 10
-(8 rows)
+    tableoid     | a  | b  
+-----------------+----+----
+ part_aa_bb      | aA |   
+ part_cc_dd      | cC |  1
+ part_null       |    |  0
+ part_null       |    |  1
+ part_ee_ff1     | ff |  1
+ part_ee_ff1     | EE |  1
+ part_ee_ff2     | ff | 11
+ part_ee_ff2     | EE | 10
+ part_default_p1 | cd | 25
+ part_default_p1 | ab | 21
+ part_default_p2 | de | 35
+(11 rows)
 
 -- some more tests to exercise tuple-routing with multi-level partitioning
 create table part_gg partition of list_parted for values in ('gg') partition by range (b);
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
index 3f3db33..aad508d 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -252,3 +252,31 @@ NOTICE:  3
  
 (1 row)
 
+-- Check that addition or removal of any partition is correctly dealt with by
+-- default partition table when it is being used in cahced plan.
+create table list_parted (a int) partition by list(a);
+create table list_part_def partition of list_parted default;
+prepare pstmt_def_insert (int) as insert into list_part_def values($1);
+execute pstmt_def_insert(null);
+execute pstmt_def_insert(1);
+delete from list_parted where a=1;
+create table list_part_1 partition of list_parted for values in (1, 2);
+-- should fail
+execute pstmt_def_insert(1);
+ERROR:  new row for relation "list_part_def" violates partition constraint
+DETAIL:  Failing row contains (1).
+delete from list_parted where a is null;
+create table list_part_null (like list_parted);
+alter table list_parted attach partition list_part_null for values in (null);
+-- should fail
+execute pstmt_def_insert(null);
+ERROR:  new row for relation "list_part_def" violates partition constraint
+DETAIL:  Failing row contains (null).
+drop table list_part_1;
+-- should be ok
+execute pstmt_def_insert(1);
+alter table list_parted detach partition list_part_null;
+-- should be ok
+execute pstmt_def_insert(null);
+drop table list_parted;
+deallocate pstmt_def_insert;
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index 9366f04..9912ef2 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -218,5 +218,20 @@ 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;
+create table list_parted (
+	a text,
+	b int
+) partition by list (a);
+create table list_part1  partition of list_parted for values in ('a', 'b');
+create table list_default partition of list_parted default;
+insert into list_part1 values ('a', 1);
+insert into list_default values ('d', 10);
+-- fail
+update list_default set a = 'a' where a = 'd';
+ERROR:  new row for relation "list_default" violates partition constraint
+DETAIL:  Failing row contains (a, 10).
+-- ok
+update list_default set a = 'x' where a = 'd';
 -- cleanup
 drop table range_parted;
+drop table list_parted;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index c41b487..36c56aa 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2095,6 +2095,10 @@ SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::reg
 -- check that the new partition won't overlap with an existing partition
 CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+-- attaching default partition overlaps if the default partition already exists
+CREATE TABLE def_part PARTITION OF list_parted DEFAULT;
+CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT;
 
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
@@ -2111,6 +2115,15 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 
+-- check partition cannot be attached if default has some row for its values
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
+INSERT INTO list_parted2_def VALUES (11, 'z');
+CREATE TABLE part_3 (LIKE list_parted2);
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+-- should be ok after deleting the bad row
+DELETE FROM list_parted2_def WHERE a = 11;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
 CREATE TABLE part_3_4 (
@@ -2172,6 +2185,17 @@ DELETE FROM part_5_a WHERE a NOT IN (3);
 ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT NULL;
 ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
 
+-- check that leaf partitions of default partition are scanned when
+-- attaching a partitioned table.
+ALTER TABLE part_5 DROP CONSTRAINT check_a;
+CREATE TABLE part5_def PARTITION OF part_5 DEFAULT PARTITION BY LIST(a);
+CREATE TABLE part5_def_p1 PARTITION OF part5_def FOR VALUES IN (5);
+INSERT INTO part5_def_p1 VALUES (5, 'y');
+CREATE TABLE part5_p1 (LIKE part_5);
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+-- should be ok after deleting the bad row
+DELETE FROM part5_def_p1 WHERE b = 'y';
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
 
 -- check that the table being attached is not already a partition
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index cb7aa5b..f44c0e0 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -439,8 +439,10 @@ CREATE TABLE list_parted (
 CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
 CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
 CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
 
 -- syntax does not allow empty list of values for list partitions
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
@@ -484,6 +486,8 @@ 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);
@@ -529,9 +533,13 @@ CREATE TABLE list_parted2 (
 ) PARTITION BY LIST (a);
 CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
 CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+-- check default partition overlap
+INSERT INTO list_parted2 VALUES('X');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y');
 
 CREATE TABLE range_parted2 (
 	a int
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 83c3ad8..6c427f2 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -118,27 +118,41 @@ create table list_parted (
 create table part_aa_bb partition of list_parted FOR VALUES IN ('aa', 'bb');
 create table part_cc_dd partition of list_parted FOR VALUES IN ('cc', 'dd');
 create table part_null partition of list_parted FOR VALUES IN (null);
+create table part_default partition of list_parted default;
 
 -- fail
 insert into part_aa_bb values ('cc', 1);
 insert into part_aa_bb values ('AAa', 1);
 insert into part_aa_bb values (null);
+insert into part_default values ('aa', 2);
+insert into part_default values (null, 2);
 -- ok
 insert into part_cc_dd values ('cC', 1);
 insert into part_null values (null, 0);
+insert into part_default values ('Zz', 2);
 
 -- check in case of multi-level partitioned table
 create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b);
 create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
 create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
+-- check in case of multi-level default partitioned table
+drop table part_default;
+create table part_default partition of list_parted default partition by range(b);
+create table part_default_p1 partition of part_default for values from (20) to (30);
+create table part_default_p2 partition of part_default for values from (30) to (40);
 
 -- fail
 insert into part_ee_ff1 values ('EE', 11);
+insert into part_default_p2 values ('gg', 43);
 -- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
 insert into part_ee_ff1 values ('cc', 1);
+insert into part_default values ('gg', 43);
 -- ok
 insert into part_ee_ff1 values ('ff', 1);
 insert into part_ee_ff2 values ('ff', 11);
+insert into part_default_p1 values ('cd', 25);
+insert into part_default_p2 values ('de', 35);
+insert into list_parted values ('ab', 21);
 
 -- Check tuple routing for partitioned tables
 
diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql
index bc20861..c05a724 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -156,3 +156,28 @@ end$$ language plpgsql;
 
 select cachebug();
 select cachebug();
+
+-- Check that addition or removal of any partition is correctly dealt with by
+-- default partition table when it is being used in cahced plan.
+create table list_parted (a int) partition by list(a);
+create table list_part_def partition of list_parted default;
+prepare pstmt_def_insert (int) as insert into list_part_def values($1);
+execute pstmt_def_insert(null);
+execute pstmt_def_insert(1);
+delete from list_parted where a=1;
+create table list_part_1 partition of list_parted for values in (1, 2);
+-- should fail
+execute pstmt_def_insert(1);
+delete from list_parted where a is null;
+create table list_part_null (like list_parted);
+alter table list_parted attach partition list_part_null for values in (null);
+-- should fail
+execute pstmt_def_insert(null);
+drop table list_part_1;
+-- should be ok
+execute pstmt_def_insert(1);
+alter table list_parted detach partition list_part_null;
+-- should be ok
+execute pstmt_def_insert(null);
+drop table list_parted;
+deallocate pstmt_def_insert;
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 6637119..44fb0dc 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -125,5 +125,20 @@ update range_parted set b = b - 1 where b = 10;
 -- ok
 update range_parted set b = b + 1 where b = 10;
 
+create table list_parted (
+	a text,
+	b int
+) partition by list (a);
+create table list_part1  partition of list_parted for values in ('a', 'b');
+create table list_default partition of list_parted default;
+insert into list_part1 values ('a', 1);
+insert into list_default values ('d', 10);
+
+-- fail
+update list_default set a = 'a' where a = 'd';
+-- ok
+update list_default set a = 'x' where a = 'd';
+
 -- cleanup
 drop table range_parted;
+drop table list_parted;
#129Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Jeevan Ladhe (#128)
Re: Adding support for Default partition in partitioning

While the refactoring seems a reasonable way to re-use existing code,
that may change based on the discussion in [1]. Till then please keep
the refactoring patches separate from the main patch. In the final
version, I think the refactoring changes to ATAttachPartition and the
default partition support should be committed separately. So, please
provide three different patches. That also makes review easy.

On Mon, Jun 12, 2017 at 8:29 AM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

Hi Ashutosh,

I tried to look into your refactoring code.
When applied all 3 patches, I got some regression failures, I have fixed all
of
them now in attached patches, attached the regression.diffs.

Moving further, I have also made following changes in attached patches:

1. 0001-Refactor-ATExecAttachPartition.patch

+ * There is a case in which we cannot rely on just the result of the
+ * proof
This comment seems to also exist in current code, and I am not able to
follow
which case this refers to. But, IIUC, this comment is for the case where we
are
handling the 'key IS NOT NULL' part separately, and if that is the case it
is
not needed here in the prologue of the function.
attachPartCanSkipValidation
+static bool
+ATCheckValidationSkippable(Relation scanRel, List *partConstraint,
+                          PartitionKey key)
The function name ATCheckValidationSkippable does not sound very intuitive
to me,
and also I think prefix AT is something does not fit here as the function is
not
really directly related to alter table command, instead is an auxiliary
function.
How about changing it to "attachPartitionRequiresScan" or
"canSkipPartConstraintValidation"

+ List *existConstraint = NIL;
Needs to be moved to inside if block instead.

+ bool skip_validate;
Needs to be initialized to false, otherwise it can be returned without
initialization when scanRel_constr is NULL.

+   if (scanRel_constr != NULL)
instead of this may be we can simply have:
+   if (scanRel_constr == NULL)
+ return false;
This can prevent further indentation.
+static void
+ATValidatePartitionConstraints(List **wqueue, Relation scanRel,
+                              List *partConstraint, Relation rel)
What about just validatePartitionConstraints()
+   bool        skip_validate = false;
+
+   /* Check if we can do away with having to scan the table being attached.
*/
+   skip_validate = ATCheckValidationSkippable(scanRel, partConstraint,
key);

First assignment is unnecessary here.

Instead of:
/* Check if we can do away with having to scan the table being attached. */
skip_validate = ATCheckValidationSkippable(scanRel, partConstraint, key);

/* It's safe to skip the validation scan after all */
if (skip_validate)
ereport(INFO,
(errmsg("partition constraint for table \"%s\" is implied by existing
constraints",
RelationGetRelationName(scanRel))));

Following change can prevent further indentation:
if (ATCheckValidationSkippable(scanRel, partConstraint, key))
{
ereport(INFO,
(errmsg("partition constraint for table \"%s\" is implied by existing
constraints",
RelationGetRelationName(scanRel))));
return;
}
This way variable skip_validate will not be needed.

Apart from this, I see that the patch will need change depending on how the
fix
for validating partition constraints in case of embedded NOT-NULL[1] shapes
up.

2. 0003-Refactor-default-partitioning-patch-to-re-used-code.patch

+ * In case the new partition bound being checked itself is a DEFAULT
+ * bound, this check shouldn't be triggered as there won't already exists
+ * the default partition in such a case.
I think above comment in DefineRelation() is not applicable, as
check_default_allows_bound() is called unconditional, and the check for
existence
of default partition is now done inside the check_default_allows_bound()
function.

* This function checks if there exists a row in the default partition that
* fits in the new partition and throws an error if it finds one.
*/
Above comment for check_default_allows_bound() needs a change now, may be
something like this:
* This function checks if a default partition already exists and if it
does
* it checks if there exists a row in the default partition that fits in
the
* new partition and throws an error if it finds one.
*/

List *new_part_constraints = NIL;
List *def_part_constraints = NIL;
I think above initialization is not needed, as the further assignments are
unconditional.

+ if (OidIsValid(default_oid))
+ {
+ Relation default_rel = heap_open(default_oid, AccessExclusiveLock);
We already have taken a lock on default and here we should be using a NoLock
instead.

+ def_part_constraints =
get_default_part_validation_constraint(new_part_constraints);
exceeds 80 columns.

+ defPartConstraint =
get_default_part_validation_constraint(partBoundConstraint);
similarly, needs indentation.

+
+List *
+get_default_part_validation_constraint(List *new_part_constraints)
+{
Needs some comment. What about:
/*
* get_default_part_validation_constraint
*
* Given partition constraints, this function returns *would be* default
* partition constraint.
*/

Apart from this, I tried to address the differences in error shown in case
of
attache and create partition when rows in default partition would violate
the
updated constraints, basically I have taken a flag in AlteredTableInfo to
indicate if the relation being scanned is a default partition or a child of
default partition(which I dint like much, but I don't see a way out here).
Still
the error message does not display the default partition name in error as of
check_default_allows_bound(). May be to address this and keep the messages
exactly similar we can copy the name of parent default partition in a field
in
AlteredTableInfo structure, which looks very ugly to me. I am open to
suggestions here.

3. changes to default_partition_v19.patch:

The default partition constraint are no more built using the negator of the
operator, instead it is formed simply as NOT of the existing partitions:
e.g.:
if a null accepting partition already exists:
NOT ((keycol IS NULL) OR (keycol = ANY (arr)))
if a null accepting partition does not exists:
NOT ((keycol IS NOT NULL) AND (keycol = ANY (arr))), where arr is an array
of
datums in boundinfo->datums.

Added tests for prepared statment.

Renamed RelationGetDefaultPartitionOid() to get_default_partition_oid().

+ if (partqualstate && ExecCheck(partqualstate, econtext))
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("new partition constraint for default partition \"%s\" would be
violated by some row",
+   RelationGetRelationName(default_rel))));
Per Ashutosh's suggestion[2], changed the error code to
ERRCODE_INVALID_OBJECT_DEFINITION.
Also, per Robert's suggestion[3], changed following message:
"new partition constraint for default partition \"%s\" would be violated by
some row"
to
"updated partition constraint for default partition \"%s\" would be violated
by some row"

Some other cosmetic changes.

Apart from this, I am exploring the tests in relation with NOT NULL
constraint
embedded within an expression. Will update on that shortly.

[1]http://www.postgresql-archive.org/A-bug-in-mapping-attributes-in-ATExecAttachPartition-td5965298.html
[2]http://www.postgresql-archive.org/Adding-support-for-Default-partition-in-partitioning-td5946868i120.html#a5965277
[3]http://www.postgresql-archive.org/Adding-support-for-Default-partition-in-partitioning-tp5946868p5965599.html

Regards,
Jeevan Ladhe

On Thu, Jun 8, 2017 at 2:54 PM, Ashutosh Bapat
<ashutosh.bapat@enterprisedb.com> wrote:

On Wed, Jun 7, 2017 at 2:08 AM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

The code in check_default_allows_bound() to check whether the default
partition
has any rows that would fit new partition looks quite similar to the
code
in
ATExecAttachPartition() checking whether all rows in the table being
attached
as a partition fit the partition bounds. One thing that
check_default_allows_bound() misses is, if there's already a constraint
on
the
default partition refutes the partition constraint on the new
partition,
we can
skip the scan of the default partition since it can not have rows that
would
fit the new partition. ATExecAttachPartition() has code to deal with a
similar
case i.e. the table being attached has a constraint which implies the
partition
constraint. There may be more cases which check_default_allows_bound()
does not
handle but ATExecAttachPartition() handles. So, I am wondering whether
it's
better to somehow take out the common code into a function and use it.
We
will
have to deal with a difference through. The first one would throw an
error
when
finding a row that satisfies partition constraints whereas the second
one
would
throw an error when it doesn't find such a row. But this difference can
be
handled through a flag or by negating the constraint. This would also
take
care
of Amit Langote's complaint about foreign partitions. There's also
another
difference that the ATExecAttachPartition() queues the table for scan
and
the
actual scan takes place in ATRewriteTable(), but there is not such
queue
while
creating a table as a partition. But we should check if we can reuse
the
code to
scan the heap for checking a constraint.

In case of ATTACH PARTITION, probably we should schedule scan of
default
partition in the alter table's work queue like what
ATExecAttachPartition() is
doing for the table being attached. That would fit in the way alter
table
works.

I tried refactoring existing code so that it can be used for default
partitioning as well. The code to validate the partition constraints
against the table being attached in ATExecAttachPartition() is
extracted out into a set of functions. For default partition we reuse
those functions to check whether it contains any row that would fit
the partition being attached. While creating a new partition, the
function to skip validation is reused but the scan portion is
duplicated from ATRewriteTable since we are not in ALTER TABLE
context. The names of the functions, their declaration will require
some thought.

There's one test failing because for ATTACH partition the error comes
from ATRewriteTable instead of check_default_allows_bounds(). May be
we want to use same message in both places or some make ATRewriteTable
give a different message while validating default partition.

Please review the patch and let me know if the changes look good.

--
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

#130Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Ashutosh Bapat (#129)
1 attachment(s)
Re: Adding support for Default partition in partitioning

On Mon, Jun 12, 2017 at 9:39 AM, Ashutosh Bapat <
ashutosh.bapat@enterprisedb.com> wrote:

While the refactoring seems a reasonable way to re-use existing code,
that may change based on the discussion in [1]. Till then please keep
the refactoring patches separate from the main patch. In the final
version, I think the refactoring changes to ATAttachPartition and the
default partition support should be committed separately. So, please
provide three different patches. That also makes review easy.

Sure Ashutosh,

PFA.

Attachments:

default_partition_v20_patches.tar.gzapplication/x-gzip; name=default_partition_v20_patches.tar.gzDownload
#131Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Jeevan Ladhe (#130)
1 attachment(s)
Re: Adding support for Default partition in partitioning

While rebasing the current set of patches to the latest source, I realized
that it might be a good idea to split the default partitioning support
patch further
into two incremental patches, where the first patch for default partition
support would prevent addition of any new partition if there exists a
default
partition, and then an incremental patch which allows to create/attach a
new partition even if there exists a default partition provided the default
partition does not have any rows satisfying the bounds of the new partition
being added. This would be easier for review.

Here are the details of the patches in attached zip.
0001. refactoring existing ATExecAttachPartition code so that it can be
used for
default partitioning as well
0002. support for default partition with the restriction of preventing
addition
of any new partition after default partition.
0003. extend default partitioning support to allow addition of new
partitions.
0004. extend default partitioning validation code to reuse the refactored
code
in patch 0001.

PFA

Regards,
Jeevan Ladhe

On Mon, Jun 12, 2017 at 11:49 AM, Jeevan Ladhe <
jeevan.ladhe@enterprisedb.com> wrote:

Show quoted text

On Mon, Jun 12, 2017 at 9:39 AM, Ashutosh Bapat <
ashutosh.bapat@enterprisedb.com> wrote:

While the refactoring seems a reasonable way to re-use existing code,
that may change based on the discussion in [1]. Till then please keep
the refactoring patches separate from the main patch. In the final
version, I think the refactoring changes to ATAttachPartition and the
default partition support should be committed separately. So, please
provide three different patches. That also makes review easy.

Sure Ashutosh,

PFA.

Attachments:

default_partition_splits_v21.tar.gzapplication/x-gzip; name=default_partition_splits_v21.tar.gzDownload
#132Robert Haas
robertmhaas@gmail.com
In reply to: Jeevan Ladhe (#131)
Re: Adding support for Default partition in partitioning

On Wed, Jun 14, 2017 at 8:02 AM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

Here are the details of the patches in attached zip.
0001. refactoring existing ATExecAttachPartition code so that it can be
used for
default partitioning as well
0002. support for default partition with the restriction of preventing
addition
of any new partition after default partition.
0003. extend default partitioning support to allow addition of new
partitions.
0004. extend default partitioning validation code to reuse the refactored
code
in patch 0001.

I think the core ideas of this patch are pretty solid now. It's come
a long way in the last month. High-level comments:

- Needs to be rebased over b08df9cab777427fdafe633ca7b8abf29817aa55.
- Still no documentation.
- Should probably be merged with the patch to add default partitioning
for ranges.

Other stuff I noticed:

- The regression tests don't seem to check that the scan-skipping
logic works as expected. We have regression tests for that case for
attaching regular partitions, and it seems like it would be worth
testing the default-partition case as well.

- check_default_allows_bound() assumes that if
canSkipPartConstraintValidation() fails for the default partition, it
will also fail for every subpartition of the default partition. That
is, once we commit to scanning one child partition, we're committed to
scanning them all. In practice, that's probably not a huge
limitation, but if it's not too much code, we could keep the top-level
check but also check each partitioning individually as we reach it,
and skip the scan for any individual partitions for which the
constraint can be proven. For example, suppose the top-level table is
list-partitioned with a partition for each of the most common values,
and then we range-partition the default partition.

- The changes to the regression test results in 0004 make the error
messages slightly worse. The old message names the default partition,
whereas the new one does not. Maybe that's worth avoiding.

Specific comments:

+ * Also, invalidate the parent's and a sibling default partition's relcache,
+ * so that the next rebuild will load the new partition's info into parent's
+ * partition descriptor and default partition constraints(which are dependent
+ * on other partition bounds) are built anew.

I find this a bit unclear, and it also repeats the comment further
down. Maybe something like: Also, invalidate the parent's relcache
entry, so that the next rebuild will load he new partition's info into
its partition descriptor. If there is a default partition, we must
invalidate its relcache entry as well.

+    /*
+     * The default partition constraints depend upon the partition bounds of
+     * other partitions. Adding a new(or even removing existing) partition
+     * would invalidate the default partition constraints. Invalidate the
+     * default partition's relcache so that the constraints are built anew and
+     * any plans dependent on those constraints are invalidated as well.
+     */

Here, I'd write: The partition constraint for the default partition
depends on the partition bounds of every other partition, so we must
invalidate the relcache entry for that partition every time a
partition is added or removed.

+                    /*
+                     * 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;
+                    }

To support default partitioning for range, this is going to have to be
moved above the switch rather than done inside of it. And there's
really no downside to putting it there.

+ * constraint, by *proving* that the existing constraints of the table
+ * *imply* the given constraints.  We include the table's check constraints and

Both the comma and the asterisks are unnecessary.

+ * Check whether all rows in the given table (scanRel) obey given partition

obey the given

I think the larger comment block could be tightened up a bit, like
this: Check whether all rows in the given table obey the given
partition constraint; if so, it can be attached as a partition. We do
this by scanning the table (or all of its leaf partitions) row by row,
except when the existing constraints are sufficient to prove that the
new partitioning constraint must already hold.

+ /* Check if we can do away with having to scan the table being attached. */

If possible, skip the validation scan.

+     * Set up to have the table be scanned to validate the partition
+     * constraint If it's a partitioned table, we instead schedule its leaf
+     * partitions to be scanned.

I suggest: Prepare to scan the default partition (or, if it is itself
partitioned, all of its leaf partitions).

+    int         default_index;  /* Index of the default partition if any; -1
+                                 * if there isn't one */

"if any" is a bit redundant with "if there isn't one"; note the
phrasing of the preceding entry.

+        /*
+         * Skip if it's a partitioned table. Only RELKIND_RELATION relations
+         * (ie, leaf partitions) need to be scanned.
+         */
+        if (part_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+            part_rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)

The comment talks about what must be included in our list of things to
scan, but the code tests for the things that can be excluded. I
suspect the comment has the right idea and the code should be adjusted
to match, but anyway they should be consistent. Also, the correct way
to punctuate i.e. is like this: (i.e. leaf partitions) You should have
a period after each letter, but no following comma.

+ * The default partition must be already having an AccessExclusiveLock.

I think we should instead change DefineRelation to open (rather than
just lock) the default partition and pass the Relation as an argument
here so that we need not reopen it.

+            /* Construct const from datum */
+            val = makeConst(key->parttypid[0],
+                            key->parttypmod[0],
+                            key->parttypcoll[0],
+                            key->parttyplen[0],
+                            *boundinfo->datums[i],
+                            false,      /* isnull */
+                            key->parttypbyval[0] /* byval */ );

The /* byval */ comment looks a bit redundant, but I think this could
use a comment along the lines of: /* Only single-column list
partitioning is supported, so we only need to worry about the
partition key with index 0. */ And I'd also add an Assert() verifying
the the partition key has exactly 1 column, so that this breaks a bit
more obviously if someone removes that restriction in the future.

+         * Handle NULL partition key here if there's a null-accepting list
+         * partition, else later it will be routed to the default partition if
+         * one exists.

This isn't a great update of the existing comment -- it's drifted from
explaining the code to which it is immediately attached to a more
general discussion of NULL handling. I'd just say something like: If
this is a NULL, send it to the null-accepting partition. Otherwise,
route by searching the array of partition bounds.

+                if (tab->is_default_partition)
+                    ereport(ERROR,
+                            (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                             errmsg("updated partition constraint for
default partition would be violated by some row")));
+                else
+                    ereport(ERROR,
+                            (errcode(ERRCODE_CHECK_VIOLATION),

While there's room for debate about the correct error code here, it's
hard for me to believe that it's correct to use one error code for the
is_default_partition case and a different error code the rest of the
time.

+         * previously cached default partition constraints; those constraints
+         * won't stand correct after addition(or even removal) of a partition.

won't be correct after addition or removal

+         * allow any row that qualifies for this new partition. So, check if
+         * the existing data in the default partition satisfies this *would be*
+         * default partition constraint.

check that the existing data in the default partition satisfies the
constraint as it will exist after adding this partition

+     * Need to take a lock on the default partition, refer comment for locking
+     * the default partition in DefineRelation().

I'd say: We must also lock the default partition, for the same reasons
explained in DefineRelation().

And similarly in the other places that refer to that same comment.

+    /*
+     * In case of the default partition, the constraint is of the form
+     * "!(result)" i.e. one of the following two forms:
+     * 1. NOT ((keycol IS NULL) OR (keycol = ANY (arr)))
+     * 2. NOT ((keycol IS NOT NULL) AND (keycol = ANY (arr))), where arr is an
+     * array of datums in boundinfo->datums.
+     */

Does this survive pgindent? You might need to surround the comment
with dashes to preserve formatting.

I think it would be worth adding a little more text this comment,
something like this: Note that, in general, applying NOT to a
constraint expression doesn't necessarily invert the set of rows it
accepts, because NOT NULL is NULL. However, the partition constraints
we construct here never evaluate to NULL, so applying NOT works as
intended.

+     * Check whether default partition has a row that would fit the partition
+     * being attached by negating the partition constraint derived from the
+     * bounds. Since default partition is already part of the partitioned
+     * table, we don't need to validate the constraints on the partitioned
+     * table.

Here again, I'd add to the end of the first sentence a parenthetical
note, like this: ...from the bounds (the partition constraint never
evaluates to NULL, so negating it like this is safe).

I don't understand the second sentence. It seems to contradict the first one.

+extern List *get_default_part_validation_constraint(List *new_part_constaints);
#endif /* PARTITION_H */

There should be a blank line after the last prototype and before the #endif.

+-- default partition table when it is being used in cahced plan.

Typo.

--
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

#133Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Robert Haas (#132)
Re: Adding support for Default partition in partitioning

On 2017/06/15 4:51, Robert Haas wrote:

On Wed, Jun 14, 2017 at 8:02 AM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

Here are the details of the patches in attached zip.
0001. refactoring existing ATExecAttachPartition code so that it can be
used for
default partitioning as well
0002. support for default partition with the restriction of preventing
addition
of any new partition after default partition.
0003. extend default partitioning support to allow addition of new
partitions.
0004. extend default partitioning validation code to reuse the refactored
code
in patch 0001.

I think the core ideas of this patch are pretty solid now. It's come
a long way in the last month.

+1

BTW, I noticed the following in 0002:

@@ -1322,15 +1357,59 @@ get_qual_for_list(PartitionKey key,
PartitionBoundSpec *spec)

[ ... ]

+ oldcxt = MemoryContextSwitchTo(CacheMemoryContext);

I'm not sure if we need to do that. Can you explain?

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#134Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Amit Langote (#133)
Re: Adding support for Default partition in partitioning

Oops, I meant to send one more comment.

On 2017/06/15 15:48, Amit Langote wrote:

BTW, I noticed the following in 0002

+ errmsg("there exists a default partition for table \"%s\", cannot
add a new partition",

This error message style seems novel to me. I'm not sure about the best
message text here, but maybe: "cannot add new partition to table \"%s\"
with default partition"

Note that the comment applies to both DefineRelation and
ATExecAttachPartition.

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#135Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Amit Langote (#134)
Re: Adding support for Default partition in partitioning

Some more comments on the latest set of patches.

In heap_drop_with_catalog(), we heap_open() the parent table to get the
default partition OID, if any. If the relcache doesn't have an entry for the
parent, this means that the entry will be created, only to be invalidated at
the end of the function. If there is no default partition, this all is
completely unnecessary. We should avoid heap_open() in this case. This also
means that get_default_partition_oid() should not rely on the relcache entry,
but should growl through pg_inherit to find the default partition.

In get_qual_for_list(), if the table has only default partition, it won't have
any boundinfo. In such a case the default partition's constraint would come out
as (NOT ((a IS NOT NULL) AND (a = ANY (ARRAY[]::integer[])))). The empty array
looks odd and may be we spend a few CPU cycles executing ANY on an empty array.
We have the same problem with a partition containing only NULL value. So, may
be this one is not that bad.

Please add a testcase to test addition of default partition as the first
partition.

get_qual_for_list() allocates the constant expressions corresponding to the
datums in CacheMemoryContext while constructing constraints for a default
partition. We do not do this for other partitions. We may not be constructing
the constraints for saving in the cache. For example, ATExecAttachPartition
constructs the constraints for validation. In such a case, this code will
unnecessarily clobber the cache memory. generate_partition_qual() copies the
partition constraint in the CacheMemoryContext.

+   if (spec->is_default)
+   {
+       result = list_make1(make_ands_explicit(result));
+       result = list_make1(makeBoolExpr(NOT_EXPR, result, -1));
+   }

If the "result" is an OR expression, calling make_ands_explicit() on it would
create AND(OR(...)) expression, with an unnecessary AND. We want to avoid that?

+       if (cur_index < 0 && (partition_bound_has_default(partdesc->boundinfo)))
+           cur_index = partdesc->boundinfo->default_index;
+
The partition_bound_has_default() check is unnecessary since we check for
cur_index < 0 next anyway.
+ *
+ * Given the parent relation checks if it has default partition, and if it
+ * does exist returns its oid, otherwise returns InvalidOid.
+ */
May be reworded as "If the given relation has a default partition, this
function returns the OID of the default partition. Otherwise it returns
InvalidOid."
+Oid
+get_default_partition_oid(Relation parent)
+{
+   PartitionDesc partdesc = RelationGetPartitionDesc(parent);
+
+   if (partdesc->boundinfo && partition_bound_has_default(partdesc->boundinfo))
+       return partdesc->oids[partdesc->boundinfo->default_index];
+
+   return InvalidOid;
+}
An unpartitioned table would not have partdesc set set. So, this function will
segfault if we pass an unpartitioned table. Either Assert that partdesc should
exist or check for its NULL-ness.
+    defaultPartOid = get_default_partition_oid(rel);
+    if (OidIsValid(defaultPartOid))
+        ereport(ERROR,
+                (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                 errmsg("there exists a default partition for table
\"%s\", cannot attach a new partition",
+                        RelationGetRelationName(rel))));
+
Should be done before heap_open on the table being attached. If we are not
going to attach the partition, there's no point in instantiating its relcache.

The comment in heap_drop_with_catalog() should mention why we lock the default
partition before locking the table being dropped.

extern List *preprune_pg_partitions(PlannerInfo *root, RangeTblEntry *rte,
Index rti, Node *quals, LOCKMODE lockmode);
-
#endif /* PARTITION_H */
Unnecessary hunk.

On Thu, Jun 15, 2017 at 12:31 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Oops, I meant to send one more comment.

On 2017/06/15 15:48, Amit Langote wrote:

BTW, I noticed the following in 0002

+ errmsg("there exists a default partition for table \"%s\", cannot
add a new partition",

This error message style seems novel to me. I'm not sure about the best
message text here, but maybe: "cannot add new partition to table \"%s\"
with default partition"

Note that the comment applies to both DefineRelation and
ATExecAttachPartition.

Thanks,
Amit

--
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

#136Robert Haas
robertmhaas@gmail.com
In reply to: Ashutosh Bapat (#135)
Re: Adding support for Default partition in partitioning

On Thu, Jun 15, 2017 at 12:54 PM, Ashutosh Bapat
<ashutosh.bapat@enterprisedb.com> wrote:

Some more comments on the latest set of patches.

In heap_drop_with_catalog(), we heap_open() the parent table to get the
default partition OID, if any. If the relcache doesn't have an entry for the
parent, this means that the entry will be created, only to be invalidated at
the end of the function. If there is no default partition, this all is
completely unnecessary. We should avoid heap_open() in this case. This also
means that get_default_partition_oid() should not rely on the relcache entry,
but should growl through pg_inherit to find the default partition.

I am *entirely* unconvinced by this line of argument. I think we want
to open the relation the first time we touch it and pass the Relation
around thereafter. Anything else is prone to accidentally failing to
have the relation locked early enough, or looking up the OID in the
relcache multiple times.

In get_qual_for_list(), if the table has only default partition, it won't have
any boundinfo. In such a case the default partition's constraint would come out
as (NOT ((a IS NOT NULL) AND (a = ANY (ARRAY[]::integer[])))). The empty array
looks odd and may be we spend a few CPU cycles executing ANY on an empty array.
We have the same problem with a partition containing only NULL value. So, may
be this one is not that bad.

I think that one is probably worth fixing.

Please add a testcase to test addition of default partition as the first
partition.

That seems like a good idea, too.

get_qual_for_list() allocates the constant expressions corresponding to the
datums in CacheMemoryContext while constructing constraints for a default
partition. We do not do this for other partitions. We may not be constructing
the constraints for saving in the cache. For example, ATExecAttachPartition
constructs the constraints for validation. In such a case, this code will
unnecessarily clobber the cache memory. generate_partition_qual() copies the
partition constraint in the CacheMemoryContext.

+   if (spec->is_default)
+   {
+       result = list_make1(make_ands_explicit(result));
+       result = list_make1(makeBoolExpr(NOT_EXPR, result, -1));
+   }

Clearly we do not want things to end up across multiple contexts. We
should ensure that anything linked from the relcache entry ends up in
CacheMemoryContext, but we must be careful not to allocate anything
else in there, because CacheMemoryContext is never reset.

If the "result" is an OR expression, calling make_ands_explicit() on it would
create AND(OR(...)) expression, with an unnecessary AND. We want to avoid that?

I'm not sure it's worth the trouble.

+    defaultPartOid = get_default_partition_oid(rel);
+    if (OidIsValid(defaultPartOid))
+        ereport(ERROR,
+                (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                 errmsg("there exists a default partition for table
\"%s\", cannot attach a new partition",
+                        RelationGetRelationName(rel))));
+
Should be done before heap_open on the table being attached. If we are not
going to attach the partition, there's no point in instantiating its relcache.

No, because we should take the lock before examining any properties of
the table.

--
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

#137Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Robert Haas (#136)
Re: Adding support for Default partition in partitioning

On Fri, Jun 16, 2017 at 12:48 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Thu, Jun 15, 2017 at 12:54 PM, Ashutosh Bapat
<ashutosh.bapat@enterprisedb.com> wrote:

Some more comments on the latest set of patches.

In heap_drop_with_catalog(), we heap_open() the parent table to get the
default partition OID, if any. If the relcache doesn't have an entry for the
parent, this means that the entry will be created, only to be invalidated at
the end of the function. If there is no default partition, this all is
completely unnecessary. We should avoid heap_open() in this case. This also
means that get_default_partition_oid() should not rely on the relcache entry,
but should growl through pg_inherit to find the default partition.

I am *entirely* unconvinced by this line of argument. I think we want
to open the relation the first time we touch it and pass the Relation
around thereafter.

If this would be correct, why heap_drop_with_catalog() without this
patch just locks the parent and doesn't call a heap_open(). I am
missing something.

Anything else is prone to accidentally failing to
have the relation locked early enough,

We are locking the parent relation even without this patch, so this
isn't an issue.

or looking up the OID in the
relcache multiple times.

I am not able to understand this in the context of default partition.
After that nobody else is going to change its partitions and their
bounds (since both of those require heap_open on parent which would be
stuck on the lock we hold.). So, we have to check only once if the
table has a default partition. If it doesn't, it's not going to
acquire one unless we release the lock on the parent i.e at the end of
transaction. If it has one, it's not going to get dropped till the end
of the transaction for the same reason. I don't see where we are
looking up OIDs multiple times.

+    defaultPartOid = get_default_partition_oid(rel);
+    if (OidIsValid(defaultPartOid))
+        ereport(ERROR,
+                (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                 errmsg("there exists a default partition for table
\"%s\", cannot attach a new partition",
+                        RelationGetRelationName(rel))));
+
Should be done before heap_open on the table being attached. If we are not
going to attach the partition, there's no point in instantiating its relcache.

No, because we should take the lock before examining any properties of
the table.

There are three tables involved here. "rel" which is the partitioned
table. "attachrel" is the table being attached as a partition to "rel"
and defaultrel, which is the default partition table. If there exists
a default partition in "rel" we are not allowing "attachrel" to be
attached to "rel". If that's the case, we don't need to examine any
properties of "attachrel" and hence we don't need to instantiate
relcache of "attachrel". That's what the comment is about.
ATExecAttachPartition() receives "rel" as an argument which has been
already locked and opened. So, we can check the existence of default
partition right at the beginning of the function.

--
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

#138Kyotaro HORIGUCHI
horiguchi.kyotaro@lab.ntt.co.jp
In reply to: Amit Langote (#134)
Re: Adding support for Default partition in partitioning

Hello, I'd like to review this but it doesn't fit the master, as
Robert said. Especially the interface of predicate_implied_by is
changed by the suggested commit.

Anyway I have some comment on this patch with fresh eyes. I
believe the basic design so my comment below are from a rather
micro viewpoint.

At Thu, 15 Jun 2017 16:01:53 +0900, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote in <a1267081-6e9a-e570-f6cf-34ff801bf503@lab.ntt.co.jp>

Oops, I meant to send one more comment.

On 2017/06/15 15:48, Amit Langote wrote:

BTW, I noticed the following in 0002

+ errmsg("there exists a default partition for table \"%s\", cannot
add a new partition",

This error message style seems novel to me. I'm not sure about the best
message text here, but maybe: "cannot add new partition to table \"%s\"
with default partition"

Note that the comment applies to both DefineRelation and
ATExecAttachPartition.

- Considering on how canSkipPartConstraintValidation is called, I
*think* that RelationProvenValid() would be better. (But this
would be disappear by rebasing..)

- 0002 changes the interface of get_qual_for_list, but left
get_qual_for_range alone. Anyway get_qual_for_range will have
to do the similar thing soon.

- In check_new_partition_bound, "overlap" and "with" is
completely correlated with each other. "with > -1" means
"overlap = true". So overlap is not useless. ("with" would be
better to be "overlap_with" or somehting if we remove
"overlap")

- The error message of check_default_allows_bound is below.

"updated partition constraint for default partition \"%s\"
would be violated by some row"

This looks an analog of validateCheckConstraint but as my
understanding this function is called only when new partition
is added. This would be difficult to recognize in the
situation.

"the default partition contains rows that should be in
the new partition: \"%s\""

or something?

- In check_default_allows_bound, the iteration over partitions is
quite similar to what validateCheckConstraint does. Can we
somehow share validateCheckConstraint with this function?

- In the same function, skipping RELKIND_PARTITIONED_TABLE is
okay, but silently ignoring RELKIND_FOREIGN_TABLE doesn't seem
good. I think at least some warning should be emitted.

"Skipping foreign tables in the defalut partition. It might
contain rows that should be in the new partition." (Needs
preventing multple warnings in single call, maybe)

- In the same function, the following condition seems somewhat
strange in comparison to validateCheckConstraint.

if (partqualstate && ExecCheck(partqualstate, econtext))

partqualstate won't be null as long as partition_constraint is
valid. Anyway (I'm believing that) an invalid constraint
results in error by ExecPrepareExpr. Therefore 'if
(partqualstate' is useless.

- In gram.y, the nonterminal for list spec clause is still
"ForValues". It seems somewhat strange. partition_spec or
something would be better.

- This is not a part of this patch, but in ruleutils.c, the error
for unknown paritioning strategy is emitted as following.

elog(ERROR, "unrecognized partition strategy: %d",
(int) strategy);

The cast is added because the strategy is a char. I suppose
this is because strategy can be an unprintable. I'd like to see
a comment if it is correct.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#139Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Ashutosh Bapat (#137)
Re: Adding support for Default partition in partitioning

On 2017/06/16 14:16, Ashutosh Bapat wrote:

On Fri, Jun 16, 2017 at 12:48 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Thu, Jun 15, 2017 at 12:54 PM, Ashutosh Bapat
<ashutosh.bapat@enterprisedb.com> wrote:

Some more comments on the latest set of patches.

In heap_drop_with_catalog(), we heap_open() the parent table to get the
default partition OID, if any. If the relcache doesn't have an entry for the
parent, this means that the entry will be created, only to be invalidated at
the end of the function. If there is no default partition, this all is
completely unnecessary. We should avoid heap_open() in this case. This also
means that get_default_partition_oid() should not rely on the relcache entry,
but should growl through pg_inherit to find the default partition.

I am *entirely* unconvinced by this line of argument. I think we want
to open the relation the first time we touch it and pass the Relation
around thereafter.

If this would be correct, why heap_drop_with_catalog() without this
patch just locks the parent and doesn't call a heap_open(). I am
missing something.

As of commit c1e0e7e1d790bf, we avoid creating relcache entry for the
parent. Before that commit, drop table
partitioned_table_with_many_partitions used to take too long and consumed
quite some memory as result of relcache invalidation requested at the end
on the parent table for every partition.

If this patch reintroduces the heap_open() on the parent table, that's
going to bring back the problem fixed by that commit.

Anything else is prone to accidentally failing to
have the relation locked early enough,

We are locking the parent relation even without this patch, so this
isn't an issue.

Yes.

or looking up the OID in the
relcache multiple times.

I am not able to understand this in the context of default partition.
After that nobody else is going to change its partitions and their
bounds (since both of those require heap_open on parent which would be
stuck on the lock we hold.). So, we have to check only once if the
table has a default partition. If it doesn't, it's not going to
acquire one unless we release the lock on the parent i.e at the end of
transaction. If it has one, it's not going to get dropped till the end
of the transaction for the same reason. I don't see where we are
looking up OIDs multiple times.

Without heap_opening the parent, the only way is to look up parentOid's
children in pg_inherits and for each child looking up its pg_class tuple
in the syscache to see if its relpartbound indicates that it's a default
partition. That seems like it won't be inexpensive either.

It would be nice if could get that information (that is - is a given
relation being heap_drop_with_catalog'd a partition of the parent that
happens to have default partition) in less number of steps than that.
Having that information in relcache is one way, but as mentioned, that
turns out be expensive.

Has anyone considered the idea of putting the default partition OID in the
pg_partitioned_table catalog? Looking the above information up would
amount to one syscache lookup. Default partition seems to be special
enough object to receive a place in the pg_partitioned_table tuple of the
parent. Thoughts?

+    defaultPartOid = get_default_partition_oid(rel);
+    if (OidIsValid(defaultPartOid))
+        ereport(ERROR,
+                (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                 errmsg("there exists a default partition for table
\"%s\", cannot attach a new partition",
+                        RelationGetRelationName(rel))));
+
Should be done before heap_open on the table being attached. If we are not
going to attach the partition, there's no point in instantiating its relcache.

No, because we should take the lock before examining any properties of
the table.

There are three tables involved here. "rel" which is the partitioned
table. "attachrel" is the table being attached as a partition to "rel"
and defaultrel, which is the default partition table. If there exists
a default partition in "rel" we are not allowing "attachrel" to be
attached to "rel". If that's the case, we don't need to examine any
properties of "attachrel" and hence we don't need to instantiate
relcache of "attachrel". That's what the comment is about.
ATExecAttachPartition() receives "rel" as an argument which has been
already locked and opened. So, we can check the existence of default
partition right at the beginning of the function.

It seems that we are examining the properties of the parent table here
(whether it has default partition), which as Ashutosh mentions, is already
locked before we got to ATExecAttachPartition(). Another place where we
are ereporting before locking the table to be attached (actually even
before looking it up by name), based just on the properties of the parent
table, is in transformPartitionCmd():

/* the table must be partitioned */
if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("\"%s\" is not partitioned",
RelationGetRelationName(parentRel))));

Thanks,
Amit

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#140Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Robert Haas (#132)
Re: Adding support for Default partition in partitioning

Hi,

Sorry for being away from here.
I had some issues with my laptop, and I have resumed working on this.

On Thu, Jun 15, 2017 at 1:21 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Wed, Jun 14, 2017 at 8:02 AM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

Here are the details of the patches in attached zip.
0001. refactoring existing ATExecAttachPartition code so that it can be
used for
default partitioning as well
0002. support for default partition with the restriction of preventing
addition
of any new partition after default partition.
0003. extend default partitioning support to allow addition of new
partitions.
0004. extend default partitioning validation code to reuse the refactored
code
in patch 0001.

I think the core ideas of this patch are pretty solid now. It's come
a long way in the last month. High-level comments:

Thanks Robert for looking into this.

- Needs to be rebased over b08df9cab777427fdafe633ca7b8abf29817aa55.

Will rebase.

- Still no documentation.
- Should probably be merged with the patch to add default partitioning
for ranges.

Will try to get this soon.

Regards,
Jeevan Ladhe

#141Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Amit Langote (#134)
Re: Adding support for Default partition in partitioning

Hi Amit,

On Thu, Jun 15, 2017 at 12:31 PM, Amit Langote <
Langote_Amit_f8@lab.ntt.co.jp> wrote:

Oops, I meant to send one more comment.

On 2017/06/15 15:48, Amit Langote wrote:

BTW, I noticed the following in 0002

+ errmsg("there exists a default
partition for table \"%s\", cannot
add a new partition",

This error message style seems novel to me. I'm not sure about the best
message text here, but maybe: "cannot add new partition to table \"%s\"
with default partition"

This sounds confusing to me, what about something like:
"\"%s\" has a default partition, cannot add a new partition."

Note that this comment belongs to patch 0002, and it will go away
in case we are going to have extended functionality i.e. patch 0003,
as in that patch we allow user to create a new partition even in the
cases when there exists a default partition.

Regards,
Jeevan Ladhe

#142Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Kyotaro HORIGUCHI (#138)
Re: Adding support for Default partition in partitioning

Thanks Ashutosh and Kyotaro for reviewing further.
I shall address your comments in next version of my patch.

Regards,
Jeevan Ladhe

On Fri, Jun 16, 2017 at 1:46 PM, Kyotaro HORIGUCHI <
horiguchi.kyotaro@lab.ntt.co.jp> wrote:

Show quoted text

Hello, I'd like to review this but it doesn't fit the master, as
Robert said. Especially the interface of predicate_implied_by is
changed by the suggested commit.

Anyway I have some comment on this patch with fresh eyes. I
believe the basic design so my comment below are from a rather
micro viewpoint.

At Thu, 15 Jun 2017 16:01:53 +0900, Amit Langote <
Langote_Amit_f8@lab.ntt.co.jp> wrote in <a1267081-6e9a-e570-f6cf-
34ff801bf503@lab.ntt.co.jp>

Oops, I meant to send one more comment.

On 2017/06/15 15:48, Amit Langote wrote:

BTW, I noticed the following in 0002

+ errmsg("there exists a default

partition for table \"%s\", cannot

add a new partition",

This error message style seems novel to me. I'm not sure about the best
message text here, but maybe: "cannot add new partition to table \"%s\"
with default partition"

Note that the comment applies to both DefineRelation and
ATExecAttachPartition.

- Considering on how canSkipPartConstraintValidation is called, I
*think* that RelationProvenValid() would be better. (But this
would be disappear by rebasing..)

- 0002 changes the interface of get_qual_for_list, but left
get_qual_for_range alone. Anyway get_qual_for_range will have
to do the similar thing soon.

- In check_new_partition_bound, "overlap" and "with" is
completely correlated with each other. "with > -1" means
"overlap = true". So overlap is not useless. ("with" would be
better to be "overlap_with" or somehting if we remove
"overlap")

- The error message of check_default_allows_bound is below.

"updated partition constraint for default partition \"%s\"
would be violated by some row"

This looks an analog of validateCheckConstraint but as my
understanding this function is called only when new partition
is added. This would be difficult to recognize in the
situation.

"the default partition contains rows that should be in
the new partition: \"%s\""

or something?

- In check_default_allows_bound, the iteration over partitions is
quite similar to what validateCheckConstraint does. Can we
somehow share validateCheckConstraint with this function?

- In the same function, skipping RELKIND_PARTITIONED_TABLE is
okay, but silently ignoring RELKIND_FOREIGN_TABLE doesn't seem
good. I think at least some warning should be emitted.

"Skipping foreign tables in the defalut partition. It might
contain rows that should be in the new partition." (Needs
preventing multple warnings in single call, maybe)

- In the same function, the following condition seems somewhat
strange in comparison to validateCheckConstraint.

if (partqualstate && ExecCheck(partqualstate, econtext))

partqualstate won't be null as long as partition_constraint is
valid. Anyway (I'm believing that) an invalid constraint
results in error by ExecPrepareExpr. Therefore 'if
(partqualstate' is useless.

- In gram.y, the nonterminal for list spec clause is still
"ForValues". It seems somewhat strange. partition_spec or
something would be better.

- This is not a part of this patch, but in ruleutils.c, the error
for unknown paritioning strategy is emitted as following.

elog(ERROR, "unrecognized partition strategy: %d",
(int) strategy);

The cast is added because the strategy is a char. I suppose
this is because strategy can be an unprintable. I'd like to see
a comment if it is correct.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

#143Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Jeevan Ladhe (#141)
Re: Adding support for Default partition in partitioning

On 2017/06/21 21:37, Jeevan Ladhe wrote:

Hi Amit,

On Thu, Jun 15, 2017 at 12:31 PM, Amit Langote <
Langote_Amit_f8@lab.ntt.co.jp> wrote:

Oops, I meant to send one more comment.

On 2017/06/15 15:48, Amit Langote wrote:

BTW, I noticed the following in 0002

+ errmsg("there exists a default
partition for table \"%s\", cannot
add a new partition",

This error message style seems novel to me. I'm not sure about the best
message text here, but maybe: "cannot add new partition to table \"%s\"
with default partition"

This sounds confusing to me, what about something like:
"\"%s\" has a default partition, cannot add a new partition."

It's the comma inside the error message that suggests to me that it's a
style that I haven't seen elsewhere in the backend code. The primary
error message here is that the new partition cannot be created. "%s has
default partition" seems to me to belong in errdetail() (see "What Goes
Where" in [1]https://www.postgresql.org/docs/devel/static/error-style-guide.html.)

Or write the sentence such that the comma is not required. Anyway, we can
leave this for the committer to decide.

Note that this comment belongs to patch 0002, and it will go away
in case we are going to have extended functionality i.e. patch 0003,
as in that patch we allow user to create a new partition even in the
cases when there exists a default partition.

Oh, that'd be great. It's always better to get rid of the error
conditions that are hard to communicate to users. :) (Although, this
one's not that ambiguous.)

Thanks,
Amit

[1]: https://www.postgresql.org/docs/devel/static/error-style-guide.html

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#144Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#143)
Re: Adding support for Default partition in partitioning

On Wed, Jun 21, 2017 at 8:47 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

It's the comma inside the error message that suggests to me that it's a
style that I haven't seen elsewhere in the backend code.

Exactly. Avoid that.

--
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

#145Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Amit Langote (#139)
Re: Adding support for Default partition in partitioning

Hi,

On Mon, Jun 19, 2017 at 12:34 PM, Amit Langote <
Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2017/06/16 14:16, Ashutosh Bapat wrote:

On Fri, Jun 16, 2017 at 12:48 AM, Robert Haas <robertmhaas@gmail.com>

wrote:

On Thu, Jun 15, 2017 at 12:54 PM, Ashutosh Bapat
<ashutosh.bapat@enterprisedb.com> wrote:

Some more comments on the latest set of patches.

or looking up the OID in the
relcache multiple times.

I am not able to understand this in the context of default partition.
After that nobody else is going to change its partitions and their
bounds (since both of those require heap_open on parent which would be
stuck on the lock we hold.). So, we have to check only once if the
table has a default partition. If it doesn't, it's not going to
acquire one unless we release the lock on the parent i.e at the end of
transaction. If it has one, it's not going to get dropped till the end
of the transaction for the same reason. I don't see where we are
looking up OIDs multiple times.

Without heap_opening the parent, the only way is to look up parentOid's
children in pg_inherits and for each child looking up its pg_class tuple
in the syscache to see if its relpartbound indicates that it's a default
partition. That seems like it won't be inexpensive either.

It would be nice if could get that information (that is - is a given
relation being heap_drop_with_catalog'd a partition of the parent that
happens to have default partition) in less number of steps than that.
Having that information in relcache is one way, but as mentioned, that
turns out be expensive.

Has anyone considered the idea of putting the default partition OID in the
pg_partitioned_table catalog? Looking the above information up would
amount to one syscache lookup. Default partition seems to be special
enough object to receive a place in the pg_partitioned_table tuple of the
parent. Thoughts?

I liked this suggestion. Having an entry in pg_partitioned_table would avoid
both expensive methods, i.e. 1. opening the parent or 2. lookup for
each of the children first in pg_inherits and then its corresponding entry
in
pg_class.
Unless anybody has any other suggestions/comments here, I am going to
implement this suggestion.

Thanks,
Jeevan Ladhe

#146Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Jeevan Ladhe (#145)
1 attachment(s)
Re: Adding support for Default partition in partitioning

Hi,

I have worked further on V21 patch set, rebased it on latest master commit,
addressed the comments given by Robert, Ashutosh and others.

The attached tar has a series of 7 patches.
Here is a brief of these 7 patches:

0001:
Refactoring existing ATExecAttachPartition code so that it can be used for
default partitioning as well

0002:
This patch teaches the partitioning code to handle the NIL returned by
get_qual_for_list().
This is needed because a default partition will not have any constraints in
case
it is the only partition of its parent.

0003:
Support for default partition with the restriction of preventing addition
of any
new partition after default partition.

0004:
Store the default partition OID in pg_partition_table, this will help us to
retrieve the OID of default relation when we don't have the relation cache
available. This was also suggested by Amit Langote here[1]/messages/by-id/35d68d49-555f-421a-99f8-185a44d085a4@lab.ntt.co.jp.

0005:
Extend default partitioning support to allow addition of new partitions.

0006:
Extend default partitioning validation code to reuse the refactored code in
patch 0001.

0007:
This patch introduces code to check if the scanning of default partition
child
can be skipped if it's constraints are proven.

TODO:
Add documentation.
Merge default range partitioning patch.
[1]: /messages/by-id/35d68d49-555f-421a-99f8-185a44d085a4@lab.ntt.co.jp
/messages/by-id/35d68d49-555f-421a-99f8-185a44d085a4@lab.ntt.co.jp

Regards,
Jeevan Ladhe

On Fri, Jun 30, 2017 at 5:48 PM, Jeevan Ladhe <jeevan.ladhe@enterprisedb.com

Show quoted text

wrote:

Hi,

On Mon, Jun 19, 2017 at 12:34 PM, Amit Langote <
Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2017/06/16 14:16, Ashutosh Bapat wrote:

On Fri, Jun 16, 2017 at 12:48 AM, Robert Haas <robertmhaas@gmail.com>

wrote:

On Thu, Jun 15, 2017 at 12:54 PM, Ashutosh Bapat
<ashutosh.bapat@enterprisedb.com> wrote:

Some more comments on the latest set of patches.

or looking up the OID in the
relcache multiple times.

I am not able to understand this in the context of default partition.
After that nobody else is going to change its partitions and their
bounds (since both of those require heap_open on parent which would be
stuck on the lock we hold.). So, we have to check only once if the
table has a default partition. If it doesn't, it's not going to
acquire one unless we release the lock on the parent i.e at the end of
transaction. If it has one, it's not going to get dropped till the end
of the transaction for the same reason. I don't see where we are
looking up OIDs multiple times.

Without heap_opening the parent, the only way is to look up parentOid's
children in pg_inherits and for each child looking up its pg_class tuple
in the syscache to see if its relpartbound indicates that it's a default
partition. That seems like it won't be inexpensive either.

It would be nice if could get that information (that is - is a given
relation being heap_drop_with_catalog'd a partition of the parent that
happens to have default partition) in less number of steps than that.
Having that information in relcache is one way, but as mentioned, that
turns out be expensive.

Has anyone considered the idea of putting the default partition OID in the
pg_partitioned_table catalog? Looking the above information up would
amount to one syscache lookup. Default partition seems to be special
enough object to receive a place in the pg_partitioned_table tuple of the
parent. Thoughts?

I liked this suggestion. Having an entry in pg_partitioned_table would
avoid
both expensive methods, i.e. 1. opening the parent or 2. lookup for
each of the children first in pg_inherits and then its corresponding entry
in
pg_class.
Unless anybody has any other suggestions/comments here, I am going to
implement this suggestion.

Thanks,
Jeevan Ladhe

Attachments:

default_partition_V22.tarapplication/x-tar; name=default_partition_V22.tarDownload
0001-Refactor-ATExecAttachPartition.patch0000664000175000017500000002532613131471566017673 0ustar  jeevanjeevanFrom 998124fdb8018c14dc8d8fac36c1887ed9301a0e Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Thu, 13 Jul 2017 00:27:01 +0530
Subject: [PATCH 1/7] Refactor ATExecAttachPartition

Move code to validate the table being attached against the partition
constraints into set of functions. This will be used for validating
the changed default partition constraints when a new partition is
added.

Ashutosh Bapat, revised by me.
---
 src/backend/commands/tablecmds.c | 311 +++++++++++++++++++++------------------
 1 file changed, 165 insertions(+), 146 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index bb00858..7964770 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13412,6 +13412,168 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
 }
 
 /*
+ * canSkipPartConstraintValidation
+ *
+ * Check if we can do away with having to scan the given table to validate
+ * given constraint by proving that the existing constraints of the table imply
+ * the given constraints.  We include the table's check constraints and NOT
+ * NULL constraints in the list of clauses passed to predicate_implied_by().
+ *
+ * The function returns true if it's safe to skip the scan, false otherwise.
+ */
+static bool
+canSkipPartConstraintValidation(Relation scanRel, List *partConstraint,
+								PartitionKey key)
+{
+	TupleDesc	tupleDesc = RelationGetDescr(scanRel);
+	TupleConstr *scanRel_constr = tupleDesc->constr;
+	int			i;
+	List	   *existConstraint = NIL;
+
+	if (scanRel_constr == NULL)
+		return false;
+
+	if (scanRel_constr->has_not_null)
+	{
+		int			natts = scanRel->rd_att->natts;
+
+		for (i = 1; i <= natts; i++)
+		{
+			Form_pg_attribute att = scanRel->rd_att->attrs[i - 1];
+
+			if (att->attnotnull && !att->attisdropped)
+			{
+				NullTest   *ntest = makeNode(NullTest);
+
+				ntest->arg = (Expr *) makeVar(1,
+											  i,
+											  att->atttypid,
+											  att->atttypmod,
+											  att->attcollation,
+											  0);
+				ntest->nulltesttype = IS_NOT_NULL;
+
+				/*
+				 * argisrow=false is correct even for a composite column,
+				 * because attnotnull does not represent a SQL-spec IS NOT
+				 * NULL test in such a case, just IS DISTINCT FROM NULL.
+				 */
+				ntest->argisrow = false;
+				ntest->location = -1;
+				existConstraint = lappend(existConstraint, ntest);
+			}
+		}
+	}
+
+	for (i = 0; i < scanRel_constr->num_check; i++)
+	{
+		Node	   *cexpr;
+
+		/*
+		 * If this constraint hasn't been fully validated yet, we must ignore
+		 * it here.
+		 */
+		if (!scanRel_constr->check[i].ccvalid)
+			continue;
+
+		cexpr = stringToNode(scanRel_constr->check[i].ccbin);
+
+		/*
+		 * Run each expression through const-simplification and
+		 * canonicalization.  It is necessary, because we will be comparing it
+		 * to similarly-processed qual clauses, and may fail to detect valid
+		 * matches without this.
+		 */
+		cexpr = eval_const_expressions(NULL, cexpr);
+		cexpr = (Node *) canonicalize_qual((Expr *) cexpr);
+
+		existConstraint = list_concat(existConstraint,
+									  make_ands_implicit((Expr *) cexpr));
+	}
+
+	existConstraint = list_make1(make_ands_explicit(existConstraint));
+
+	/* And away we go ... */
+	return predicate_implied_by(partConstraint, existConstraint, true);
+}
+
+/*
+ * validatePartitionConstraints
+ *
+ * Check whether all rows in the given table obey the given partition
+ * constraint; if so, it can be attached as a partition.  We do this by
+ * scanning the table (or all of its leaf partitions) row by row, except when
+ * the existing constraints are sufficient to prove that the new partitioning
+ * constraint must already hold.
+ */
+static void
+validatePartitionConstraints(List **wqueue, Relation scanRel,
+							 List *partConstraint, Relation rel)
+{
+	PartitionKey key = RelationGetPartitionKey(rel);
+	List	   *all_parts;
+	ListCell   *lc;
+
+	/* If possible, skip the validation scan. */
+	if (canSkipPartConstraintValidation(scanRel, partConstraint, key))
+	{
+		ereport(INFO,
+				(errmsg("partition constraint for table \"%s\" is implied by existing constraints",
+						RelationGetRelationName(scanRel))));
+		return;
+	}
+
+	/*
+	 * Prepare to scan the default partition (or, if it is itself partitioned,
+	 * all of its leaf partitions).
+	 *
+	 * Take an exclusive lock on the partitions to be checked.
+	 */
+	if (scanRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		all_parts = find_all_inheritors(RelationGetRelid(scanRel),
+										AccessExclusiveLock, NULL);
+	else
+		all_parts = list_make1_oid(RelationGetRelid(scanRel));
+
+	foreach(lc, all_parts)
+	{
+		AlteredTableInfo *tab;
+		Oid			part_relid = lfirst_oid(lc);
+		Relation	part_rel;
+		Expr	   *constr;
+
+		/* Lock already taken */
+		if (part_relid != RelationGetRelid(scanRel))
+			part_rel = heap_open(part_relid, NoLock);
+		else
+			part_rel = scanRel;
+
+		/*
+		 * Skip if it's a partitioned table.  Only RELKIND_RELATION relations
+		 * (ie, leaf partitions) need to be scanned.
+		 */
+		if (part_rel != scanRel &&
+			part_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			heap_close(part_rel, NoLock);
+			continue;
+		}
+
+		/* Grab a work queue entry */
+		tab = ATGetQueueEntry(wqueue, part_rel);
+
+		/* Adjust constraint to match this partition */
+		constr = linitial(partConstraint);
+		tab->partition_constraint = (Expr *)
+			map_partition_varattnos((List *) constr, 1,
+									part_rel, rel);
+		/* keep our lock until commit */
+		if (part_rel != scanRel)
+			heap_close(part_rel, NoLock);
+	}
+}
+
+/*
  * ALTER TABLE <name> ATTACH PARTITION <partition-name> FOR VALUES
  *
  * Return the address of the newly attached partition.
@@ -13422,15 +13584,12 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	Relation	attachRel,
 				catalog;
 	List	   *childrels;
-	TupleConstr *attachRel_constr;
-	List	   *partConstraint,
-			   *existConstraint;
+	List	   *partConstraint;
 	SysScanDesc scan;
 	ScanKeyData skey;
 	AttrNumber	attno;
 	int			natts;
 	TupleDesc	tupleDesc;
-	bool		skip_validate = false;
 	ObjectAddress address;
 	const char *trigger_name;
 
@@ -13602,148 +13761,8 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
 	partConstraint = list_make1(make_ands_explicit(partConstraint));
 
-	/*
-	 * Check if we can do away with having to scan the table being attached to
-	 * validate the partition constraint, by *proving* that the existing
-	 * constraints of the table *imply* the partition predicate.  We include
-	 * the table's check constraints and NOT NULL constraints in the list of
-	 * clauses passed to predicate_implied_by().
-	 *
-	 * There is a case in which we cannot rely on just the result of the
-	 * proof.
-	 */
-	attachRel_constr = tupleDesc->constr;
-	existConstraint = NIL;
-	if (attachRel_constr != NULL)
-	{
-		int			num_check = attachRel_constr->num_check;
-		int			i;
-
-		if (attachRel_constr->has_not_null)
-		{
-			int			natts = attachRel->rd_att->natts;
-
-			for (i = 1; i <= natts; i++)
-			{
-				Form_pg_attribute att = attachRel->rd_att->attrs[i - 1];
-
-				if (att->attnotnull && !att->attisdropped)
-				{
-					NullTest   *ntest = makeNode(NullTest);
-
-					ntest->arg = (Expr *) makeVar(1,
-												  i,
-												  att->atttypid,
-												  att->atttypmod,
-												  att->attcollation,
-												  0);
-					ntest->nulltesttype = IS_NOT_NULL;
-
-					/*
-					 * argisrow=false is correct even for a composite column,
-					 * because attnotnull does not represent a SQL-spec IS NOT
-					 * NULL test in such a case, just IS DISTINCT FROM NULL.
-					 */
-					ntest->argisrow = false;
-					ntest->location = -1;
-					existConstraint = lappend(existConstraint, ntest);
-				}
-			}
-		}
-
-		for (i = 0; i < num_check; i++)
-		{
-			Node	   *cexpr;
-
-			/*
-			 * If this constraint hasn't been fully validated yet, we must
-			 * ignore it here.
-			 */
-			if (!attachRel_constr->check[i].ccvalid)
-				continue;
-
-			cexpr = stringToNode(attachRel_constr->check[i].ccbin);
-
-			/*
-			 * Run each expression through const-simplification and
-			 * canonicalization.  It is necessary, because we will be
-			 * comparing it to similarly-processed qual clauses, and may fail
-			 * to detect valid matches without this.
-			 */
-			cexpr = eval_const_expressions(NULL, cexpr);
-			cexpr = (Node *) canonicalize_qual((Expr *) cexpr);
-
-			existConstraint = list_concat(existConstraint,
-										  make_ands_implicit((Expr *) cexpr));
-		}
-
-		existConstraint = list_make1(make_ands_explicit(existConstraint));
-
-		/* And away we go ... */
-		if (predicate_implied_by(partConstraint, existConstraint, true))
-			skip_validate = true;
-	}
-
-	/* It's safe to skip the validation scan after all */
-	if (skip_validate)
-		ereport(INFO,
-				(errmsg("partition constraint for table \"%s\" is implied by existing constraints",
-						RelationGetRelationName(attachRel))));
-
-	/*
-	 * Set up to have the table be scanned to validate the partition
-	 * constraint (see partConstraint above).  If it's a partitioned table, we
-	 * instead schedule its leaf partitions to be scanned.
-	 */
-	if (!skip_validate)
-	{
-		List	   *all_parts;
-		ListCell   *lc;
-
-		/* Take an exclusive lock on the partitions to be checked */
-		if (attachRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
-			all_parts = find_all_inheritors(RelationGetRelid(attachRel),
-											AccessExclusiveLock, NULL);
-		else
-			all_parts = list_make1_oid(RelationGetRelid(attachRel));
-
-		foreach(lc, all_parts)
-		{
-			AlteredTableInfo *tab;
-			Oid			part_relid = lfirst_oid(lc);
-			Relation	part_rel;
-			Expr	   *constr;
-
-			/* Lock already taken */
-			if (part_relid != RelationGetRelid(attachRel))
-				part_rel = heap_open(part_relid, NoLock);
-			else
-				part_rel = attachRel;
-
-			/*
-			 * Skip if it's a partitioned table.  Only RELKIND_RELATION
-			 * relations (ie, leaf partitions) need to be scanned.
-			 */
-			if (part_rel != attachRel &&
-				part_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
-			{
-				heap_close(part_rel, NoLock);
-				continue;
-			}
-
-			/* Grab a work queue entry */
-			tab = ATGetQueueEntry(wqueue, part_rel);
-
-			/* Adjust constraint to match this partition */
-			constr = linitial(partConstraint);
-			tab->partition_constraint = (Expr *)
-				map_partition_varattnos((List *) constr, 1,
-										part_rel, rel);
-			/* keep our lock until commit */
-			if (part_rel != attachRel)
-				heap_close(part_rel, NoLock);
-		}
-	}
+	/* Validate partition constraints against the table being attached. */
+	validatePartitionConstraints(wqueue, attachRel, partConstraint, rel);
 
 	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
 
-- 
1.9.1

0002-Fix-assumptions-that-get_qual_from_partbound-cannot.patch0000664000175000017500000000511513131471566024222 0ustar  jeevanjeevanFrom 73ddc43933978f2ccc567c09c376abbcb1dad20e Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Thu, 13 Jul 2017 00:27:57 +0530
Subject: [PATCH 2/7] Fix assumptions that get_qual_from_partbound() cannot
 return NIL list.

Current partitioning code assumes that there cannot be any partition
without partition constraints, but in future this assumption might
not hold true.
e.g. if we introduce support for default partition, then default
partition will not have any constraints in case it is the only
partition of its parent.

Jeevan Ladhe.
---
 src/backend/catalog/partition.c  |  5 ++++-
 src/backend/commands/tablecmds.c | 17 +++++++++++------
 2 files changed, 15 insertions(+), 7 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 43b8924..96e602d 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -974,7 +974,10 @@ get_partition_qual_relid(Oid relid)
 	if (rel->rd_rel->relispartition)
 	{
 		and_args = generate_partition_qual(rel);
-		if (list_length(and_args) > 1)
+
+		if (!and_args)
+			result = NULL;
+		else if (list_length(and_args) > 1)
 			result = makeBoolExpr(AND_EXPR, and_args, -1);
 		else
 			result = linitial(and_args);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 7964770..f24602d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13756,13 +13756,18 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	partConstraint = list_concat(get_qual_from_partbound(attachRel, rel,
 														 cmd->bound),
 								 RelationGetPartitionQual(rel));
-	partConstraint = (List *) eval_const_expressions(NULL,
-													 (Node *) partConstraint);
-	partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
-	partConstraint = list_make1(make_ands_explicit(partConstraint));
 
-	/* Validate partition constraints against the table being attached. */
-	validatePartitionConstraints(wqueue, attachRel, partConstraint, rel);
+	/* Skip validation if there are no constraints to validate. */
+	if (partConstraint)
+	{
+		partConstraint =
+			(List *) eval_const_expressions(NULL, (Node *) partConstraint);
+		partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
+		partConstraint = list_make1(make_ands_explicit(partConstraint));
+
+		/* Validate partition constraints against the table being attached. */
+		validatePartitionConstraints(wqueue, attachRel, partConstraint, rel);
+	}
 
 	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
 
-- 
1.9.1

0003-Implement-default-partition-support.patch0000664000175000017500000012216113131471566021072 0ustar  jeevanjeevanFrom 89f636c7d3139b36132000214428b4ee5d95c8e1 Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Thu, 13 Jul 2017 00:31:28 +0530
Subject: [PATCH 3/7] Implement default partition support

This patch introduces default partition for a list partitioned
table. However, in this version of patch no other partition can
be attached to a list partitioned table if it has a default
partition.

Jeevan Ladhe.
---
 src/backend/catalog/heap.c                 |  33 +++++-
 src/backend/catalog/partition.c            | 159 ++++++++++++++++++++++++++---
 src/backend/commands/tablecmds.c           |  39 ++++++-
 src/backend/nodes/copyfuncs.c              |   1 +
 src/backend/nodes/equalfuncs.c             |   1 +
 src/backend/nodes/outfuncs.c               |   1 +
 src/backend/nodes/readfuncs.c              |   1 +
 src/backend/parser/gram.y                  |  27 +++--
 src/backend/parser/parse_utilcmd.c         |  19 ++++
 src/backend/utils/adt/ruleutils.c          |  12 ++-
 src/bin/psql/tab-complete.c                |   4 +-
 src/include/catalog/partition.h            |   2 +
 src/include/nodes/parsenodes.h             |   1 +
 src/test/regress/expected/alter_table.out  |  14 +++
 src/test/regress/expected/create_table.out |  10 ++
 src/test/regress/expected/insert.out       |  56 ++++++++++
 src/test/regress/expected/plancache.out    |  22 ++++
 src/test/regress/expected/update.out       |  15 +++
 src/test/regress/sql/alter_table.sql       |  13 +++
 src/test/regress/sql/create_table.sql      |   8 ++
 src/test/regress/sql/insert.sql            |  32 ++++++
 src/test/regress/sql/plancache.sql         |  19 ++++
 src/test/regress/sql/update.sql            |  15 +++
 23 files changed, 474 insertions(+), 30 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index a376b99..068c3bd 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1755,9 +1755,11 @@ RemoveAttrDefaultById(Oid attrdefId)
 void
 heap_drop_with_catalog(Oid relid)
 {
-	Relation	rel;
+	Relation	rel,
+				parentRel;
 	HeapTuple	tuple;
-	Oid			parentOid = InvalidOid;
+	Oid			parentOid = InvalidOid,
+				defaultPartOid = InvalidOid;
 
 	/*
 	 * To drop a partition safely, we must grab exclusive lock on its parent,
@@ -1772,7 +1774,22 @@ heap_drop_with_catalog(Oid relid)
 	if (((Form_pg_class) GETSTRUCT(tuple))->relispartition)
 	{
 		parentOid = get_partition_parent(relid);
-		LockRelationOid(parentOid, AccessExclusiveLock);
+		parentRel = heap_open(parentOid, AccessExclusiveLock);
+
+		/*
+		 * The partition constraint of the default partition depends on the
+		 * partition bounds of every other partition. It is possible that
+		 * other backend might be about to execute a query on the default
+		 * partition table and the query relies on previously cached default
+		 * partition constraints, which won't be correct after removal of a
+		 * partition. We must therefore take a table lock strong enough to
+		 * prevent all queries on the default partition from proceeding until
+		 * we commit and send out a shared-cache-inval notice that will make
+		 * them update their index lists.
+		 */
+		defaultPartOid = get_default_partition_oid(parentRel);
+		if (OidIsValid(defaultPartOid))
+			LockRelationOid(defaultPartOid, AccessExclusiveLock);
 	}
 
 	ReleaseSysCache(tuple);
@@ -1883,11 +1900,21 @@ heap_drop_with_catalog(Oid relid)
 	if (OidIsValid(parentOid))
 	{
 		/*
+		 * The partition constraint for the default partition depends on the
+		 * partition bounds of every other partition, so we must invalidate
+		 * the relcache entry for that partition every time a partition is
+		 * added or removed.
+		 */
+		if (OidIsValid(defaultPartOid))
+			CacheInvalidateRelcacheByRelid(defaultPartOid);
+
+		/*
 		 * Invalidate the parent's relcache so that the partition is no longer
 		 * included in its partition descriptor.
 		 */
 		CacheInvalidateRelcacheByRelid(parentOid);
 		/* keep the lock */
+		heap_close(parentRel, NoLock);
 	}
 }
 
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 96e602d..a69015a 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -89,9 +89,12 @@ typedef struct PartitionBoundInfoData
 								 * partitioned table) */
 	int			null_index;		/* Index of the null-accepting partition; -1
 								 * if there isn't one */
+	int			default_index;	/* Index of the default partition; -1 if there
+								 * isn't one */
 } 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
@@ -129,7 +132,7 @@ static void get_range_key_properties(PartitionKey key, int keynum,
 						 ListCell **partexprs_item,
 						 Expr **keyCol,
 						 Const **lower_val, Const **upper_val);
-static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_list(Relation parent, PartitionBoundSpec *spec);
 static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
 static List *generate_partition_qual(Relation rel);
 
@@ -175,6 +178,7 @@ RelationBuildPartitionDesc(Relation rel)
 	/* List partitioning specific */
 	PartitionListValue **all_values = NULL;
 	int			null_index = -1;
+	int			default_index = -1;
 
 	/* Range partitioning specific */
 	PartitionRangeBound **rbounds = NULL;
@@ -255,6 +259,18 @@ RelationBuildPartitionDesc(Relation rel)
 				if (spec->strategy != PARTITION_STRATEGY_LIST)
 					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 list of non-null
+				 * datums for this partition.
+				 */
+				if (spec->is_default)
+				{
+					default_index = i;
+					i++;
+					continue;
+				}
+
 				foreach(c, spec->listdatums)
 				{
 					Const	   *val = castNode(Const, lfirst(c));
@@ -454,6 +470,7 @@ RelationBuildPartitionDesc(Relation rel)
 		boundinfo = (PartitionBoundInfoData *)
 			palloc0(sizeof(PartitionBoundInfoData));
 		boundinfo->strategy = key->strategy;
+		boundinfo->default_index = -1;
 		boundinfo->ndatums = ndatums;
 		boundinfo->null_index = -1;
 		boundinfo->datums = (Datum **) palloc0(ndatums * sizeof(Datum *));
@@ -506,6 +523,20 @@ RelationBuildPartitionDesc(Relation rel)
 						boundinfo->null_index = mapping[null_index];
 					}
 
+					/* Assign mapped index for the default partition. */
+					if (default_index != -1)
+					{
+						/*
+						 * The default partition accepts any non-specified
+						 * value, hence it should not get a mapped index while
+						 * assigning those for non-null datums.
+						 */
+						Assert(default_index >= 0 &&
+							   mapping[default_index] == -1);
+						mapping[default_index] = next_index++;
+						boundinfo->default_index = mapping[default_index];
+					}
+
 					/* All partition must now have a valid mapping */
 					Assert(next_index == nparts);
 					break;
@@ -610,6 +641,9 @@ partition_bounds_equal(PartitionKey key,
 	if (b1->null_index != b2->null_index)
 		return false;
 
+	if (b1->default_index != b2->default_index)
+		return false;
+
 	for (i = 0; i < b1->ndatums; i++)
 	{
 		int			j;
@@ -890,7 +924,7 @@ get_qual_from_partbound(Relation rel, Relation parent,
 	{
 		case PARTITION_STRATEGY_LIST:
 			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
-			my_qual = get_qual_for_list(key, spec);
+			my_qual = get_qual_for_list(parent, spec);
 			break;
 
 		case PARTITION_STRATEGY_RANGE:
@@ -1288,10 +1322,14 @@ make_partition_op_expr(PartitionKey key, int keynum,
  *
  * Returns an implicit-AND list of expressions to use as a list partition's
  * constraint, given the partition key and bound structures.
+ *
+ * The function returns NIL for a default partition when it's the only
+ * partition since it can not have any partition constraint.
  */
 static List *
-get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
+get_qual_for_list(Relation parent, PartitionBoundSpec *spec)
 {
+	PartitionKey key = RelationGetPartitionKey(parent);
 	List	   *result;
 	Expr	   *keyCol;
 	ArrayExpr  *arr;
@@ -1318,15 +1356,63 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 	else
 		keyCol = (Expr *) copyObject(linitial(key->partexprs));
 
-	/* Create list of Consts for the allowed values, excluding any nulls */
-	foreach(cell, spec->listdatums)
+	/*
+	 * For default list partition, collect datums for all the partitions. The
+	 * default partition constraint should check that the partition key is
+	 * equal to none of those.
+	 */
+	if (spec->is_default)
 	{
-		Const	   *val = castNode(Const, lfirst(cell));
+		int			i;
+		int			ndatums = 0;
+		PartitionDesc pdesc = RelationGetPartitionDesc(parent);
+		PartitionBoundInfo boundinfo = pdesc->boundinfo;
 
-		if (val->constisnull)
-			list_has_null = true;
-		else
-			arrelems = lappend(arrelems, copyObject(val));
+		if (boundinfo)
+		{
+			ndatums = boundinfo->ndatums;
+
+			if (partition_bound_accepts_nulls(boundinfo))
+				list_has_null = true;
+		}
+
+		/*
+		 * If default is the only partition, there need not be any partition
+		 * constraint on it.
+		 */
+		if (ndatums == 0 && !list_has_null)
+			return NIL;
+
+		for (i = 0; i < ndatums; i++)
+		{
+			Const	   *val;
+
+			/* Construct const from datum */
+			val = makeConst(key->parttypid[0],
+							key->parttypmod[0],
+							key->parttypcoll[0],
+							key->parttyplen[0],
+							*boundinfo->datums[i],
+							false,	/* isnull */
+							key->parttypbyval[0]);
+
+			arrelems = lappend(arrelems, val);
+		}
+	}
+	else
+	{
+		/*
+		 * Create list of Consts for the allowed values, excluding any nulls.
+		 */
+		foreach(cell, spec->listdatums)
+		{
+			Const	   *val = castNode(Const, lfirst(cell));
+
+			if (val->constisnull)
+				list_has_null = true;
+			else
+				arrelems = lappend(arrelems, copyObject(val));
+		}
 	}
 
 	if (arrelems)
@@ -1390,6 +1476,25 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 			result = list_make1(nulltest);
 	}
 
+	/*
+	 * In case of the default partition, the constraint is of the form
+	 * "!(result)" i.e. one of the following two forms:
+	 *
+	 * 1. NOT ((keycol IS NULL) OR (keycol = ANY (arr)))
+	 *
+	 * 2. NOT ((keycol IS NOT NULL) AND (keycol = ANY (arr)))
+	 *
+	 * Note that, in general, applying NOT to a constraint expression doesn't
+	 * necessarily invert the set of rows it accepts, because NOT (NULL) is
+	 * NULL.  However, the partition constraints we construct here never
+	 * evaluate to NULL, so applying NOT works as intended.
+	 */
+	if (spec->is_default)
+	{
+		result = list_make1(make_ands_explicit(result));
+		result = list_make1(makeBoolExpr(NOT_EXPR, result, -1));
+	}
+
 	return result;
 }
 
@@ -1997,8 +2102,8 @@ get_partition_for_tuple(PartitionDispatch *pd,
 		}
 
 		/*
-		 * A null partition key is only acceptable if null-accepting list
-		 * partition exists.
+		 * If this is a NULL, route it to the null-accepting partition. 
+		 * Otherwise, route by searching the array of partition bounds.
 		 */
 		cur_index = -1;
 		if (isnull[0] && partition_bound_accepts_nulls(partdesc->boundinfo))
@@ -2036,11 +2141,17 @@ get_partition_for_tuple(PartitionDispatch *pd,
 		}
 
 		/*
-		 * cur_index < 0 means we failed to find a partition of this parent.
-		 * cur_index >= 0 means we either found the leaf partition, or the
-		 * next parent to find a partition of.
+		 * cur_index < 0 means we could not find a non-default partition of
+		 * this parent. cur_index >= 0 means we either found the leaf
+		 * partition, or the next parent to find a partition of.
+		 *
+		 * If we couldn't find a non-default partition check if the default
+		 * partition exists, if it does, get its index.
 		 */
 		if (cur_index < 0)
+			cur_index = partdesc->boundinfo->default_index;
+
+		if (cur_index < 0)
 		{
 			result = -1;
 			*failed_at = parent;
@@ -2336,3 +2447,21 @@ partition_bound_bsearch(PartitionKey key, PartitionBoundInfo boundinfo,
 
 	return lo;
 }
+
+/*
+ * get_default_partition_oid
+ *
+ * If the given relation has a default partition return the OID of the default
+ * partition, otherwise return InvalidOid.
+ */
+Oid
+get_default_partition_oid(Relation parent)
+{
+	PartitionDesc partdesc = RelationGetPartitionDesc(parent);
+
+	if (partdesc && partdesc->boundinfo &&
+		partition_bound_has_default(partdesc->boundinfo))
+		return partdesc->oids[partdesc->boundinfo->default_index];
+
+	return InvalidOid;
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f24602d..bcca46b 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -767,7 +767,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	{
 		PartitionBoundSpec *bound;
 		ParseState *pstate;
-		Oid			parentId = linitial_oid(inheritOids);
+		Oid			parentId = linitial_oid(inheritOids),
+					defaultPartOid;
 		Relation	parent;
 
 		/* Already have strong enough lock on the parent */
@@ -783,6 +784,17 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 					 errmsg("\"%s\" is not partitioned",
 							RelationGetRelationName(parent))));
 
+		/*
+		 * A table cannot be created as a partition of a parent already having
+		 * a default partition.
+		 */
+		defaultPartOid = get_default_partition_oid(parent);
+		if (OidIsValid(defaultPartOid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("cannot add a new partition to table \"%s\" having a default partition",
+							RelationGetRelationName(parent))));
+
 		/* Tranform the bound values */
 		pstate = make_parsestate(NULL);
 		pstate->p_sourcetext = queryString;
@@ -13591,8 +13603,17 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	int			natts;
 	TupleDesc	tupleDesc;
 	ObjectAddress address;
+	Oid			defaultPartOid;
 	const char *trigger_name;
 
+	/* A partition cannot be attached if there exists a default partition */
+	defaultPartOid = get_default_partition_oid(rel);
+	if (OidIsValid(defaultPartOid))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot attach a new partition to table \"%s\" having a default partition",
+						RelationGetRelationName(rel))));
+
 	attachRel = heap_openrv(cmd->name, AccessExclusiveLock);
 
 	/*
@@ -13794,6 +13815,15 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 				new_null[Natts_pg_class],
 				new_repl[Natts_pg_class];
 	ObjectAddress address;
+	Oid			defaultPartOid;
+
+	/*
+	 * We must also lock the default partition, for the same reasons explained
+	 * in heap_drop_with_catalog().
+	 */
+	defaultPartOid = get_default_partition_oid(rel);
+	if (OidIsValid(defaultPartOid))
+		LockRelationOid(defaultPartOid, AccessExclusiveLock);
 
 	partRel = heap_openrv(name, AccessShareLock);
 
@@ -13826,6 +13856,13 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 	heap_close(classRel, RowExclusiveLock);
 
 	/*
+	 * We must invalidate default partition's relcache, for the same reasons
+	 * explained in heap_drop_with_catalog().
+	 */
+	if (OidIsValid(defaultPartOid))
+		CacheInvalidateRelcacheByRelid(defaultPartOid);
+
+	/*
 	 * Invalidate the parent's relcache so that the partition is no longer
 	 * included in its partition descriptor.
 	 */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 67ac814..a4ba58a 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4445,6 +4445,7 @@ _copyPartitionBoundSpec(const PartitionBoundSpec *from)
 	PartitionBoundSpec *newnode = makeNode(PartitionBoundSpec);
 
 	COPY_SCALAR_FIELD(strategy);
+	COPY_SCALAR_FIELD(is_default);
 	COPY_NODE_FIELD(listdatums);
 	COPY_NODE_FIELD(lowerdatums);
 	COPY_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 91d64b7..373f050 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2838,6 +2838,7 @@ static bool
 _equalPartitionBoundSpec(const PartitionBoundSpec *a, const PartitionBoundSpec *b)
 {
 	COMPARE_SCALAR_FIELD(strategy);
+	COMPARE_SCALAR_FIELD(is_default);
 	COMPARE_NODE_FIELD(listdatums);
 	COMPARE_NODE_FIELD(lowerdatums);
 	COMPARE_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index b0abe9e..a633f63 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3562,6 +3562,7 @@ _outPartitionBoundSpec(StringInfo str, const PartitionBoundSpec *node)
 	WRITE_NODE_TYPE("PARTITIONBOUNDSPEC");
 
 	WRITE_CHAR_FIELD(strategy);
+	WRITE_BOOL_FIELD(is_default);
 	WRITE_NODE_FIELD(listdatums);
 	WRITE_NODE_FIELD(lowerdatums);
 	WRITE_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 1380703..8ed168b 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2374,6 +2374,7 @@ _readPartitionBoundSpec(void)
 	READ_LOCALS(PartitionBoundSpec);
 
 	READ_CHAR_FIELD(strategy);
+	READ_BOOL_FIELD(is_default);
 	READ_NODE_FIELD(listdatums);
 	READ_NODE_FIELD(lowerdatums);
 	READ_NODE_FIELD(upperdatums);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0f3998f..93c42bc 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -575,7 +575,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>			part_strategy
 %type <partelem>	part_elem
 %type <list>		part_params
-%type <partboundspec> ForValues
+%type <partboundspec> PartitionBoundSpec
 %type <node>		partbound_datum PartitionRangeDatum
 %type <list>		partbound_datum_list range_datum_list
 
@@ -2011,7 +2011,7 @@ alter_table_cmds:
 
 partition_cmd:
 			/* ALTER TABLE <name> ATTACH PARTITION <table_name> FOR VALUES */
-			ATTACH PARTITION qualified_name ForValues
+			ATTACH PARTITION qualified_name PartitionBoundSpec
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
 					PartitionCmd *cmd = makeNode(PartitionCmd);
@@ -2650,13 +2650,14 @@ alter_identity_column_option:
 				}
 		;
 
-ForValues:
+PartitionBoundSpec:
 			/* a LIST partition */
 			FOR VALUES IN_P '(' partbound_datum_list ')'
 				{
 					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
 
 					n->strategy = PARTITION_STRATEGY_LIST;
+					n->is_default = false;
 					n->listdatums = $5;
 					n->location = @3;
 
@@ -2669,12 +2670,24 @@ ForValues:
 					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
 
 					n->strategy = PARTITION_STRATEGY_RANGE;
+					n->is_default = false;
 					n->lowerdatums = $5;
 					n->upperdatums = $9;
 					n->location = @3;
 
 					$$ = n;
 				}
+
+			/* a DEFAULT partition */
+			| DEFAULT
+				{
+					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+					n->is_default = true;
+					n->location = @1;
+
+					$$ = n;
+				}
 		;
 
 partbound_datum:
@@ -3135,7 +3148,7 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
-			OptTypedTableElementList ForValues OptPartitionSpec OptWith
+			OptTypedTableElementList PartitionBoundSpec OptPartitionSpec OptWith
 			OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
@@ -3154,7 +3167,7 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
-			qualified_name OptTypedTableElementList ForValues OptPartitionSpec
+			qualified_name OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
 			OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
@@ -4869,7 +4882,7 @@ CreateForeignTableStmt:
 					$$ = (Node *) n;
 				}
 		| CREATE FOREIGN TABLE qualified_name
-			PARTITION OF qualified_name OptTypedTableElementList ForValues
+			PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
 			SERVER name create_generic_options
 				{
 					CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
@@ -4890,7 +4903,7 @@ CreateForeignTableStmt:
 					$$ = (Node *) n;
 				}
 		| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
-			PARTITION OF qualified_name OptTypedTableElementList ForValues
+			PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
 			SERVER name create_generic_options
 				{
 					CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index ee5f3a3..57c09c2 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -60,6 +61,7 @@
 #include "parser/parse_utilcmd.h"
 #include "parser/parser.h"
 #include "rewrite/rewriteManip.h"
+#include "storage/lmgr.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
@@ -3303,6 +3305,23 @@ transformPartitionBound(ParseState *pstate, Relation parent,
 	/* Avoid scribbling on input */
 	result_spec = copyObject(spec);
 
+	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
+		 * partition bound spec.
+		 */
+		result_spec->strategy = strategy;
+
+		return result_spec;
+	}
+
 	if (strategy == PARTITION_STRATEGY_LIST)
 	{
 		ListCell   *cell;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 18d9e27..7726499 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8644,10 +8644,18 @@ get_rule_expr(Node *node, deparse_context *context,
 		case T_PartitionBoundSpec:
 			{
 				PartitionBoundSpec *spec = (PartitionBoundSpec *) node;
+				char		strategy = spec->strategy;
 				ListCell   *cell;
 				char	   *sep;
 
-				switch (spec->strategy)
+				if (spec->is_default)
+				{
+					Assert(strategy == PARTITION_STRATEGY_LIST);
+					appendStringInfoString(buf, "DEFAULT");
+					break;
+				}
+
+				switch (strategy)
 				{
 					case PARTITION_STRATEGY_LIST:
 						Assert(spec->listdatums != NIL);
@@ -8713,7 +8721,7 @@ get_rule_expr(Node *node, deparse_context *context,
 
 					default:
 						elog(ERROR, "unrecognized partition strategy: %d",
-							 (int) spec->strategy);
+							 (int) strategy);
 						break;
 				}
 			}
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index e9fdc90..db937a8 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2050,7 +2050,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("ATTACH", "PARTITION", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 	else if (TailMatches2("FOR", "VALUES"))
 		COMPLETE_WITH_LIST2("FROM (", "IN (");
 
@@ -2489,7 +2489,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("PARTITION", "OF", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 
 /* CREATE TABLESPACE */
 	else if (Matches3("CREATE", "TABLESPACE", MatchAny))
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index f10879a..707fca4 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -98,4 +98,6 @@ extern int get_partition_for_tuple(PartitionDispatch *pd,
 						EState *estate,
 						PartitionDispatchData **failed_at,
 						TupleTableSlot **failed_slot);
+extern Oid	get_default_partition_oid(Relation parent);
+
 #endif							/* PARTITION_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 1d96169..387ce05 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -797,6 +797,7 @@ typedef struct PartitionBoundSpec
 	NodeTag		type;
 
 	char		strategy;		/* see PARTITION_STRATEGY codes above */
+	bool		is_default;		/* is it a default partition bound? */
 
 	/* Partitioning info for LIST strategy: */
 	List	   *listdatums;		/* List of Consts (or A_Consts in raw tree) */
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 13d6a4b..0acf7ef 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3273,6 +3273,11 @@ SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::reg
 CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
 ERROR:  partition "fail_part" would overlap partition "part_1"
+-- attaching default partition overlaps if the default partition already exists
+CREATE TABLE def_part PARTITION OF list_parted DEFAULT;
+CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT;
+ERROR:  cannot attach a new partition to table "list_parted" having a default partition
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
 	a int,
@@ -3286,6 +3291,15 @@ ERROR:  partition constraint is violated by some row
 -- should be ok after deleting the bad row
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+-- check partition cannot be attached if default exists
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
+INSERT INTO list_parted2_def VALUES (11, 'z');
+CREATE TABLE part_3 (LIKE list_parted2);
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+ERROR:  cannot attach a new partition to table "list_parted2" having a default partition
+-- should be ok after detaching a default partition
+ALTER TABLE list_parted2 DETACH PARTITION list_parted2_def;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
 CREATE TABLE part_3_4 (
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index b6f794e..8db37bf 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -467,6 +467,13 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
 ERROR:  invalid bound specification for a list partition
 LINE 1: ...BLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) T...
                                                              ^
+-- check default partition cannot be created more than once
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
+ERROR:  cannot add a new partition to table "list_parted" having a default partition
+-- check that no partition can be created after default partition
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (3);
+ERROR:  cannot add a new partition to table "list_parted" having a default partition
 -- specified literal can't be cast to the partition column data type
 CREATE TABLE bools (
 	a bool
@@ -514,6 +521,9 @@ 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);
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index d1153f4..194a429 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -219,17 +219,56 @@ insert into part_null values (null, 0);
 create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b);
 create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
 create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
+-- test default partition
+create table part_default partition of list_parted default;
+-- fail
+insert into part_default values ('aa', 2);
+ERROR:  new row for relation "part_default" violates partition constraint
+DETAIL:  Failing row contains (aa, 2).
+insert into part_default values (null, 2);
+ERROR:  new row for relation "part_default" violates partition constraint
+DETAIL:  Failing row contains (null, 2).
+-- ok
+insert into part_default values ('Zz', 2);
+-- check in case of multi-level default partitioned table
+drop table part_default;
+create table part_default partition of list_parted default partition by range(b);
+create table part_default_p1 partition of part_default for values from (20) to (30);
+create table part_default_p2 partition of part_default for values from (30) to (40);
 -- fail
 insert into part_ee_ff1 values ('EE', 11);
 ERROR:  new row for relation "part_ee_ff1" violates partition constraint
 DETAIL:  Failing row contains (EE, 11).
+insert into part_default_p2 values ('gg', 43);
+ERROR:  new row for relation "part_default_p2" violates partition constraint
+DETAIL:  Failing row contains (gg, 43).
 -- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
 insert into part_ee_ff1 values ('cc', 1);
 ERROR:  new row for relation "part_ee_ff1" violates partition constraint
 DETAIL:  Failing row contains (cc, 1).
+insert into part_default values ('gg', 43);
+ERROR:  no partition of relation "part_default" found for row
+DETAIL:  Partition key of the failing row contains (b) = (43).
 -- ok
 insert into part_ee_ff1 values ('ff', 1);
 insert into part_ee_ff2 values ('ff', 11);
+insert into part_default_p1 values ('cd', 25);
+insert into part_default_p2 values ('de', 35);
+insert into list_parted values ('ab', 21);
+-- drop default, as we need to add some more partitions to test tuple routing
+select tableoid::regclass, * from list_parted;
+    tableoid     | a  | b  
+-----------------+----+----
+ part_cc_dd      | cC |  1
+ part_null       |    |  0
+ part_ee_ff1     | ff |  1
+ part_ee_ff2     | ff | 11
+ part_default_p1 | cd | 25
+ part_default_p1 | ab | 21
+ part_default_p2 | de | 35
+(7 rows)
+
+drop table part_default;
 -- Check tuple routing for partitioned tables
 -- fail
 insert into range_parted values ('a', 0);
@@ -316,6 +355,23 @@ select tableoid::regclass::text, a, min(b) as min_b, max(b) as max_b from list_p
 
 -- cleanup
 drop table range_parted, list_parted;
+-- test adding default partition as first partition accepts any value including
+-- null
+create table list_parted (a int) partition by list (a);
+create table part_default partition of list_parted default;
+insert into part_default values (null);
+insert into part_default values (1);
+insert into part_default values (-1);
+select tableoid::regclass, a from list_parted;
+   tableoid   | a  
+--------------+----
+ part_default |   
+ part_default |  1
+ part_default | -1
+(3 rows)
+
+-- cleanup
+drop table list_parted;
 -- more tests for certain multi-level partitioning scenarios
 create table mlparted (a int, b int) partition by range (a, b);
 create table mlparted1 (b int not null, a int not null) partition by range ((b+0));
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
index 3f3db33..53fe3d4 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -252,3 +252,25 @@ NOTICE:  3
  
 (1 row)
 
+-- Check that addition or removal of any partition is correctly dealt with by
+-- default partition table when it is being used in cached plan.
+create table list_parted (a int) partition by list(a);
+create table list_part_null partition of list_parted for values in (null);
+create table list_part_1 partition of list_parted for values in (1);
+create table list_part_def partition of list_parted default;
+prepare pstmt_def_insert (int) as insert into list_part_def values($1);
+-- should fail
+execute pstmt_def_insert(null);
+ERROR:  new row for relation "list_part_def" violates partition constraint
+DETAIL:  Failing row contains (null).
+execute pstmt_def_insert(1);
+ERROR:  new row for relation "list_part_def" violates partition constraint
+DETAIL:  Failing row contains (1).
+alter table list_parted detach partition list_part_null;
+-- should be ok
+execute pstmt_def_insert(null);
+drop table list_part_1;
+-- should be ok
+execute pstmt_def_insert(1);
+drop table list_parted;
+deallocate pstmt_def_insert;
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index 9366f04..9912ef2 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -218,5 +218,20 @@ 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;
+create table list_parted (
+	a text,
+	b int
+) partition by list (a);
+create table list_part1  partition of list_parted for values in ('a', 'b');
+create table list_default partition of list_parted default;
+insert into list_part1 values ('a', 1);
+insert into list_default values ('d', 10);
+-- fail
+update list_default set a = 'a' where a = 'd';
+ERROR:  new row for relation "list_default" violates partition constraint
+DETAIL:  Failing row contains (a, 10).
+-- ok
+update list_default set a = 'x' where a = 'd';
 -- cleanup
 drop table range_parted;
+drop table list_parted;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 5dd1402..2346c1f 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2095,6 +2095,10 @@ SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::reg
 -- check that the new partition won't overlap with an existing partition
 CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+-- attaching default partition overlaps if the default partition already exists
+CREATE TABLE def_part PARTITION OF list_parted DEFAULT;
+CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT;
 
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
@@ -2111,6 +2115,15 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 
+-- check partition cannot be attached if default exists
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
+INSERT INTO list_parted2_def VALUES (11, 'z');
+CREATE TABLE part_3 (LIKE list_parted2);
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+-- should be ok after detaching a default partition
+ALTER TABLE list_parted2 DETACH PARTITION list_parted2_def;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
 CREATE TABLE part_3_4 (
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index cb7aa5b..650c1f5 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -447,6 +447,12 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
 -- trying to specify range for list partitioned table
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
 
+-- check default partition cannot be created more than once
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
+-- check that no partition can be created after default partition
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (3);
+
 -- specified literal can't be cast to the partition column data type
 CREATE TABLE bools (
 	a bool
@@ -484,6 +490,8 @@ 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);
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 83c3ad8..1154d0c 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -132,13 +132,34 @@ create table part_ee_ff partition of list_parted for values in ('ee', 'ff') part
 create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
 create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
 
+-- test default partition
+create table part_default partition of list_parted default;
+-- fail
+insert into part_default values ('aa', 2);
+insert into part_default values (null, 2);
+-- ok
+insert into part_default values ('Zz', 2);
+-- check in case of multi-level default partitioned table
+drop table part_default;
+create table part_default partition of list_parted default partition by range(b);
+create table part_default_p1 partition of part_default for values from (20) to (30);
+create table part_default_p2 partition of part_default for values from (30) to (40);
+
 -- fail
 insert into part_ee_ff1 values ('EE', 11);
+insert into part_default_p2 values ('gg', 43);
 -- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
 insert into part_ee_ff1 values ('cc', 1);
+insert into part_default values ('gg', 43);
 -- ok
 insert into part_ee_ff1 values ('ff', 1);
 insert into part_ee_ff2 values ('ff', 11);
+insert into part_default_p1 values ('cd', 25);
+insert into part_default_p2 values ('de', 35);
+insert into list_parted values ('ab', 21);
+-- drop default, as we need to add some more partitions to test tuple routing
+select tableoid::regclass, * from list_parted;
+drop table part_default;
 
 -- Check tuple routing for partitioned tables
 
@@ -188,6 +209,17 @@ select tableoid::regclass::text, a, min(b) as min_b, max(b) as max_b from list_p
 -- cleanup
 drop table range_parted, list_parted;
 
+-- test adding default partition as first partition accepts any value including
+-- null
+create table list_parted (a int) partition by list (a);
+create table part_default partition of list_parted default;
+insert into part_default values (null);
+insert into part_default values (1);
+insert into part_default values (-1);
+select tableoid::regclass, a from list_parted;
+-- cleanup
+drop table list_parted;
+
 -- more tests for certain multi-level partitioning scenarios
 create table mlparted (a int, b int) partition by range (a, b);
 create table mlparted1 (b int not null, a int not null) partition by range ((b+0));
diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql
index bc20861..89ddf3f 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -156,3 +156,22 @@ end$$ language plpgsql;
 
 select cachebug();
 select cachebug();
+
+-- Check that addition or removal of any partition is correctly dealt with by
+-- default partition table when it is being used in cached plan.
+create table list_parted (a int) partition by list(a);
+create table list_part_null partition of list_parted for values in (null);
+create table list_part_1 partition of list_parted for values in (1);
+create table list_part_def partition of list_parted default;
+prepare pstmt_def_insert (int) as insert into list_part_def values($1);
+-- should fail
+execute pstmt_def_insert(null);
+execute pstmt_def_insert(1);
+alter table list_parted detach partition list_part_null;
+-- should be ok
+execute pstmt_def_insert(null);
+drop table list_part_1;
+-- should be ok
+execute pstmt_def_insert(1);
+drop table list_parted;
+deallocate pstmt_def_insert;
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 6637119..44fb0dc 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -125,5 +125,20 @@ update range_parted set b = b - 1 where b = 10;
 -- ok
 update range_parted set b = b + 1 where b = 10;
 
+create table list_parted (
+	a text,
+	b int
+) partition by list (a);
+create table list_part1  partition of list_parted for values in ('a', 'b');
+create table list_default partition of list_parted default;
+insert into list_part1 values ('a', 1);
+insert into list_default values ('d', 10);
+
+-- fail
+update list_default set a = 'a' where a = 'd';
+-- ok
+update list_default set a = 'x' where a = 'd';
+
 -- cleanup
 drop table range_parted;
+drop table list_parted;
-- 
1.9.1

0004-Store-the-default-partition-OID-in-catalog.patch0000664000175000017500000002465513131471566021737 0ustar  jeevanjeevanFrom 40dc8675c6441460df4e16733625b1b06423c298 Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Thu, 13 Jul 2017 00:32:29 +0530
Subject: [PATCH 4/7] Store the default partition OID in catalog
 pg_partitioned_table

This patch introduces a new field partdefid to pg_partitioned_table.
This can be used to quickly look up for default partition instead
of looking for every child in pg_inherits, then looking its entry
in pg_class to verify if the partition is default of or not.

Jeevan Ladhe.
---
 src/backend/catalog/heap.c                 | 16 +++++++---
 src/backend/catalog/partition.c            | 50 ++++++++++++++++++++++++++----
 src/backend/commands/tablecmds.c           | 32 ++++++++++++++-----
 src/include/catalog/partition.h            |  3 +-
 src/include/catalog/pg_partitioned_table.h | 13 +++++---
 5 files changed, 90 insertions(+), 24 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 068c3bd..0f6b41c 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1755,8 +1755,7 @@ RemoveAttrDefaultById(Oid attrdefId)
 void
 heap_drop_with_catalog(Oid relid)
 {
-	Relation	rel,
-				parentRel;
+	Relation	rel;
 	HeapTuple	tuple;
 	Oid			parentOid = InvalidOid,
 				defaultPartOid = InvalidOid;
@@ -1774,7 +1773,7 @@ heap_drop_with_catalog(Oid relid)
 	if (((Form_pg_class) GETSTRUCT(tuple))->relispartition)
 	{
 		parentOid = get_partition_parent(relid);
-		parentRel = heap_open(parentOid, AccessExclusiveLock);
+		LockRelationOid(parentOid, AccessExclusiveLock);
 
 		/*
 		 * The partition constraint of the default partition depends on the
@@ -1787,7 +1786,7 @@ heap_drop_with_catalog(Oid relid)
 		 * we commit and send out a shared-cache-inval notice that will make
 		 * them update their index lists.
 		 */
-		defaultPartOid = get_default_partition_oid(parentRel);
+		defaultPartOid = get_default_partition_oid(parentOid);
 		if (OidIsValid(defaultPartOid))
 			LockRelationOid(defaultPartOid, AccessExclusiveLock);
 	}
@@ -1841,6 +1840,13 @@ heap_drop_with_catalog(Oid relid)
 		RemovePartitionKeyByRelId(relid);
 
 	/*
+	 * If the relation being dropped is the default partition itself,
+	 * invalidate its entry in pg_partitioned_table.
+	 */
+	if (relid == defaultPartOid)
+		update_default_partition_oid(parentOid, InvalidOid);
+
+	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -1914,7 +1920,6 @@ heap_drop_with_catalog(Oid relid)
 		 */
 		CacheInvalidateRelcacheByRelid(parentOid);
 		/* keep the lock */
-		heap_close(parentRel, NoLock);
 	}
 }
 
@@ -3163,6 +3168,7 @@ StorePartitionKey(Relation rel,
 	values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
 	values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
 	values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
+	values[Anum_pg_partitioned_table_partdefid - 1] = ObjectIdGetDatum(InvalidOid);
 	values[Anum_pg_partitioned_table_partattrs - 1] = PointerGetDatum(partattrs_vec);
 	values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
 	values[Anum_pg_partitioned_table_partcollation - 1] = PointerGetDatum(partcollation_vec);
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index a69015a..805d6f7 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -27,6 +27,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_inherits_fn.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_type.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
@@ -2455,13 +2456,50 @@ partition_bound_bsearch(PartitionKey key, PartitionBoundInfo boundinfo,
  * partition, otherwise return InvalidOid.
  */
 Oid
-get_default_partition_oid(Relation parent)
+get_default_partition_oid(Oid parentId)
 {
-	PartitionDesc partdesc = RelationGetPartitionDesc(parent);
+	HeapTuple	tuple;
+	Oid			defaultPartId = InvalidOid;
+
+	tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(parentId));
+
+	if (HeapTupleIsValid(tuple))
+	{
+		Form_pg_partitioned_table part_table_form;
+
+		part_table_form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+		defaultPartId = part_table_form->partdefid;
+	}
+
+	ReleaseSysCache(tuple);
+	return defaultPartId;
+}
+
+/*
+ * update_default_partition_oid
+ *
+ * Updates the pg_partition_table catalog partdefid field for the given parent
+ * with the given default partition oid.
+ */
+void
+update_default_partition_oid(Oid parentId, Oid defaultPartId)
+{
+	HeapTuple	tuple;
+	Relation	pg_partitioned_table;
+	Form_pg_partitioned_table part_table_form;
+
+	pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCacheCopy1(PARTRELID, ObjectIdGetDatum(parentId));
+
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of relation %u",
+			 parentId);
 
-	if (partdesc && partdesc->boundinfo &&
-		partition_bound_has_default(partdesc->boundinfo))
-		return partdesc->oids[partdesc->boundinfo->default_index];
+	part_table_form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+	part_table_form->partdefid = defaultPartId;
+	CatalogTupleUpdate(pg_partitioned_table, &tuple->t_self, tuple);
 
-	return InvalidOid;
+	heap_freetuple(tuple);
+	heap_close(pg_partitioned_table, RowExclusiveLock);
 }
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index bcca46b..fc8a6f6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -788,7 +788,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		 * A table cannot be created as a partition of a parent already having
 		 * a default partition.
 		 */
-		defaultPartOid = get_default_partition_oid(parent);
+		defaultPartOid = get_default_partition_oid(RelationGetRelid(parent));
 		if (OidIsValid(defaultPartOid))
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
@@ -811,6 +811,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		/* Update the pg_class entry. */
 		StorePartitionBound(rel, parent, bound);
 
+		/* Update the default partition oid */
+		if (bound->is_default)
+			update_default_partition_oid(RelationGetRelid(parent), relationId);
+
 		heap_close(parent, NoLock);
 
 		/*
@@ -13607,7 +13611,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	const char *trigger_name;
 
 	/* A partition cannot be attached if there exists a default partition */
-	defaultPartOid = get_default_partition_oid(rel);
+	defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
 	if (OidIsValid(defaultPartOid))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
@@ -13769,6 +13773,11 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	/* Update the pg_class entry. */
 	StorePartitionBound(attachRel, rel, cmd->bound);
 
+	/* Update the default partition oid */
+	if (cmd->bound->is_default)
+		update_default_partition_oid(RelationGetRelid(rel),
+									 RelationGetRelid(attachRel));
+
 	/*
 	 * Generate partition constraint from the partition bound specification.
 	 * If the parent itself is a partition, make sure to include its
@@ -13821,7 +13830,7 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 	 * We must also lock the default partition, for the same reasons explained
 	 * in heap_drop_with_catalog().
 	 */
-	defaultPartOid = get_default_partition_oid(rel);
+	defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
 	if (OidIsValid(defaultPartOid))
 		LockRelationOid(defaultPartOid, AccessExclusiveLock);
 
@@ -13855,12 +13864,21 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 	heap_freetuple(newtuple);
 	heap_close(classRel, RowExclusiveLock);
 
-	/*
-	 * We must invalidate default partition's relcache, for the same reasons
-	 * explained in heap_drop_with_catalog().
-	 */
 	if (OidIsValid(defaultPartOid))
+	{
+		/*
+		 * If the detach relation is the default partition itself, invalidate
+		 * its entry in pg_partitioned_table.
+		 */
+		if (RelationGetRelid(partRel) == defaultPartOid)
+			update_default_partition_oid(RelationGetRelid(rel), InvalidOid);
+
+		/*
+		 * We must invalidate default partition's relcache, for the same
+		 * reasons explained in heap_drop_with_catalog().
+		 */
 		CacheInvalidateRelcacheByRelid(defaultPartOid);
+	}
 
 	/*
 	 * Invalidate the parent's relcache so that the partition is no longer
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 707fca4..7800c96 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -98,6 +98,7 @@ extern int get_partition_for_tuple(PartitionDispatch *pd,
 						EState *estate,
 						PartitionDispatchData **failed_at,
 						TupleTableSlot **failed_slot);
-extern Oid	get_default_partition_oid(Relation parent);
+extern Oid	get_default_partition_oid(Oid parentId);
+extern void update_default_partition_oid(Oid parentId, Oid defaultPartId);
 
 #endif							/* PARTITION_H */
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
index 38d64d6..78c7ee9 100644
--- a/src/include/catalog/pg_partitioned_table.h
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -32,6 +32,8 @@ CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
 	Oid			partrelid;		/* partitioned table oid */
 	char		partstrat;		/* partitioning strategy */
 	int16		partnatts;		/* number of partition key columns */
+	Oid			partdefid;		/* default partition oid; InvalidOid if there
+								 * isn't one */
 
 	/*
 	 * variable-length fields start here, but we allow direct access to
@@ -62,13 +64,14 @@ typedef FormData_pg_partitioned_table *Form_pg_partitioned_table;
  *		compiler constants for pg_partitioned_table
  * ----------------
  */
-#define Natts_pg_partitioned_table				7
+#define Natts_pg_partitioned_table				8
 #define Anum_pg_partitioned_table_partrelid		1
 #define Anum_pg_partitioned_table_partstrat		2
 #define Anum_pg_partitioned_table_partnatts		3
-#define Anum_pg_partitioned_table_partattrs		4
-#define Anum_pg_partitioned_table_partclass		5
-#define Anum_pg_partitioned_table_partcollation 6
-#define Anum_pg_partitioned_table_partexprs		7
+#define Anum_pg_partitioned_table_partdefid		4
+#define Anum_pg_partitioned_table_partattrs		5
+#define Anum_pg_partitioned_table_partclass		6
+#define Anum_pg_partitioned_table_partcollation	7
+#define Anum_pg_partitioned_table_partexprs		8
 
 #endif							/* PG_PARTITIONED_TABLE_H */
-- 
1.9.1

0005-Default-partition-extended-for-create-and-attach.patch0000664000175000017500000010377013131471566023224 0ustar  jeevanjeevanFrom 203b7893b8c4ce670316c35e7c5bf0c38a8bff91 Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Thu, 13 Jul 2017 00:33:50 +0530
Subject: [PATCH 5/7] Default partition extended for create and attach

This patch extends the previous patch in this series to allow a
new partition to be created even when a default partition on a list
partition exists. Also, extends the regression tests to test this
functionality.

Jeevan Ladhe.
---
 src/backend/catalog/heap.c                 |  33 +++---
 src/backend/catalog/partition.c            | 162 ++++++++++++++++++++++++++++-
 src/backend/commands/tablecmds.c           |  41 +++++---
 src/test/regress/expected/alter_table.out  |  22 +++-
 src/test/regress/expected/create_table.out |  15 +--
 src/test/regress/expected/insert.out       |  56 ++++------
 src/test/regress/expected/plancache.out    |  20 ++--
 src/test/regress/sql/alter_table.sql       |  18 +++-
 src/test/regress/sql/create_table.sql      |  12 +--
 src/test/regress/sql/insert.sql            |  15 +--
 src/test/regress/sql/plancache.sql         |  16 ++-
 11 files changed, 300 insertions(+), 110 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 0f6b41c..c1cb5c6 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1776,15 +1776,8 @@ heap_drop_with_catalog(Oid relid)
 		LockRelationOid(parentOid, AccessExclusiveLock);
 
 		/*
-		 * The partition constraint of the default partition depends on the
-		 * partition bounds of every other partition. It is possible that
-		 * other backend might be about to execute a query on the default
-		 * partition table and the query relies on previously cached default
-		 * partition constraints, which won't be correct after removal of a
-		 * partition. We must therefore take a table lock strong enough to
-		 * prevent all queries on the default partition from proceeding until
-		 * we commit and send out a shared-cache-inval notice that will make
-		 * them update their index lists.
+		 * We must also lock the default partition, for the same reasons
+		 * explained in DefineRelation().
 		 */
 		defaultPartOid = get_default_partition_oid(parentOid);
 		if (OidIsValid(defaultPartOid))
@@ -1906,10 +1899,8 @@ heap_drop_with_catalog(Oid relid)
 	if (OidIsValid(parentOid))
 	{
 		/*
-		 * The partition constraint for the default partition depends on the
-		 * partition bounds of every other partition, so we must invalidate
-		 * the relcache entry for that partition every time a partition is
-		 * added or removed.
+		 * We must invalidate default partition's relcache, for the same
+		 * reasons explained in StorePartitionBound().
 		 */
 		if (OidIsValid(defaultPartOid))
 			CacheInvalidateRelcacheByRelid(defaultPartOid);
@@ -3253,8 +3244,9 @@ RemovePartitionKeyByRelId(Oid relid)
  *		Update pg_class tuple of rel to store the partition bound and set
  *		relispartition to true
  *
- * Also, invalidate the parent's relcache, so that the next rebuild will load
- * the new partition's info into its partition descriptor.
+ * Also, invalidate the parent's relcache entry, so that the next rebuild will
+ * load he new partition's info into its partition descriptor.  If there is a
+ * default partition, we must invalidate its relcache entry as well.
  */
 void
 StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
@@ -3265,6 +3257,7 @@ StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
 	Datum		new_val[Natts_pg_class];
 	bool		new_null[Natts_pg_class],
 				new_repl[Natts_pg_class];
+	Oid			defaultPartOid;
 
 	/* Update pg_class tuple */
 	classRel = heap_open(RelationRelationId, RowExclusiveLock);
@@ -3302,5 +3295,15 @@ StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
 	heap_freetuple(newtuple);
 	heap_close(classRel, RowExclusiveLock);
 
+	/*
+	 * The partition constraint for the default partition depends on the
+	 * partition bounds of every other partition, so we must invalidate the
+	 * relcache entry for that partition every time a partition is added or
+	 * removed.
+	 */
+	defaultPartOid = get_default_partition_oid(RelationGetRelid(parent));
+	if (OidIsValid(defaultPartOid))
+		CacheInvalidateRelcacheByRelid(defaultPartOid);
+
 	CacheInvalidateRelcache(parent);
 }
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 805d6f7..0b747eb 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -36,6 +36,7 @@
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planmain.h"
+#include "optimizer/prep.h"
 #include "optimizer/var.h"
 #include "rewrite/rewriteManip.h"
 #include "storage/lmgr.h"
@@ -152,6 +153,8 @@ static int32 partition_bound_cmp(PartitionKey key,
 static int partition_bound_bsearch(PartitionKey key,
 						PartitionBoundInfo boundinfo,
 						void *probe, bool probe_is_bound, bool *is_equal);
+static void check_default_allows_bound(Relation parent,
+						   PartitionBoundSpec *new_spec);
 
 /*
  * RelationBuildPartitionDesc
@@ -707,10 +710,27 @@ check_new_partition_bound(char *relname, Relation parent,
 {
 	PartitionKey key = RelationGetPartitionKey(parent);
 	PartitionDesc partdesc = RelationGetPartitionDesc(parent);
+	PartitionBoundInfo boundinfo = partdesc->boundinfo;
 	ParseState *pstate = make_parsestate(NULL);
 	int			with = -1;
 	bool		overlap = false;
 
+	if (spec->is_default)
+	{
+		/* Default partition cannot be added if there already exists one. */
+		if (partdesc->nparts > 0 && partition_bound_has_default(boundinfo))
+		{
+			with = boundinfo->default_index;
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("partition \"%s\" conflicts with existing default partition \"%s\"",
+							relname, get_rel_name(partdesc->oids[with])),
+					 parser_errposition(pstate, spec->location)));
+		}
+
+		return;
+	}
+
 	switch (key->strategy)
 	{
 		case PARTITION_STRATEGY_LIST:
@@ -719,13 +739,13 @@ check_new_partition_bound(char *relname, Relation parent,
 
 				if (partdesc->nparts > 0)
 				{
-					PartitionBoundInfo boundinfo = partdesc->boundinfo;
 					ListCell   *cell;
 
 					Assert(boundinfo &&
 						   boundinfo->strategy == PARTITION_STRATEGY_LIST &&
 						   (boundinfo->ndatums > 0 ||
-							partition_bound_accepts_nulls(boundinfo)));
+							partition_bound_accepts_nulls(boundinfo) ||
+							partition_bound_has_default(boundinfo)));
 
 					foreach(cell, spec->listdatums)
 					{
@@ -859,6 +879,144 @@ check_new_partition_bound(char *relname, Relation parent,
 						relname, get_rel_name(partdesc->oids[with])),
 				 parser_errposition(pstate, spec->location)));
 	}
+
+	/*
+	 * If the default partition exists, its partition constraints will change
+	 * after the addition of this new partition such that it won't allow any
+	 * row that qualifies for this new partition. So, check if the existing
+	 * data in the default partition satisfies this *would be* default
+	 * partition constraint. In case the new partition bound being checked
+	 * itself is a DEFAULT bound, this check shouldn't be triggered as there
+	 * won't already exists the default partition in such a case.
+	 */
+	if (boundinfo && partition_bound_has_default(boundinfo))
+		check_default_allows_bound(parent, spec);
+}
+
+/*
+ * check_default_allows_bound
+ *
+ * This function checks if there exists a row in the default partition that
+ * fits in the new partition and throws an error if it finds one.
+ */
+static void
+check_default_allows_bound(Relation parent, PartitionBoundSpec *new_spec)
+{
+	Relation	default_rel;
+	List	   *new_part_constraints = NIL;
+	List	   *all_parts;
+	ListCell   *lc;
+	PartitionDescData *part_desc = RelationGetPartitionDesc(parent);
+
+	/* 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 = (List *) eval_const_expressions(NULL,
+														   (Node *) new_part_constraints);
+	new_part_constraints =
+		(List *) canonicalize_qual((Expr *) new_part_constraints);
+	new_part_constraints = list_make1(make_ands_explicit(new_part_constraints));
+
+	/*
+	 * Generate the constraint and default execution states.
+	 *
+	 * The default partition must be already having an AccessExclusiveLock.
+	 */
+	default_rel = heap_open(get_default_partition_oid(RelationGetRelid(parent)),
+							NoLock);
+
+	if (default_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		all_parts = find_all_inheritors(RelationGetRelid(default_rel),
+										AccessExclusiveLock, NULL);
+	else
+		all_parts = list_make1_oid(RelationGetRelid(default_rel));
+
+	foreach(lc, all_parts)
+	{
+		Oid			part_relid = lfirst_oid(lc);
+		Relation	part_rel;
+		Expr	   *constr;
+		Expr	   *partition_constraint;
+		EState	   *estate;
+		HeapTuple	tuple;
+		ExprState  *partqualstate = NULL;
+		Snapshot	snapshot;
+		TupleDesc	tupdesc;
+		ExprContext *econtext;
+		HeapScanDesc scan;
+		MemoryContext oldCxt;
+		TupleTableSlot *tupslot;
+
+		/* Lock already taken above. */
+		if (part_relid != RelationGetRelid(default_rel))
+			part_rel = heap_open(part_relid, NoLock);
+		else
+			part_rel = default_rel;
+
+		/*
+		 * Only RELKIND_RELATION relations (i.e. leaf partitions) need to be
+		 * scanned.
+		 */
+		if (part_rel->rd_rel->relkind != RELKIND_RELATION)
+		{
+			if (part_rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+				ereport(WARNING,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("skipped scanning foreign table \"%s\" which is a partition of default partition \"%s\"",
+								RelationGetRelationName(part_rel),
+								RelationGetRelationName(default_rel))));
+
+			heap_close(part_rel, NoLock);
+			continue;
+		}
+
+		tupdesc = CreateTupleDescCopy(RelationGetDescr(part_rel));
+		constr = linitial(new_part_constraints);
+		partition_constraint = (Expr *) map_partition_varattnos((List *) constr,
+																1, part_rel, parent);
+		estate = CreateExecutorState();
+
+		/* Build expression execution states for partition check quals */
+		partqualstate = ExecPrepareExpr(partition_constraint, estate);
+
+		econtext = GetPerTupleExprContext(estate);
+		snapshot = RegisterSnapshot(GetLatestSnapshot());
+		scan = heap_beginscan(part_rel, snapshot, 0, NULL);
+		tupslot = MakeSingleTupleTableSlot(tupdesc);
+
+		/*
+		 * Switch to per-tuple memory context and reset it for each tuple
+		 * produced, so we don't leak memory.
+		 */
+		oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		{
+			ExecStoreTuple(tuple, tupslot, InvalidBuffer, false);
+			econtext->ecxt_scantuple = tupslot;
+
+			if (ExecCheck(partqualstate, econtext))
+				ereport(ERROR,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("updated partition constraint for default partition \"%s\" would be violated by some row",
+								RelationGetRelationName(default_rel))));
+
+			ResetExprContext(econtext);
+			CHECK_FOR_INTERRUPTS();
+		}
+
+		MemoryContextSwitchTo(oldCxt);
+		heap_endscan(scan);
+		UnregisterSnapshot(snapshot);
+		ExecDropSingleTupleTableSlot(tupslot);
+		FreeExecutorState(estate);
+		/* keep our lock until commit */
+		heap_close(part_rel, NoLock);
+	}
 }
 
 /*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index fc8a6f6..b875dbe 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -785,15 +785,28 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 							RelationGetRelationName(parent))));
 
 		/*
-		 * A table cannot be created as a partition of a parent already having
-		 * a default partition.
+		 * The partition constraint of the default partition depends on the
+		 * partition bounds of every other partition. It is possible that
+		 * other backend might be about to execute a query on the default
+		 * partition table and the query relies on previously cached default
+		 * partition constraints, which won't be correct after removal of a
+		 * partition. We must therefore take a table lock strong enough to
+		 * prevent all queries on the default partition from proceeding until
+		 * we commit and send out a shared-cache-inval notice that will make
+		 * them update their index lists.
+		 *
+		 * Order of locking: The relation being added won't be visible to
+		 * other backends until it is committed, hence here in
+		 * DefineRelation() the order of locking the default partition and the
+		 * relation being added does not matter. But at all other places we
+		 * need to lock the default relation before we lock the relation being
+		 * added or removed i.e. we should take the lock in same order at all
+		 * the places such that lock parent, lock default partition and then
+		 * lock the partition so as to avoid a deadlock.
 		 */
 		defaultPartOid = get_default_partition_oid(RelationGetRelid(parent));
 		if (OidIsValid(defaultPartOid))
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-					 errmsg("cannot add a new partition to table \"%s\" having a default partition",
-							RelationGetRelationName(parent))));
+			LockRelationOid(defaultPartOid, AccessExclusiveLock);
 
 		/* Tranform the bound values */
 		pstate = make_parsestate(NULL);
@@ -13610,13 +13623,13 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	Oid			defaultPartOid;
 	const char *trigger_name;
 
-	/* A partition cannot be attached if there exists a default partition */
+	/*
+	 * We must lock the default partition, for the same reasons explained in
+	 * DefineRelation().
+	 */
 	defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
 	if (OidIsValid(defaultPartOid))
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-				 errmsg("cannot attach a new partition to table \"%s\" having a default partition",
-						RelationGetRelationName(rel))));
+		LockRelationOid(defaultPartOid, AccessExclusiveLock);
 
 	attachRel = heap_openrv(cmd->name, AccessExclusiveLock);
 
@@ -13827,8 +13840,8 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 	Oid			defaultPartOid;
 
 	/*
-	 * We must also lock the default partition, for the same reasons explained
-	 * in heap_drop_with_catalog().
+	 * We must lock the default partition, for the same reasons explained in
+	 * DefineRelation().
 	 */
 	defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
 	if (OidIsValid(defaultPartOid))
@@ -13875,7 +13888,7 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 
 		/*
 		 * We must invalidate default partition's relcache, for the same
-		 * reasons explained in heap_drop_with_catalog().
+		 * reasons explained in StorePartitionBound().
 		 */
 		CacheInvalidateRelcacheByRelid(defaultPartOid);
 	}
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 0acf7ef..938fe28 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3277,7 +3277,7 @@ ERROR:  partition "fail_part" would overlap partition "part_1"
 CREATE TABLE def_part PARTITION OF list_parted DEFAULT;
 CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT;
-ERROR:  cannot attach a new partition to table "list_parted" having a default partition
+ERROR:  partition "fail_def_part" conflicts with existing default partition "def_part"
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
 	a int,
@@ -3291,14 +3291,14 @@ ERROR:  partition constraint is violated by some row
 -- should be ok after deleting the bad row
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
--- check partition cannot be attached if default exists
+-- check partition cannot be attached if default has some row for its values
 CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 INSERT INTO list_parted2_def VALUES (11, 'z');
 CREATE TABLE part_3 (LIKE list_parted2);
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
-ERROR:  cannot attach a new partition to table "list_parted2" having a default partition
--- should be ok after detaching a default partition
-ALTER TABLE list_parted2 DETACH PARTITION list_parted2_def;
+ERROR:  updated partition constraint for default partition "list_parted2_def" would be violated by some row
+-- should be ok after deleting the bad row
+DELETE FROM list_parted2_def WHERE a = 11;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
@@ -3361,6 +3361,18 @@ ALTER TABLE part_5 DROP CONSTRAINT check_a;
 ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT NULL;
 ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
 INFO:  partition constraint for table "part_5" is implied by existing constraints
+-- check that leaf partitions of default partition are scanned when
+-- attaching a partitioned table.
+ALTER TABLE part_5 DROP CONSTRAINT check_a;
+CREATE TABLE part5_def PARTITION OF part_5 DEFAULT PARTITION BY LIST(a);
+CREATE TABLE part5_def_p1 PARTITION OF part5_def FOR VALUES IN (5);
+INSERT INTO part5_def_p1 VALUES (5, 'y');
+CREATE TABLE part5_p1 (LIKE part_5);
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+ERROR:  updated partition constraint for default partition "part5_def" would be violated by some row
+-- should be ok after deleting the bad row
+DELETE FROM part5_def_p1 WHERE b = 'y';
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
 -- check that the table being attached is not already a partition
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 ERROR:  "part_2" is already a partition
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 8db37bf..3c56812 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -449,6 +449,7 @@ CREATE TABLE list_parted (
 CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
 CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
 CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
 ERROR:  syntax error at or near "int"
 LINE 1: ... fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
@@ -457,6 +458,8 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
 ERROR:  syntax error at or near "::"
 LINE 1: ...fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
                                                                 ^
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
+ERROR:  partition "fail_default_part" conflicts with existing default partition "part_default"
 -- syntax does not allow empty list of values for list partitions
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
 ERROR:  syntax error at or near ")"
@@ -467,13 +470,6 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
 ERROR:  invalid bound specification for a list partition
 LINE 1: ...BLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) T...
                                                              ^
--- check default partition cannot be created more than once
-CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
-CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
-ERROR:  cannot add a new partition to table "list_parted" having a default partition
--- check that no partition can be created after default partition
-CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (3);
-ERROR:  cannot add a new partition to table "list_parted" having a default partition
 -- specified literal can't be cast to the partition column data type
 CREATE TABLE bools (
 	a bool
@@ -575,10 +571,15 @@ CREATE TABLE list_parted2 (
 ) PARTITION BY LIST (a);
 CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
 CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
 ERROR:  partition "fail_part" would overlap partition "part_null_z"
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
 ERROR:  partition "fail_part" would overlap partition "part_ab"
+-- check default partition overlap
+INSERT INTO list_parted2 VALUES('X');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y');
+ERROR:  updated partition constraint for default partition "list_parted2_def" would be violated by some row
 CREATE TABLE range_parted2 (
 	a int
 ) PARTITION BY RANGE (a);
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 194a429..72290cc 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -202,6 +202,7 @@ create table list_parted (
 create table part_aa_bb partition of list_parted FOR VALUES IN ('aa', 'bb');
 create table part_cc_dd partition of list_parted FOR VALUES IN ('cc', 'dd');
 create table part_null partition of list_parted FOR VALUES IN (null);
+create table part_default partition of list_parted default;
 -- fail
 insert into part_aa_bb values ('cc', 1);
 ERROR:  new row for relation "part_aa_bb" violates partition constraint
@@ -212,16 +213,6 @@ DETAIL:  Failing row contains (AAa, 1).
 insert into part_aa_bb values (null);
 ERROR:  new row for relation "part_aa_bb" violates partition constraint
 DETAIL:  Failing row contains (null, null).
--- ok
-insert into part_cc_dd values ('cC', 1);
-insert into part_null values (null, 0);
--- check in case of multi-level partitioned table
-create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b);
-create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
-create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
--- test default partition
-create table part_default partition of list_parted default;
--- fail
 insert into part_default values ('aa', 2);
 ERROR:  new row for relation "part_default" violates partition constraint
 DETAIL:  Failing row contains (aa, 2).
@@ -229,7 +220,13 @@ insert into part_default values (null, 2);
 ERROR:  new row for relation "part_default" violates partition constraint
 DETAIL:  Failing row contains (null, 2).
 -- ok
+insert into part_cc_dd values ('cC', 1);
+insert into part_null values (null, 0);
 insert into part_default values ('Zz', 2);
+-- check in case of multi-level partitioned table
+create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b);
+create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
+create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
 -- check in case of multi-level default partitioned table
 drop table part_default;
 create table part_default partition of list_parted default partition by range(b);
@@ -255,20 +252,6 @@ insert into part_ee_ff2 values ('ff', 11);
 insert into part_default_p1 values ('cd', 25);
 insert into part_default_p2 values ('de', 35);
 insert into list_parted values ('ab', 21);
--- drop default, as we need to add some more partitions to test tuple routing
-select tableoid::regclass, * from list_parted;
-    tableoid     | a  | b  
------------------+----+----
- part_cc_dd      | cC |  1
- part_null       |    |  0
- part_ee_ff1     | ff |  1
- part_ee_ff2     | ff | 11
- part_default_p1 | cd | 25
- part_default_p1 | ab | 21
- part_default_p2 | de | 35
-(7 rows)
-
-drop table part_default;
 -- Check tuple routing for partitioned tables
 -- fail
 insert into range_parted values ('a', 0);
@@ -313,17 +296,20 @@ DETAIL:  Partition key of the failing row contains (b) = (0).
 insert into list_parted values ('EE', 1);
 insert into part_ee_ff values ('EE', 10);
 select tableoid::regclass, * from list_parted;
-  tableoid   | a  | b  
--------------+----+----
- part_aa_bb  | aA |   
- part_cc_dd  | cC |  1
- part_null   |    |  0
- part_null   |    |  1
- part_ee_ff1 | ff |  1
- part_ee_ff1 | EE |  1
- part_ee_ff2 | ff | 11
- part_ee_ff2 | EE | 10
-(8 rows)
+    tableoid     | a  | b  
+-----------------+----+----
+ part_aa_bb      | aA |   
+ part_cc_dd      | cC |  1
+ part_null       |    |  0
+ part_null       |    |  1
+ part_ee_ff1     | ff |  1
+ part_ee_ff1     | EE |  1
+ part_ee_ff2     | ff | 11
+ part_ee_ff2     | EE | 10
+ part_default_p1 | cd | 25
+ part_default_p1 | ab | 21
+ part_default_p2 | de | 35
+(11 rows)
 
 -- some more tests to exercise tuple-routing with multi-level partitioning
 create table part_gg partition of list_parted for values in ('gg') partition by range (b);
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
index 53fe3d4..f0e811f 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -255,22 +255,28 @@ NOTICE:  3
 -- Check that addition or removal of any partition is correctly dealt with by
 -- default partition table when it is being used in cached plan.
 create table list_parted (a int) partition by list(a);
-create table list_part_null partition of list_parted for values in (null);
-create table list_part_1 partition of list_parted for values in (1);
 create table list_part_def partition of list_parted default;
 prepare pstmt_def_insert (int) as insert into list_part_def values($1);
--- should fail
 execute pstmt_def_insert(null);
-ERROR:  new row for relation "list_part_def" violates partition constraint
-DETAIL:  Failing row contains (null).
+execute pstmt_def_insert(1);
+delete from list_parted where a=1;
+create table list_part_1 partition of list_parted for values in (1, 2);
+-- should fail
 execute pstmt_def_insert(1);
 ERROR:  new row for relation "list_part_def" violates partition constraint
 DETAIL:  Failing row contains (1).
-alter table list_parted detach partition list_part_null;
--- should be ok
+delete from list_parted where a is null;
+create table list_part_null (like list_parted);
+alter table list_parted attach partition list_part_null for values in (null);
+-- should fail
 execute pstmt_def_insert(null);
+ERROR:  new row for relation "list_part_def" violates partition constraint
+DETAIL:  Failing row contains (null).
 drop table list_part_1;
 -- should be ok
 execute pstmt_def_insert(1);
+alter table list_parted detach partition list_part_null;
+-- should be ok
+execute pstmt_def_insert(null);
 drop table list_parted;
 deallocate pstmt_def_insert;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 2346c1f..493acaf 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2115,13 +2115,13 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 
--- check partition cannot be attached if default exists
+-- check partition cannot be attached if default has some row for its values
 CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 INSERT INTO list_parted2_def VALUES (11, 'z');
 CREATE TABLE part_3 (LIKE list_parted2);
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
--- should be ok after detaching a default partition
-ALTER TABLE list_parted2 DETACH PARTITION list_parted2_def;
+-- should be ok after deleting the bad row
+DELETE FROM list_parted2_def WHERE a = 11;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
 
 -- adding constraints that describe the desired partition constraint
@@ -2191,6 +2191,18 @@ ALTER TABLE part_5 DROP CONSTRAINT check_a;
 ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT NULL;
 ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
 
+-- check that leaf partitions of default partition are scanned when
+-- attaching a partitioned table.
+ALTER TABLE part_5 DROP CONSTRAINT check_a;
+CREATE TABLE part5_def PARTITION OF part_5 DEFAULT PARTITION BY LIST(a);
+CREATE TABLE part5_def_p1 PARTITION OF part5_def FOR VALUES IN (5);
+INSERT INTO part5_def_p1 VALUES (5, 'y');
+CREATE TABLE part5_p1 (LIKE part_5);
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+-- should be ok after deleting the bad row
+DELETE FROM part5_def_p1 WHERE b = 'y';
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+
 -- check that the table being attached is not already a partition
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 650c1f5..f44c0e0 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -439,20 +439,16 @@ CREATE TABLE list_parted (
 CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
 CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
 CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
 
 -- syntax does not allow empty list of values for list partitions
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
 -- trying to specify range for list partitioned table
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
 
--- check default partition cannot be created more than once
-CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
-CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
--- check that no partition can be created after default partition
-CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (3);
-
 -- specified literal can't be cast to the partition column data type
 CREATE TABLE bools (
 	a bool
@@ -537,9 +533,13 @@ CREATE TABLE list_parted2 (
 ) PARTITION BY LIST (a);
 CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
 CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+-- check default partition overlap
+INSERT INTO list_parted2 VALUES('X');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y');
 
 CREATE TABLE range_parted2 (
 	a int
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 1154d0c..624f171 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -118,27 +118,23 @@ create table list_parted (
 create table part_aa_bb partition of list_parted FOR VALUES IN ('aa', 'bb');
 create table part_cc_dd partition of list_parted FOR VALUES IN ('cc', 'dd');
 create table part_null partition of list_parted FOR VALUES IN (null);
+create table part_default partition of list_parted default;
 
 -- fail
 insert into part_aa_bb values ('cc', 1);
 insert into part_aa_bb values ('AAa', 1);
 insert into part_aa_bb values (null);
+insert into part_default values ('aa', 2);
+insert into part_default values (null, 2);
 -- ok
 insert into part_cc_dd values ('cC', 1);
 insert into part_null values (null, 0);
+insert into part_default values ('Zz', 2);
 
 -- check in case of multi-level partitioned table
 create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b);
 create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
 create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
-
--- test default partition
-create table part_default partition of list_parted default;
--- fail
-insert into part_default values ('aa', 2);
-insert into part_default values (null, 2);
--- ok
-insert into part_default values ('Zz', 2);
 -- check in case of multi-level default partitioned table
 drop table part_default;
 create table part_default partition of list_parted default partition by range(b);
@@ -157,9 +153,6 @@ insert into part_ee_ff2 values ('ff', 11);
 insert into part_default_p1 values ('cd', 25);
 insert into part_default_p2 values ('de', 35);
 insert into list_parted values ('ab', 21);
--- drop default, as we need to add some more partitions to test tuple routing
-select tableoid::regclass, * from list_parted;
-drop table part_default;
 
 -- Check tuple routing for partitioned tables
 
diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql
index 89ddf3f..e8814e1 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -160,18 +160,24 @@ select cachebug();
 -- Check that addition or removal of any partition is correctly dealt with by
 -- default partition table when it is being used in cached plan.
 create table list_parted (a int) partition by list(a);
-create table list_part_null partition of list_parted for values in (null);
-create table list_part_1 partition of list_parted for values in (1);
 create table list_part_def partition of list_parted default;
 prepare pstmt_def_insert (int) as insert into list_part_def values($1);
--- should fail
 execute pstmt_def_insert(null);
 execute pstmt_def_insert(1);
-alter table list_parted detach partition list_part_null;
--- should be ok
+delete from list_parted where a=1;
+create table list_part_1 partition of list_parted for values in (1, 2);
+-- should fail
+execute pstmt_def_insert(1);
+delete from list_parted where a is null;
+create table list_part_null (like list_parted);
+alter table list_parted attach partition list_part_null for values in (null);
+-- should fail
 execute pstmt_def_insert(null);
 drop table list_part_1;
 -- should be ok
 execute pstmt_def_insert(1);
+alter table list_parted detach partition list_part_null;
+-- should be ok
+execute pstmt_def_insert(null);
 drop table list_parted;
 deallocate pstmt_def_insert;
-- 
1.9.1

0006-Refactor-default-partitioning-to-re-use-code.patch0000664000175000017500000004152113131471566022422 0ustar  jeevanjeevanFrom 9ff068d71a4d350fd3e9752148d73164f879a5fa Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Thu, 13 Jul 2017 00:34:48 +0530
Subject: [PATCH 6/7] Refactor default partitioning to re-use code

This patch uses the new functions created in the first patch in this
series.

Ashutosh Bapat, revised by me.
---
 src/backend/catalog/partition.c           | 89 ++++++++++++++++++-------------
 src/backend/commands/tablecmds.c          | 69 ++++++++++++++++++++----
 src/include/catalog/partition.h           |  3 ++
 src/include/commands/tablecmds.h          |  4 ++
 src/test/regress/expected/alter_table.out |  8 ++-
 src/test/regress/sql/alter_table.sql      |  3 ++
 6 files changed, 126 insertions(+), 50 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 0b747eb..cad154c 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -29,6 +29,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_type.h"
+#include "commands/tablecmds.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -153,8 +154,6 @@ static int32 partition_bound_cmp(PartitionKey key,
 static int partition_bound_bsearch(PartitionKey key,
 						PartitionBoundInfo boundinfo,
 						void *probe, bool probe_is_bound, bool *is_equal);
-static void check_default_allows_bound(Relation parent,
-						   PartitionBoundSpec *new_spec);
 
 /*
  * RelationBuildPartitionDesc
@@ -879,56 +878,45 @@ check_new_partition_bound(char *relname, Relation parent,
 						relname, get_rel_name(partdesc->oids[with])),
 				 parser_errposition(pstate, spec->location)));
 	}
-
-	/*
-	 * If the default partition exists, its partition constraints will change
-	 * after the addition of this new partition such that it won't allow any
-	 * row that qualifies for this new partition. So, check if the existing
-	 * data in the default partition satisfies this *would be* default
-	 * partition constraint. In case the new partition bound being checked
-	 * itself is a DEFAULT bound, this check shouldn't be triggered as there
-	 * won't already exists the default partition in such a case.
-	 */
-	if (boundinfo && partition_bound_has_default(boundinfo))
-		check_default_allows_bound(parent, spec);
 }
 
 /*
  * check_default_allows_bound
  *
  * This function checks if there exists a row in the default partition that
- * fits in the new partition and throws an error if it finds one.
+ * fits in the new partition being added and throws an error if it finds one.
  */
-static void
-check_default_allows_bound(Relation parent, PartitionBoundSpec *new_spec)
+void
+check_default_allows_bound(Relation parent, Relation default_rel,
+						   PartitionBoundSpec *new_spec)
 {
-	Relation	default_rel;
-	List	   *new_part_constraints = NIL;
+	List	   *new_part_constraints;
+	List	   *def_part_constraints;
 	List	   *all_parts;
 	ListCell   *lc;
-	PartitionDescData *part_desc = RelationGetPartitionDesc(parent);
 
 	/* 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 = (List *) eval_const_expressions(NULL,
-														   (Node *) new_part_constraints);
-	new_part_constraints =
-		(List *) canonicalize_qual((Expr *) new_part_constraints);
-	new_part_constraints = list_make1(make_ands_explicit(new_part_constraints));
+	def_part_constraints =
+		get_default_part_validation_constraint(new_part_constraints);
 
 	/*
-	 * Generate the constraint and default execution states.
-	 *
-	 * The default partition must be already having an AccessExclusiveLock.
+	 * If the existing constraints on the default partition imply that it will
+	 * not contain any row that would belong to the new partition, we can
+	 * avoid scanning the default partition.
 	 */
-	default_rel = heap_open(get_default_partition_oid(RelationGetRelid(parent)),
-							NoLock);
+	if (canSkipPartConstraintValidation(default_rel, def_part_constraints,
+										RelationGetPartitionKey(parent)))
+	{
+		ereport(INFO,
+				(errmsg("partition constraint for table \"%s\" is implied by existing constraints",
+						RelationGetRelationName(default_rel))));
+		return;
+	}
 
+	/* Bad luck, scan the default partition and its subpartitions, if any. */
 	if (default_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		all_parts = find_all_inheritors(RelationGetRelid(default_rel),
 										AccessExclusiveLock, NULL);
@@ -970,12 +958,14 @@ check_default_allows_bound(Relation parent, PartitionBoundSpec *new_spec)
 								RelationGetRelationName(part_rel),
 								RelationGetRelationName(default_rel))));
 
-			heap_close(part_rel, NoLock);
+			if (RelationGetRelid(default_rel) != RelationGetRelid(part_rel))
+				heap_close(part_rel, NoLock);
+
 			continue;
 		}
 
 		tupdesc = CreateTupleDescCopy(RelationGetDescr(part_rel));
-		constr = linitial(new_part_constraints);
+		constr = linitial(def_part_constraints);
 		partition_constraint = (Expr *) map_partition_varattnos((List *) constr,
 																1, part_rel, parent);
 		estate = CreateExecutorState();
@@ -999,7 +989,7 @@ check_default_allows_bound(Relation parent, PartitionBoundSpec *new_spec)
 			ExecStoreTuple(tuple, tupslot, InvalidBuffer, false);
 			econtext->ecxt_scantuple = tupslot;
 
-			if (ExecCheck(partqualstate, econtext))
+			if (!ExecCheck(partqualstate, econtext))
 				ereport(ERROR,
 						(errcode(ERRCODE_CHECK_VIOLATION),
 						 errmsg("updated partition constraint for default partition \"%s\" would be violated by some row",
@@ -1014,8 +1004,9 @@ check_default_allows_bound(Relation parent, PartitionBoundSpec *new_spec)
 		UnregisterSnapshot(snapshot);
 		ExecDropSingleTupleTableSlot(tupslot);
 		FreeExecutorState(estate);
-		/* keep our lock until commit */
-		heap_close(part_rel, NoLock);
+
+		if (RelationGetRelid(default_rel) != RelationGetRelid(part_rel))
+			heap_close(part_rel, NoLock);	/* keep the lock until commit */
 	}
 }
 
@@ -2661,3 +2652,25 @@ update_default_partition_oid(Oid parentId, Oid defaultPartId)
 	heap_freetuple(tuple);
 	heap_close(pg_partitioned_table, RowExclusiveLock);
 }
+
+/*
+ * get_default_part_validation_constraint
+ *
+ * Given partition constraints, this function returns *would be* default
+ * partition constraint.
+ */
+List *
+get_default_part_validation_constraint(List *new_part_constraints)
+{
+	Expr	   *defPartConstraint;
+
+	defPartConstraint = make_ands_explicit(new_part_constraints);
+	defPartConstraint = makeBoolExpr(NOT_EXPR,
+									 list_make1(defPartConstraint),
+									 -1);
+	defPartConstraint = (Expr *) eval_const_expressions(NULL,
+														(Node *) defPartConstraint);
+	defPartConstraint = canonicalize_qual(defPartConstraint);
+
+	return list_make1(defPartConstraint);
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index b875dbe..5449790 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -168,6 +168,8 @@ typedef struct AlteredTableInfo
 	bool		chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
 	char		newrelpersistence;	/* if above is true */
 	Expr	   *partition_constraint;	/* for attach partition validation */
+	/* true, if is a default partition or a child of default partition */
+	bool		is_default_partition;
 	/* Objects to rebuild after completing ALTER TYPE operations */
 	List	   *changedConstraintOids;	/* OIDs of constraints to rebuild */
 	List	   *changedConstraintDefs;	/* string definitions of same */
@@ -769,7 +771,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		ParseState *pstate;
 		Oid			parentId = linitial_oid(inheritOids),
 					defaultPartOid;
-		Relation	parent;
+		Relation	parent,
+					defaultRel;
 
 		/* Already have strong enough lock on the parent */
 		parent = heap_open(parentId, NoLock);
@@ -806,7 +809,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		 */
 		defaultPartOid = get_default_partition_oid(RelationGetRelid(parent));
 		if (OidIsValid(defaultPartOid))
-			LockRelationOid(defaultPartOid, AccessExclusiveLock);
+			defaultRel = heap_open(defaultPartOid, AccessExclusiveLock);
 
 		/* Tranform the bound values */
 		pstate = make_parsestate(NULL);
@@ -821,6 +824,20 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		 */
 		check_new_partition_bound(relname, parent, bound);
 
+		/*
+		 * If the default partition exists, its partition constraints will
+		 * change after the addition of this new partition such that it won't
+		 * allow any row that qualifies for this new partition. So, check that
+		 * the existing data in the default partition satisfies the constraint
+		 * as it will exist after adding this partition.
+		 */
+		if (OidIsValid(defaultPartOid))
+		{
+			check_default_allows_bound(parent, defaultRel, bound);
+			/* Keep the lock until commit. */
+			heap_close(defaultRel, NoLock);
+		}
+
 		/* Update the pg_class entry. */
 		StorePartitionBound(rel, parent, bound);
 
@@ -4611,9 +4628,16 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 			}
 
 			if (partqualstate && !ExecCheck(partqualstate, econtext))
-				ereport(ERROR,
-						(errcode(ERRCODE_CHECK_VIOLATION),
-						 errmsg("partition constraint is violated by some row")));
+			{
+				if (tab->is_default_partition)
+					ereport(ERROR,
+							(errcode(ERRCODE_CHECK_VIOLATION),
+							 errmsg("updated partition constraint for default partition would be violated by some row")));
+				else
+					ereport(ERROR,
+							(errcode(ERRCODE_CHECK_VIOLATION),
+							 errmsg("partition constraint is violated by some row")));
+			}
 
 			/* Write the tuple out to the new relation */
 			if (newrel)
@@ -13450,7 +13474,7 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
  *
  * The function returns true if it's safe to skip the scan, false otherwise.
  */
-static bool
+bool
 canSkipPartConstraintValidation(Relation scanRel, List *partConstraint,
 								PartitionKey key)
 {
@@ -13537,7 +13561,8 @@ canSkipPartConstraintValidation(Relation scanRel, List *partConstraint,
  */
 static void
 validatePartitionConstraints(List **wqueue, Relation scanRel,
-							 List *partConstraint, Relation rel)
+							 List *partConstraint, Relation rel,
+							 bool scanrel_is_default)
 {
 	PartitionKey key = RelationGetPartitionKey(rel);
 	List	   *all_parts;
@@ -13596,6 +13621,7 @@ validatePartitionConstraints(List **wqueue, Relation scanRel,
 		tab->partition_constraint = (Expr *)
 			map_partition_varattnos((List *) constr, 1,
 									part_rel, rel);
+		tab->is_default_partition = scanrel_is_default;
 		/* keep our lock until commit */
 		if (part_rel != scanRel)
 			heap_close(part_rel, NoLock);
@@ -13621,6 +13647,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	TupleDesc	tupleDesc;
 	ObjectAddress address;
 	Oid			defaultPartOid;
+	List	   *partBoundConstraint;
 	const char *trigger_name;
 
 	/*
@@ -13796,8 +13823,8 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	 * If the parent itself is a partition, make sure to include its
 	 * constraint as well.
 	 */
-	partConstraint = list_concat(get_qual_from_partbound(attachRel, rel,
-														 cmd->bound),
+	partBoundConstraint = get_qual_from_partbound(attachRel, rel, cmd->bound);
+	partConstraint = list_concat(partBoundConstraint,
 								 RelationGetPartitionQual(rel));
 
 	/* Skip validation if there are no constraints to validate. */
@@ -13809,7 +13836,29 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 		partConstraint = list_make1(make_ands_explicit(partConstraint));
 
 		/* Validate partition constraints against the table being attached. */
-		validatePartitionConstraints(wqueue, attachRel, partConstraint, rel);
+		validatePartitionConstraints(wqueue, attachRel, partConstraint, rel, false);
+
+		/*
+		 * Check whether default partition has a row that would fit the
+		 * partition being attached by negating the partition constraint
+		 * derived from the bounds(the partition constraint never evaluates to
+		 * NULL, so negating it like this is safe).
+		 */
+		defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
+		if (OidIsValid(defaultPartOid))
+		{
+			Relation	default_rel;
+			List	   *defPartConstraint;
+
+			/* We already have taken a lock on default partition. */
+			default_rel = heap_open(defaultPartOid, NoLock);
+			defPartConstraint = get_default_part_validation_constraint(partBoundConstraint);
+			validatePartitionConstraints(wqueue, default_rel, defPartConstraint,
+										 rel, true);
+
+			/* keep our lock until commit. */
+			heap_close(default_rel, NoLock);
+		}
 	}
 
 	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 7800c96..c3153d7 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -100,5 +100,8 @@ extern int get_partition_for_tuple(PartitionDispatch *pd,
 						TupleTableSlot **failed_slot);
 extern Oid	get_default_partition_oid(Oid parentId);
 extern void update_default_partition_oid(Oid parentId, Oid defaultPartId);
+extern void check_default_allows_bound(Relation parent, Relation defaultRel,
+						   PartitionBoundSpec *new_spec);
+extern List *get_default_part_validation_constraint(List *new_part_constaints);
 
 #endif							/* PARTITION_H */
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index abd31b6..bb40d88 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -18,6 +18,7 @@
 #include "catalog/dependency.h"
 #include "catalog/objectaddress.h"
 #include "nodes/parsenodes.h"
+#include "catalog/partition.h"
 #include "storage/lock.h"
 #include "utils/relcache.h"
 
@@ -87,4 +88,7 @@ extern void RangeVarCallbackOwnsTable(const RangeVar *relation,
 
 extern void RangeVarCallbackOwnsRelation(const RangeVar *relation,
 							 Oid relId, Oid oldRelId, void *noCatalogs);
+extern bool canSkipPartConstraintValidation(Relation scanRel, List *partConstraint,
+								PartitionKey key);
+
 #endif							/* TABLECMDS_H */
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 938fe28..88e4a89 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3296,7 +3296,7 @@ CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 INSERT INTO list_parted2_def VALUES (11, 'z');
 CREATE TABLE part_3 (LIKE list_parted2);
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
-ERROR:  updated partition constraint for default partition "list_parted2_def" would be violated by some row
+ERROR:  updated partition constraint for default partition would be violated by some row
 -- should be ok after deleting the bad row
 DELETE FROM list_parted2_def WHERE a = 11;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
@@ -3315,6 +3315,10 @@ ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
 ALTER TABLE part_3_4 ALTER a SET NOT NULL;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
 INFO:  partition constraint for table "part_3_4" is implied by existing constraints
+-- check if default partition scan skipped
+ALTER TABLE list_parted2_def ADD CONSTRAINT check_a CHECK (a IN (5, 6));
+CREATE TABLE part_7_8 PARTITION OF list_parted2 FOR VALUES IN (7, 8);
+INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
 -- check validation when attaching range partitions
 CREATE TABLE range_parted (
 	a int,
@@ -3369,7 +3373,7 @@ CREATE TABLE part5_def_p1 PARTITION OF part5_def FOR VALUES IN (5);
 INSERT INTO part5_def_p1 VALUES (5, 'y');
 CREATE TABLE part5_p1 (LIKE part_5);
 ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
-ERROR:  updated partition constraint for default partition "part5_def" would be violated by some row
+ERROR:  updated partition constraint for default partition would be violated by some row
 -- should be ok after deleting the bad row
 DELETE FROM part5_def_p1 WHERE b = 'y';
 ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 493acaf..38655ee 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2141,6 +2141,9 @@ ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
 ALTER TABLE part_3_4 ALTER a SET NOT NULL;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
 
+-- check if default partition scan skipped
+ALTER TABLE list_parted2_def ADD CONSTRAINT check_a CHECK (a IN (5, 6));
+CREATE TABLE part_7_8 PARTITION OF list_parted2 FOR VALUES IN (7, 8);
 
 -- check validation when attaching range partitions
 CREATE TABLE range_parted (
-- 
1.9.1

0007-Check-default-partitition-child-relations-predicate.patch0000664000175000017500000001106013131471566024014 0ustar  jeevanjeevanFrom 128118a93f358a3139c8553cfd97644ac45c0244 Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Thu, 13 Jul 2017 00:36:14 +0530
Subject: [PATCH 7/7] Check default partitition child relations predicate
 implication.

Add code to check_default_allows_bound() such that the default
partition children constraints are checked against new partition
constraints for implication and avoid scan of the child of which
existing constraints are implied by new default partition
constraints. Also, added testcase for the same.

Jeevan Ladhe.
---
 src/backend/catalog/partition.c           | 19 +++++++++++++++++++
 src/test/regress/expected/alter_table.out | 12 ++++++++++++
 src/test/regress/sql/alter_table.sql      |  9 +++++++++
 3 files changed, 40 insertions(+)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index cad154c..b31c272 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -941,7 +941,26 @@ check_default_allows_bound(Relation parent, Relation default_rel,
 
 		/* Lock already taken above. */
 		if (part_relid != RelationGetRelid(default_rel))
+		{
 			part_rel = heap_open(part_relid, NoLock);
+
+			/*
+			 * If the partition constraints on default partition child imply
+			 * that it will not contain any row that would belong to the new
+			 * partition, we can avoid scanning the child table.
+			 */
+			if (canSkipPartConstraintValidation(part_rel,
+												def_part_constraints,
+												RelationGetPartitionKey(parent)))
+			{
+				ereport(INFO,
+						(errmsg("partition constraint for table \"%s\" is implied by existing constraints",
+								RelationGetRelationName(part_rel))));
+
+				heap_close(part_rel, NoLock);
+				continue;
+			}
+		}
 		else
 			part_rel = default_rel;
 
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 88e4a89..9a5f6bf 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3319,6 +3319,18 @@ INFO:  partition constraint for table "part_3_4" is implied by existing constrai
 ALTER TABLE list_parted2_def ADD CONSTRAINT check_a CHECK (a IN (5, 6));
 CREATE TABLE part_7_8 PARTITION OF list_parted2 FOR VALUES IN (7, 8);
 INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
+-- check if default partition child relation scan skipped
+DROP TABLE list_parted2_def;
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT PARTITION BY RANGE (a);
+CREATE TABLE list_parted2_def_p1 PARTITION OF list_parted2_def FOR VALUES FROM (21) TO (30);
+CREATE TABLE list_parted2_def_p2 PARTITION OF list_parted2_def FOR VALUES FROM (31) TO (40);
+ALTER TABLE list_parted2_def_p1 ADD CONSTRAINT check_a CHECK (a IN (21, 22));
+ALTER TABLE list_parted2_def_p2 ADD CONSTRAINT check_a CHECK (a IN (31, 32));
+CREATE TABLE part_9 PARTITION OF list_parted2 FOR VALUES IN (9);
+INFO:  partition constraint for table "list_parted2_def_p1" is implied by existing constraints
+INFO:  partition constraint for table "list_parted2_def_p2" is implied by existing constraints
+CREATE TABLE part_31 PARTITION OF list_parted2 FOR VALUES IN (31);
+INFO:  partition constraint for table "list_parted2_def_p1" is implied by existing constraints
 -- check validation when attaching range partitions
 CREATE TABLE range_parted (
 	a int,
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 38655ee..935b24b 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2144,6 +2144,15 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
 -- check if default partition scan skipped
 ALTER TABLE list_parted2_def ADD CONSTRAINT check_a CHECK (a IN (5, 6));
 CREATE TABLE part_7_8 PARTITION OF list_parted2 FOR VALUES IN (7, 8);
+-- check if default partition child relation scan skipped
+DROP TABLE list_parted2_def;
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT PARTITION BY RANGE (a);
+CREATE TABLE list_parted2_def_p1 PARTITION OF list_parted2_def FOR VALUES FROM (21) TO (30);
+CREATE TABLE list_parted2_def_p2 PARTITION OF list_parted2_def FOR VALUES FROM (31) TO (40);
+ALTER TABLE list_parted2_def_p1 ADD CONSTRAINT check_a CHECK (a IN (21, 22));
+ALTER TABLE list_parted2_def_p2 ADD CONSTRAINT check_a CHECK (a IN (31, 32));
+CREATE TABLE part_9 PARTITION OF list_parted2 FOR VALUES IN (9);
+CREATE TABLE part_31 PARTITION OF list_parted2 FOR VALUES IN (31);
 
 -- check validation when attaching range partitions
 CREATE TABLE range_parted (
-- 
1.9.1

#147Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Robert Haas (#132)
Re: Adding support for Default partition in partitioning

Hi Robert,

I have tried to address your comments in the V22 set of patches[1]/messages/by-id/CAOgcT0OARciE2X+U0rjSKp9VuC279dYcCGkc3nCWKhHQ1_m2rw@mail.gmail.com.
Please find my feedback inlined on your comments.

On Thu, Jun 15, 2017 at 1:21 AM, Robert Haas <robertmhaas@gmail.com> wrote:

- Needs to be rebased over b08df9cab777427fdafe633ca7b8abf29817aa55.

Rebased on master latest commit: ca793c59a51e94cedf8cbea5c29668bf8fa298f3

- Still no documentation.

Yes, this is long pending, and I will make this is a priority to get it
included
in next set of my patches.

- Should probably be merged with the patch to add default partitioning

for ranges.

Beena is already rebasing her patch on my latest patches, so I think getting
them merged here won't be an issue, mostly will be just like one more patch
on top my patches.

Other stuff I noticed:

- The regression tests don't seem to check that the scan-skipping
logic works as expected. We have regression tests for that case for
attaching regular partitions, and it seems like it would be worth
testing the default-partition case as well.

Added a test case for default in alter_table.sql.

- check_default_allows_bound() assumes that if

canSkipPartConstraintValidation() fails for the default partition, it
will also fail for every subpartition of the default partition. That
is, once we commit to scanning one child partition, we're committed to
scanning them all. In practice, that's probably not a huge
limitation, but if it's not too much code, we could keep the top-level
check but also check each partitioning individually as we reach it,
and skip the scan for any individual partitions for which the
constraint can be proven. For example, suppose the top-level table is
list-partitioned with a partition for each of the most common values,
and then we range-partition the default partition.

I have tried to address this in patch 0007, please let me know your views on
that patch.

- The changes to the regression test results in 0004 make the error

messages slightly worse. The old message names the default partition,
whereas the new one does not. Maybe that's worth avoiding.

The only way for this, I can think of to achieve this is to store the name
of
the default relation in AlteredTableInfo, currently I am using a flag for
realizing if the scanned table is a default partition to throw specific
error.
But, IMO storing a string in AlteredTableInfo just for error purpose might
be
overkill. Your suggestions?

Specific comments:

+ * Also, invalidate the parent's and a sibling default partition's
relcache,
+ * so that the next rebuild will load the new partition's info into
parent's
+ * partition descriptor and default partition constraints(which are
dependent
+ * on other partition bounds) are built anew.

I find this a bit unclear, and it also repeats the comment further
down. Maybe something like: Also, invalidate the parent's relcache
entry, so that the next rebuild will load he new partition's info into
its partition descriptor. If there is a default partition, we must
invalidate its relcache entry as well.

Done.

+    /*
+     * The default partition constraints depend upon the partition bounds
of
+     * other partitions. Adding a new(or even removing existing) partition
+     * would invalidate the default partition constraints. Invalidate the
+     * default partition's relcache so that the constraints are built
anew and
+     * any plans dependent on those constraints are invalidated as well.
+     */

Here, I'd write: The partition constraint for the default partition
depends on the partition bounds of every other partition, so we must
invalidate the relcache entry for that partition every time a
partition is added or removed.

Done.

+                    /*
+                     * 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;
+                    }

To support default partitioning for range, this is going to have to be
moved above the switch rather than done inside of it. And there's
really no downside to putting it there.

Done.

+ * constraint, by *proving* that the existing constraints of the table
+ * *imply* the given constraints.  We include the table's check
constraints and

Both the comma and the asterisks are unnecessary.

Done.

+ * Check whether all rows in the given table (scanRel) obey given
partition

obey the given

I think the larger comment block could be tightened up a bit, like
this: Check whether all rows in the given table obey the given
partition constraint; if so, it can be attached as a partition. We do
this by scanning the table (or all of its leaf partitions) row by row,
except when the existing constraints are sufficient to prove that the
new partitioning constraint must already hold.

Done.

+ /* Check if we can do away with having to scan the table being
attached. */

If possible, skip the validation scan.

Fixed.

+     * Set up to have the table be scanned to validate the partition
+     * constraint If it's a partitioned table, we instead schedule its
leaf
+     * partitions to be scanned.

I suggest: Prepare to scan the default partition (or, if it is itself
partitioned, all of its leaf partitions).

Done.

+    int         default_index;  /* Index of the default partition if any;
-1
+                                 * if there isn't one */

"if any" is a bit redundant with "if there isn't one"; note the
phrasing of the preceding entry.

Done.

+        /*
+         * Skip if it's a partitioned table. Only RELKIND_RELATION
relations
+         * (ie, leaf partitions) need to be scanned.
+         */
+        if (part_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+            part_rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)

The comment talks about what must be included in our list of things to
scan, but the code tests for the things that can be excluded. I
suspect the comment has the right idea and the code should be adjusted
to match, but anyway they should be consistent. Also, the correct way
to punctuate i.e. is like this: (i.e. leaf partitions) You should have
a period after each letter, but no following comma.

Done.

+ * The default partition must be already having an
AccessExclusiveLock.

I think we should instead change DefineRelation to open (rather than
just lock) the default partition and pass the Relation as an argument
here so that we need not reopen it.

I have fixed this as a part of patch 0006.

+            /* Construct const from datum */
+            val = makeConst(key->parttypid[0],
+                            key->parttypmod[0],
+                            key->parttypcoll[0],
+                            key->parttyplen[0],
+                            *boundinfo->datums[i],
+                            false,      /* isnull */
+                            key->parttypbyval[0] /* byval */ );

The /* byval */ comment looks a bit redundant, but I think this could
use a comment along the lines of: /* Only single-column list
partitioning is supported, so we only need to worry about the
partition key with index 0. */ And I'd also add an Assert() verifying
the the partition key has exactly 1 column, so that this breaks a bit
more obviously if someone removes that restriction in the future.

Removed the /* byval */ comment.
The assert is taken care as part of commit
5efccc1cb43005a832776ed9158d2704fd976f8f.

+         * Handle NULL partition key here if there's a null-accepting list
+         * partition, else later it will be routed to the default
partition if
+         * one exists.

This isn't a great update of the existing comment -- it's drifted from
explaining the code to which it is immediately attached to a more
general discussion of NULL handling. I'd just say something like: If
this is a NULL, send it to the null-accepting partition. Otherwise,
route by searching the array of partition bounds.

Done.

+                if (tab->is_default_partition)
+                    ereport(ERROR,
+                            (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                             errmsg("updated partition constraint for
default partition would be violated by some row")));
+                else
+                    ereport(ERROR,
+                            (errcode(ERRCODE_CHECK_VIOLATION),

While there's room for debate about the correct error code here, it's
hard for me to believe that it's correct to use one error code for the
is_default_partition case and a different error code the rest of the
time.

Per discussion here[2], I had changed this error code, but as of now I have

restored this to ERRCODE_CHECK_VIOLATION to be consistent with the error
when
non-default partition being attached has some existing row that violates
partition constraints. Similarly, for consistency I have changed this in
check_default_allows_bound() too.
I agree that there is still a room for debate here after this change too,
and
also this change reverts the suggestion by Ashutosh.

+ * previously cached default partition constraints; those

constraints
+ * won't stand correct after addition(or even removal) of a
partition.

won't be correct after addition or removal

Done.

+         * allow any row that qualifies for this new partition. So, check
if
+         * the existing data in the default partition satisfies this
*would be*
+         * default partition constraint.

check that the existing data in the default partition satisfies the
constraint as it will exist after adding this partition

Done.

+     * Need to take a lock on the default partition, refer comment for
locking
+     * the default partition in DefineRelation().

I'd say: We must also lock the default partition, for the same reasons
explained in DefineRelation().

And similarly in the other places that refer to that same comment.

Done.

+    /*
+     * In case of the default partition, the constraint is of the form
+     * "!(result)" i.e. one of the following two forms:
+     * 1. NOT ((keycol IS NULL) OR (keycol = ANY (arr)))
+     * 2. NOT ((keycol IS NOT NULL) AND (keycol = ANY (arr))), where arr
is an
+     * array of datums in boundinfo->datums.
+     */

Does this survive pgindent? You might need to surround the comment
with dashes to preserve formatting.

Yes, this din't survive pg_indent, but even adding dashes '--' did not make

the deal(may be I misunderstood the workaround), I have instead added
blank line in the bullets.

I think it would be worth adding a little more text this comment,

something like this: Note that, in general, applying NOT to a
constraint expression doesn't necessarily invert the set of rows it
accepts, because NOT NULL is NULL. However, the partition constraints
we construct here never evaluate to NULL, so applying NOT works as
intended.

Added.

+     * Check whether default partition has a row that would fit the
partition
+     * being attached by negating the partition constraint derived from
the
+     * bounds. Since default partition is already part of the partitioned
+     * table, we don't need to validate the constraints on the partitioned
+     * table.

Here again, I'd add to the end of the first sentence a parenthetical
note, like this: ...from the bounds (the partition constraint never
evaluates to NULL, so negating it like this is safe).

Done.

I don't understand the second sentence. It seems to contradict the first
one.

Fixed, I removed the second sentence.

+extern List *get_default_part_validation_constraint(List
*new_part_constaints);
#endif /* PARTITION_H */

There should be a blank line after the last prototype and before the
#endif.

+-- default partition table when it is being used in cahced plan.

Typo.

Fixed.

Thanks,
Jeevan Ladhe

Refs:
[1]: /messages/by-id/CAOgcT0OARciE2X+U0rjSKp9VuC279dYcCGkc3nCWKhHQ1_m2rw@mail.gmail.com
/messages/by-id/CAOgcT0OARciE2X+U0rjSKp9VuC279dYcCGkc3nCWKhHQ1_m2rw@mail.gmail.com
[2]: /messages/by-id/CA+TgmobkFaptsmQiP94sbAKTtDKS6Azz+P4Bw1FxzmNrnyVa0w@mail.gmail.com
/messages/by-id/CA+TgmobkFaptsmQiP94sbAKTtDKS6Azz+P4Bw1FxzmNrnyVa0w@mail.gmail.com

#148Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Ashutosh Bapat (#135)
Re: Adding support for Default partition in partitioning

Hi Ashutosh,

I have tried to address your comments in the V22 set of patches[1]/messages/by-id/CAOgcT0OARciE2X%25 2BU0rjSKp9VuC279dYcCGkc3nCWKhHQ1_m2rw%40mail.gmail.com.
Please find my feedback inlined on your comments.

On Thu, Jun 15, 2017 at 10:24 PM, Ashutosh Bapat <
ashutosh.bapat@enterprisedb.com> wrote:

Some more comments on the latest set of patches.

In heap_drop_with_catalog(), we heap_open() the parent table to get the
default partition OID, if any. If the relcache doesn't have an entry for
the
parent, this means that the entry will be created, only to be invalidated
at
the end of the function. If there is no default partition, this all is
completely unnecessary. We should avoid heap_open() in this case. This also
means that get_default_partition_oid() should not rely on the relcache
entry,
but should growl through pg_inherit to find the default partition.

Instead of reading the defaultOid from cache, as suggested by Amit here[2]/messages/by-id/35d68d49-555f-421a-99f8-185a44d085a4@lab.ntt.co.jp,
now
I have stored this in pg_partition_table, and reading it from there.

In get_qual_for_list(), if the table has only default partition, it won't
have
any boundinfo. In such a case the default partition's constraint would
come out
as (NOT ((a IS NOT NULL) AND (a = ANY (ARRAY[]::integer[])))). The empty
array
looks odd and may be we spend a few CPU cycles executing ANY on an empty
array.
We have the same problem with a partition containing only NULL value. So,
may
be this one is not that bad.

Fixed.

Please add a testcase to test addition of default partition as the first
partition.

Added this in insert.sql rather than create_table.sql, as the purpose here

is to test if default being a first partition accepts any values for the key
including null.

get_qual_for_list() allocates the constant expressions corresponding to the
datums in CacheMemoryContext while constructing constraints for a default
partition. We do not do this for other partitions. We may not be
constructing
the constraints for saving in the cache. For example, ATExecAttachPartition
constructs the constraints for validation. In such a case, this code will
unnecessarily clobber the cache memory. generate_partition_qual() copies
the
partition constraint in the CacheMemoryContext.

Removed CacheMemoryContext.
I thought once the partition qual is generated, it should be in remain in
the memory context, but when it is needed, it is indirectly taken care by
generate_partition_qual() in following code:

/* Save a copy in the relcache */
oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
rel->rd_partcheck = copyObject(result);
MemoryContextSwitchTo(oldcxt);

+   if (spec->is_default)
+   {
+       result = list_make1(make_ands_explicit(result));
+       result = list_make1(makeBoolExpr(NOT_EXPR, result, -1));
+   }

If the "result" is an OR expression, calling make_ands_explicit() on it
would
create AND(OR(...)) expression, with an unnecessary AND. We want to avoid
that?

Actually the OR expression here is generated using a call to makeBoolExpr(),
which returns a single expression node, and if this is passed to
make_ands_explicit(), it checks if the list length is node, returns the
initial
node itself, and hence AND(OR(...)) kind of expression is not generated
here.

+       if (cur_index < 0 && (partition_bound_has_default(
partdesc->boundinfo)))
+           cur_index = partdesc->boundinfo->default_index;
+
The partition_bound_has_default() check is unnecessary since we check for
cur_index < 0 next anyway.

Done.

+ *
+ * Given the parent relation checks if it has default partition, and if it
+ * does exist returns its oid, otherwise returns InvalidOid.
+ */
May be reworded as "If the given relation has a default partition, this
function returns the OID of the default partition. Otherwise it returns
InvalidOid."

I have reworded this to:

"If the given relation has a default partition return the OID of the default
partition, otherwise return InvalidOid."

+Oid
+get_default_partition_oid(Relation parent)
+{
+   PartitionDesc partdesc = RelationGetPartitionDesc(parent);
+
+   if (partdesc->boundinfo && partition_bound_has_default(
partdesc->boundinfo))
+       return partdesc->oids[partdesc->boundinfo->default_index];
+
+   return InvalidOid;
+}
An unpartitioned table would not have partdesc set set. So, this function
will
segfault if we pass an unpartitioned table. Either Assert that partdesc
should
exist or check for its NULL-ness.

Fixed.

+    defaultPartOid = get_default_partition_oid(rel);
+    if (OidIsValid(defaultPartOid))
+        ereport(ERROR,
+                (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                 errmsg("there exists a default partition for table
\"%s\", cannot attach a new partition",
+                        RelationGetRelationName(rel))));
+
Should be done before heap_open on the table being attached. If we are not
going to attach the partition, there's no point in instantiating its
relcache.

Fixed.

The comment in heap_drop_with_catalog() should mention why we lock the
default
partition before locking the table being dropped.

extern List *preprune_pg_partitions(PlannerInfo *root, RangeTblEntry
*rte,
Index rti, Node *quals, LOCKMODE lockmode);
-
#endif /* PARTITION_H */
Unnecessary hunk.

I could not locate this hunk.

Regards,
Jeevan Ladhe

Refs:
[1]: /messages/by-id/CAOgcT0OARciE2X%25 2BU0rjSKp9VuC279dYcCGkc3nCWKhHQ1_m2rw%40mail.gmail.com
2BU0rjSKp9VuC279dYcCGkc3nCWKhHQ1_m2rw%40mail.gmail.com
[2]: /messages/by-id/35d68d49-555f-421a-99f8-185a44d085a4@lab.ntt.co.jp
/messages/by-id/35d68d49-555f-421a-99f8-185a44d085a4@lab.ntt.co.jp

#149Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Kyotaro HORIGUCHI (#138)
Re: Adding support for Default partition in partitioning

Hi,

I have tried to address your comments in the V22 set of patches[1]/messages/by-id/CAOgcT0OARciE2X%25 2BU0rjSKp9VuC279dYcCGkc3nCWKhHQ1_m2rw%40mail.gmail.com.
Please find my feedback inlined on your comments.

On Fri, Jun 16, 2017 at 1:46 PM, Kyotaro HORIGUCHI <
horiguchi.kyotaro@lab.ntt.co.jp> wrote:

Hello, I'd like to review this but it doesn't fit the master, as
Robert said. Especially the interface of predicate_implied_by is
changed by the suggested commit.

Anyway I have some comment on this patch with fresh eyes. I
believe the basic design so my comment below are from a rather
micro viewpoint.

- Considering on how canSkipPartConstraintValidation is called, I
*think* that RelationProvenValid() would be better. (But this
would be disappear by rebasing..)

I think RelationProvenValid() is bit confusing in the sense that, it does

not
imply the meaning that some constraint is being checke

- 0002 changes the interface of get_qual_for_list, but left
get_qual_for_range alone. Anyway get_qual_for_range will have
to do the similar thing soon.

Yes, this will be taken care with default partition for range.

- In check_new_partition_bound, "overlap" and "with" is
completely correlated with each other. "with > -1" means
"overlap = true". So overlap is not useless. ("with" would be
better to be "overlap_with" or somehting if we remove
"overlap")

Agree, probably this can be taken as a separate refactoring patch.

Currently,
for in case of default I have got rid of "overlap", and now use of "with"
and
that is also used just for code simplification.

- The error message of check_default_allows_bound is below.

"updated partition constraint for default partition \"%s\"
would be violated by some row"

This looks an analog of validateCheckConstraint but as my
understanding this function is called only when new partition
is added. This would be difficult to recognize in the
situation.

"the default partition contains rows that should be in
the new partition: \"%s\""

or something?

I think the current error message is more clearer. Agree that there might

be
sort of confusion if it's due to addition or attach partition, but we have
already stretched the message longer. I am open to suggestions here.

- In check_default_allows_bound, the iteration over partitions is
quite similar to what validateCheckConstraint does. Can we
somehow share validateCheckConstraint with this function?

May be we can, but I think again this can also be categorized as

refactoring
patch and done later maybe. Your thoughts?

- In the same function, skipping RELKIND_PARTITIONED_TABLE is
okay, but silently ignoring RELKIND_FOREIGN_TABLE doesn't seem
good. I think at least some warning should be emitted.

"Skipping foreign tables in the defalut partition. It might
contain rows that should be in the new partition." (Needs
preventing multple warnings in single call, maybe)

Currently we do not emit any warning when attaching a foreign table as a

non-default partition having rows that do not match its partition
constraints
and we still let attach the partition.
But, I agree that we should emit such a warning, I added a code to do this.

- In the same function, the following condition seems somewhat
strange in comparison to validateCheckConstraint.

if (partqualstate && ExecCheck(partqualstate, econtext))

partqualstate won't be null as long as partition_constraint is
valid. Anyway (I'm believing that) an invalid constraint
results in error by ExecPrepareExpr. Therefore 'if
(partqualstate' is useless.

Removed the check for partqualstate.

- In gram.y, the nonterminal for list spec clause is still
"ForValues". It seems somewhat strange. partition_spec or
something would be better.

Done.

Thanks for catching this, I agree with you.
I have changed the name to PartitionBoundSpec.

- This is not a part of this patch, but in ruleutils.c, the error
for unknown paritioning strategy is emitted as following.

elog(ERROR, "unrecognized partition strategy: %d",
(int) strategy);

The cast is added because the strategy is a char. I suppose
this is because strategy can be an unprintable. I'd like to see
a comment if it is correct.

I think this should be taken separately.

Thanks,
Jeevan Ladhe

Refs:
[1]: /messages/by-id/CAOgcT0OARciE2X%25 2BU0rjSKp9VuC279dYcCGkc3nCWKhHQ1_m2rw%40mail.gmail.com
2BU0rjSKp9VuC279dYcCGkc3nCWKhHQ1_m2rw%40mail.gmail.com

#150Beena Emerson
memissemerson@gmail.com
In reply to: Jeevan Ladhe (#147)
Re: Adding support for Default partition in partitioning

Hello,

On Thu, Jul 13, 2017 at 1:22 AM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

- Should probably be merged with the patch to add default partitioning
for ranges.

Beena is already rebasing her patch on my latest patches, so I think getting
them merged here won't be an issue, mostly will be just like one more patch
on top my patches.

I have posted the updated patch which can be applied over the v22
patches submitted here.
/messages/by-id/CAOG9ApGEZxSQD-ZD3icj_CwTmprSGG7sZ_r3k9m4rmcc6ozr=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

#151Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Jeevan Ladhe (#146)
1 attachment(s)
Re: Adding support for Default partition in partitioning

Hi,

On Thu, Jul 13, 2017 at 1:01 AM, Jeevan Ladhe <jeevan.ladhe@enterprisedb.com

wrote:

Hi,

I have worked further on V21 patch set, rebased it on latest master commit,
addressed the comments given by Robert, Ashutosh and others.

The attached tar has a series of 7 patches.
Here is a brief of these 7 patches:

0001:
Refactoring existing ATExecAttachPartition code so that it can be used for
default partitioning as well

0002:
This patch teaches the partitioning code to handle the NIL returned by
get_qual_for_list().
This is needed because a default partition will not have any constraints
in case
it is the only partition of its parent.

0003:
Support for default partition with the restriction of preventing addition
of any
new partition after default partition.

0004:
Store the default partition OID in pg_partition_table, this will help us to
retrieve the OID of default relation when we don't have the relation cache
available. This was also suggested by Amit Langote here[1].

0005:
Extend default partitioning support to allow addition of new partitions.

0006:
Extend default partitioning validation code to reuse the refactored code in
patch 0001.

0007:
This patch introduces code to check if the scanning of default partition
child
can be skipped if it's constraints are proven.

TODO:
Add documentation.

I have added a documentation patch(patch 0008) to the existing set of
patches.
PFA.

Merge default range partitioning patch.

Beena has created a patch on top of my patches here[1]/messages/by-id/CAOG9ApGEZxSQD- ZD3icj_CwTmprSGG7sZ_r3k9m4rmcc6ozr%3Dg%40mail.gmail.com.

[1]: /messages/by-id/CAOG9ApGEZxSQD- ZD3icj_CwTmprSGG7sZ_r3k9m4rmcc6ozr%3Dg%40mail.gmail.com
ZD3icj_CwTmprSGG7sZ_r3k9m4rmcc6ozr%3Dg%40mail.gmail.com

Regards,
Jeevan Ladhe

Attachments:

default_partition_V23.tarapplication/x-tar; name=default_partition_V23.tarDownload
0001_comments.patch0000664000175000017500000003035513123017136013673 0ustar  jeevanjeevan�^LY�\�r�F����St�j�eQ�g����$�u���$��M�(�e1C�
I��f�]�Y��@�IQ�������eId�Fh��h�Z-���3�M��:�����4u���N���������O�j������=���wx�����c�;��n�u{���?a�'�y��*I���O~���	���w����}-X���f#�����3�����qznw_��mq��G���}���/�=`��K�cm��*�y��J:b��7����f����a��e�'��6�h����I�K6����f]��	����=�e��[�N�z������%��������}���){e��Z�~�r�Fgi�n����/��P�3
8�r?�a�q�97�&)5Xj.n�b��&�E,�)�fl�
]l�4Ax?aw~G����%;�.����;�
���
��f�p7�!sX������<�5���d�J�d��8��
�[�%����7��eUY�GS���C�T�pB/9��/i����cw�a���xU��f~��`v�HxL����R��<pq��W��?�1���S���h��I�=�+x#w�5����3��~���G?U�}���Kf���h���C�B��o��r�rmd`sqR���C3����}0��>h�,��V�������T�*�XeUvtP=d�u���9�����!�ChC�������3v���ys��5�_:gs�M�������`R��M7���g�]��`G����
�W26M|@��9��`}`�j4o2��q��g���	����A/.�����s��A�P� �8�q	xK�~#\���]����7��k��V��������8LX�8*�OA���������`3'H8��B|NG����p�>�p��=��Y
���[KF��V�K[����}��z��7�;^-��'n%U��+���S��d/�cE"�`��DhH5����$ 
���G��; [�D���sh��g�Vd��f���j�F`��;�$��I�
$��JAB��:�$�����%�5�,+zk>������x��M�����>���
���<��2���?��b�O�[�QQR��g�Su�O�8Z.�G�+�l�a�A��
&�����/`����u�G������N]�����{'������1�xA	�������"�z��aO�-2�LH�~v�=����Np��p0���!�I�����1���c��	t��b%~�x�Z(�|�]tyf(�� �pg����p��s+Yr��H��)���!�$+w��9	o��!	������go�����7�������C#�1+�6��+|��&����XJ��
w��HE����#�S�x�l�}(\-&V���
�����IN�����s�-�b�y
�iE�����5a
Z����0��dK>�H�_Ic�1��$	�j�.��wa�Y�Q!Q�1�O��qD��������]AR�!�c��'	�I:����\��Jh���r�h"R�(
�r���n�2v�����NN�nh�e�$��A�A��'���8X[��"9�����E��������)<��_�z$���xB�}�Ji�LU+�A�-4�������	�R�k�Y1��������������v	Z�0g#�0�b�����i��M��~��]�8��Th_������� A�X��:�Si�PX����b��]��2�S^��� )$r��cB�0�$:�1�6���h
Y����P�9fZ��u%S#W�������LE.�m� 3�"�q_����zS��,],J����}Q�hHR�!B�X�b@/�.�'_CGd���U�]���A^C��������_E��J� �#���tZ��~��I�Q�x&4����_~G��)�C�F�����&���;���6�I�g��%3�*2�,���D��K����l'�E����0+p#p��=t�R�9������[g���c���BX��Q���.�_J��x/�������)����_��@��qwMe[�z6}���B/d��x2e���00�k������C��P�w�0D%������)�dZ����A�+tU0��;3��0z
J�&�m���@-��K�)dCT��@�IqPm��1������������]��|{r5>�]^����O���R��3���?���������==�D���5�T����� �^��la��'o�F.I`R�����4m�'�R��4�i9g;�"+��=[�^|{��1�����%��nHWq�9����T���@
����c������\t(�����Xs�,'���a��	)�M*�������=z����w�b�l�O�40����dT�����
C�CF'�(o��-������I�c�C��D	���e&�p����)h��l����P�����x������z�2fv��.����9�� ��2�TB��p��|����kM:q�lNe~4�h!kw����Kjr��K�,��4�(���,Z�"��@{�/, ��9��{M��,�ob'���+F���!����O�~����/��,����������k�A|�D.�Q��<L���;0n��i^M	Wv[���C��7�4�6���������e���e,�%�{J4j�e'�n����	�����Vu��}����Y�U��r�G���^'�0���|�5x�������Z�V�����2���
F��RAlp��[��g�KzPq"�N��1�������@��a-`.J��61��A�aw�8��N�1|�	�+�R;����������W+p@�ap)������N	f����o�f�C�3�zO����� �}p)��Qpl.h����%�������8/�-�4�80
Z�S�e|v%.������{��.^-[%�z��-���|�`d�KK��
��"�	�e>|�o%��g�"�*���l~&���g���nE�K��1h��C@hj/��7-���C[4,������.A�[nXt���9Qw��jTb���x��, �<�V�GU��s�����T�?���(T[D��H�v�"-&FN���h�B��m�"@z3lB�4�,�b���;�	X�8�G���q���6��C��K���M�z:m<�j~ >mD�{���?DM|�b�r�zB-,����-�tV���V�k"�rf���I6kL��Ya#�����m��J��YFz�4P��
�8/d���F�����q���n;7�v�l�9�P[����&X��q��a���X�����l~���.����S��}��aK�z�+C��*���[���J)���7`<��l������3F(�y,�B���I�yn��E������6��}��clYf����b~Q���p[����Y�{�{R����!���E�O�g7��s�������>�����RP^�i��x�������h�x�%��X,g5	7e��z?�;����U���iW������������[�:@��z�G�>�������;���v��i�Y��G�����N���q��ld��n��{�.�#�w�i�m
����w���
��d����g��v��������^��WF���V�
����<��C����������C��Y��c��}{�vT�����7*�{L����o6;v��[�g�}��j�-|�!?={��|H����Y�^co=S����5���Qc�\�#^�!�����9Qj�����7QM�p�g��z��|g��������!��Ot����)(��Y��w�g�x�{��+�g�x,H���G��+���x�U��K��/g����8�g��S�mB�������h�;q��%���HH����:M��f
1W�/S:���LiXrb���l��>�og<f���5���^�;�(���3��t�;�/!���vc@�$�(0h<�++E"�)*��j��'-�$�	p��kSs�E������!p�V���Z����x�Z����j?�T��_*����G���y>�!�?z"�&<���4��
>�e�)��L���O1�w�3XY8��,9	VV����h�O�����n������[p��������3m�S���;:#>�����5�`����h6��n����������mo��wp����$hH{�V��F(Zy+�MQ�A��[��9������B�7��D���y���L{��j&"Q6{P�!��9�*R��Q��ou�G�f�6���7cu�e������[~+@A�@>����iy<�5��Iq@���h8����Vii7�h0���l����G7��h�7�ip[vXNEo�U��C�;�
��a�������a�f
�dm������/��E[
��M��a��K"�C.1�y���t�h��hP�1O�����o�;��.���w������G,.�!u3��S�M�A�T-��H`n��M��I���EwW�@�n��t�%fc�.����
'�T|/��
��:4jX�$���zQD���2]��E� ����N��	�I�'���E��~_�4;�;nJp����Z���e�G��%�h���m�]�5W|�DD7��������]�..1�gX�-�~�@k?'�U���1=����dZ��A�wy�s�0�'���5���xQ
��Fu	���
�����Y��/��Kn}69�m�H�#�c�A3�����
>-���%l������`�m�I���mk5U��N���W�����wo�5�J�N~���{AS
7<5���;5������|EM�n; phLV�Tce���\�L�;�I��%0�
AIV�3PDA<�������G�s'�h� �h��F��h���~���<%�gJ����U�~��A�+^o�?����h�S���L��sK�*	��)��_s:VQ��K��]�������T��(������;6qf�
?�����L���o����81s�jT�p/@e��C4ETU�qYXa���p_�ly���H���:���$g	�*�<#y����F�,Z"@���_���8W�A�a��������F
XO��we��v�q���^ <@�/f/�%�����h��E�@�`�����25U���%�
���	�K��,:�t���;��X����H_!���*��AP<�������]I���E-L�&�L���E����#(�)�H�Rb�I&��$s�:h#qc�FqS�E0r���	�%Q�
�PP��I��Q�D.�^�7G2w�����BfK#>�^��t�Q���gK&��)��
�z8��	����x�f��W^5����1�<�8�(�al�������FtLz>�����p�r\|
���c�5�>��(:x�K�%�S*t�n������N����^��-Z�T�����;�04�J�TTR��uH���8g#$��:�?��OVI�M��*&L?T���$�����M�G�b:�L��EAH�GC���"������-gOLh��q31���X�����IM+L�8"@)~W���Y��a�R<!�L��MD�P����2a�p=��`;^���V����I�V<@i��@"0A@�D��L_���<}��m3��Af��x���H����-�1nW3s�fU.���K��8��U�����.�b����C��C�����6�UfE��n�3���e��B��W���{b���P_��Z��@�Xz�����2�=��jC��b��k\�S~�F��e�����O������z,i*�������{!�m������4�4�_����:�����^����X�]?��V���pXOlr�j�����i�8�=����_�����0'�������������c��O<�f�!��D/(�N��799�	+n!^�!�������<`Ps�2
��:h&����>S�9��L�+V+_V���@.��%��x4���.����W7����]�K�Z�r*_1�i�A�]��5y��~�R���l�t�k�P��v��n=����p�K�?f:�O{���6�\�J�b���(�����o"K�Z�,�$�6�� A�Y�d��j�������'0AI�y����y`f����_#��.����C�����	�S���-�����%f�}[p�"J���YKFFK$�u~
�
1fs��0D��
?[��<�&F�'Ip��	�
-6rCW�S��k��|j���f������Zk��O�D����Zl��*��w{�;�-a@�����F�l~G��H�ebw�@����3��G�������	��^P������HIw��_��^�c?-+���b����P�4�������]�eb�qb�S4�8�g�_�l�AJ��B��"
�/Q�I�\Q�"��3+�0����)v�:��R���=�J�\l�
�,)���+E<)�i������~�)\Z<pd�<�M��x5�?��]'J��d����!9h�����2Y�|�Hz�}�(1y���t�e����D:�m~R�~t|�6�et��
�<Z.e��fd�z�g#�CV�QPG���,��TU�a���Hgta�hyR�J$+�����q�N�t���#Rp���zE]J��-�<�s���]KVd����a
:WC�5����#�U�4���\MJ <d�D�8nD.y��L���S��y�2�$�X���_�O�H�-&J�������|�;�������b��K2a�Q&��-�(�5�*���"���;����!��������D+1�aM��nr3�|9����*����(r�LV2�x6���2T�62�5�9"h��cRK��C�< �XWoL�����Nmf]����s���*{��l���A�8ZC^�����Lx������L%0�4)�&<�LD�Tg���y�,�C�5J(�&$%c�6f�����F�w�h:�Spc!�L'���eF{4�3���)�#K�l��
�V�T�����&�c��,��i�&WJI���
}�
n����i����$�nG���rx��������g�)g�)�XD]>�k����*}p��&���_�Z�)x���c��Kj�70p�
���}�(�
��9���f���cM �Y�h&��y�#w�:��/9�.��.L$��\0TA�'3�!,���8�%w�M%S��������DUH�����u���g5�W����~���^��l�u�y�6�^�-Q#���	��b��]Bp���C��L��
)��I/��%f':����.�{�W���h$�d���v���s��D��'K8�������M��f.S�4X�5Bm��.��	�8�E�R}(M��Q��� ������M�������X�%�����c����C�L?�YP%4���0��H�m���
�^��.:������q�>E����\����fs�b���e� z8����p�V�W���tL!o[��VG�������
��^K���"��C���;��4����i{����� ��z��@����1=�x�DK.J�����^�}!��t��R�u����.:��<��e�����}v�P<#�"8�Ab=��
����DE?�"�L&������TR�t�|:
	��;
	������E&C�����W?�G*q�hn�������)z���8���/F�i���/�x�%ZB�J"����
���:8����������f��x��w.1��(]�A���9L�����8L����V�v.����	��T���I���,|Ad� ���:�<?�a~�T��K!Tw�h�t�u����D�L��3�#2.*�$���Ml��}i3T����&��B��17������`��p��D��V��M�
�U��������Qp|�����������^��o�OQ5��C-59��s�+�o�.����;(�U��S�w��fpnA,#��>��S,��
�\%�KUdjSU^|�����a�z����y��7u~�����
{�Se�.�<7��������dgr����s�W��O9i�����'���Dd�4rS\^R�3����������"
�+q^����&U�Q�]�[qn��������sm��w�Sl<\��������
R��������3������)��"�u��~	;]�;(�;Jl�KY���o�g<�n>g<��x
�L���T��|���<�f�S�����4	c~�y�������w�gy���Z?�e0���"��.�$��3�49��B��nB�������`Fbs>�s
U����n������X�2F��[
�%���d�4���#��r������\���h����t���V7�?d{�
�u����D|�T0�*Y��J���6�}��(�t	>lf��V�zx���������|���rT����bCW�J�?nD���Y>���`���@�
�u-�YAz~��v3�4a'{��$b�.�lUB�G7����mz������S6�wTg��1�a5�88���Q#R�S�l�`3U6��
�Zq],O�Q��7���kw�up5:���Q`�����o�����t@�f���|6����b�.?}�W+��������������T����T�����
Z�CN�f�S`�T��f��k��r����&]�8
��:��=����CZ���&�Bd�/���
N������X)PX�',�S�8�tyI -��q~��9E�V�j4�^/�i���Y����v���F���/��}�R�e�;��C������l���3�Fh^�%
5|x���Y�/7��PJ�A���� >��W������\��iKO��-(���J�=�SQ�w��1~�=�xr�>b���C�Z.�[�Z���3Af��n%G/��$pf��9��x�v�-R�4�"UI��$i��(l4Z������nr7`�"�=$�!��]�g���b�	�\q��]�*~�&P^��km��=\Tu�_�ng*�"� ���W����s}���*�T�2�[Nn�����h6Y�������*����*�Z�QN��c@�d�rNW�+�w!f�����Q�|	f+P������&8��A��kp6��)�*��Vht�.8��������]����e�~6�Z�V�Pi����d.'~HEE�;Si�*���IY�������d���	&O��F��\���0�(j�lW�L:��41�����b������`�'��s��;�@jqg�������zI�O��N�n�z����K����a<0��B�]���#���A���Ho�����G]��_������b:�#xP��w���	p�������*Y�V��,�#��s_��5�P*&����>��������*B����"�@E���Q��Z!n�*vR1���'�eRfu;>Fzt;��C=6F�M��jV4��l��W��o���jK�d�:�7Y��*������2R��DF���
%,����9����Ru@�?�$����	�7���osj�WR���<Xx���zc:����9cH w��'G?�P���������w���w����~����L��=t0S:��)�zGZr!d?."�o�C>�Nt��.���=]�V+t)t�K!1������%{��g�����+Zl ��`��w�{@�����'���'�P������?hy=�����5h��r���R"���>�ut������~j�z�T:����f�BQn�x]
A������F#����:TE}hQTC���&��@����*Y��6����/�nr=P?gQH�s��D7�&��o�������p���<�\����g�3&cA�9
h���i�)���� �Q������ng�x`AY:*��Q�M������_j������T&�-3QZ��1+08���BF��w-��/���2�#��w�rDa� �UP@a��l0v��*�O�"��;@2��&\����y1�44X�����+1 ��f�h�Q�kv�P�7�����9 �0�)��E��x	z��
�DS��Of�� 7t!��^`?������R���P�{/_�A�Zt��q'�����Ue���'��O�gH���O//��\��E[�D��������5Yg�9!`��d���UQ
*���qT]N��Y�FW��,�m����MMW)h3$_x����n�Z^����s����`N�R
q�u��m���s=����T��s����Nx���Ulm�*�d��V�/6��(��\�����,r�AD�L�S�n���j~
�w8J�'l����D9P��q4��G}>�����d!AM��4�Z�s���q"��yy."�^��/�P.���qV}���~��Oc.N)(k�.@���	3[�D<O���Z�C����L����\zQ�jeu��(}W
���J,s�&K���c/�A���w�jn������=1��s2]������4�A����l7�f����+K�9�v�-�x[]���eo��u�7��d����[9l2�%�������ND��H���:u����\�:�3�%����������=7��#�(�z�x�q�/�����^�e�)�LO�%���������S��G��I:�|J>�-�$rX�����#�B�=�{���*�@Mo�g#�)'Ei��"�+a�(�I)�|�?����	�� @v��7�{t�E�z�0��=���C3��1 U8��Y�������>�K��sG���v��&#������(��vJ<�]��B������(�R�!�'��M�#y��0�-N�4��b��o�xp�7��*��,����RXO��'�^�"��+��o�@�sY�Z�s�3A��_�G�%i��E�KW<s���p�l4F!(.������FUTw�=�����
(��Odo`n-�u���	tk&�`l���.�$q�R=�E�{I�����xo�TL��mR�>��_1�n|s���3��Ec6����O�`�]�7�)Wt��@Rw��>��2���H��E_��d@��:?�
N���+���r�5��Ox_o�(��B`%�+�������#|����m�B�.�����\F��'������4�%�fa��e
�t�B��c���7pk�w9X����.������[��k����^��8!>g����
�>��oi�
�Z���_�}�d>��������f8�{x,Z_]�gj9Y5�c�� @R7Vv��--��^���"��1�����X��8�1�5��P�����X}�
��#2{�s��>xC(�pR�~=9P�z�����n��LT# b���F�#��a��I�4���o_PH�t��:�%���V����<<3j5"'�[�3�`��*��uHr����1L���51�?�WYY\�������%�/^���Wb8�F���!��$�P���H�O�	@��\60�	�e�*3 ��7����,��,�.�?��'����]x<Lr�
 #]9k��m,�|k�C������=��J���
bE��US�f��Q��F���q�)+�}������@�G�+@%�����a��p�c����Wt��#�c_~���g0qM?~��hzC������rn�A%=]K���(�|��O�����V�3VJ���bQ���v�T��b����}��;�zx	z!��]@V���&��h��4���T�Ir'��$����2���I�KO!�������l��$�����f�2:��m�@�6�����
lPt���,M�����&C�v{�5�^����]mG���Q��z�]�u���|��	���}q��Q��f��U��R���E*?���� }������cm�L�5%dE��g��r��x����'��mn����ha�J�Q2��V����x�2S"�o�W��<������#�53RT[N�hv��e��6Q�`��j�}����&vuk4��^�ha�=t���(gW���+j����{_����P�<�� �	�~ (�8�{�&|Z�>)z���F����o��[2��8m|9w�u��B����?��������'x�0�D�i�[gE�LN��
�Q�.%1!����
i,���=������,I��(����n!l��o����9&4�����y�c��n\f&t�$�.��7���U����f��Ta!/ ��� ��pF�n�z�^�,CD�>
��QK\��l5x�����������}c6�M�[[��`��=���i��d���b�5Y~-�e��ncS]	��YL��$�X<�n�o4�^7heSv7�������#�<$���g�����@V�.�x]\$���A|{�"��������eLD�����JoRa* ���z�;��k4��q���%h��G�0j�}�-0L��S�����C�JlkW*iWz�e�af��c
=�����5����>�g�l���}����>�g�l���}����>�g�l���}����>����G�+�0001-Refactor-ATExecAttachPartition.patch0000664000175000017500000002532613134125753017670 0ustar  jeevanjeevanFrom c81e021a7e0bf2a1fbf4df2628f08647acbae205 Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Thu, 20 Jul 2017 18:36:01 +0530
Subject: [PATCH 1/8] Refactor ATExecAttachPartition

Move code to validate the table being attached against the partition
constraints into set of functions. This will be used for validating
the changed default partition constraints when a new partition is
added.

Ashutosh Bapat, revised by me.
---
 src/backend/commands/tablecmds.c | 311 +++++++++++++++++++++------------------
 1 file changed, 165 insertions(+), 146 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index bb00858..7964770 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13412,6 +13412,168 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
 }
 
 /*
+ * canSkipPartConstraintValidation
+ *
+ * Check if we can do away with having to scan the given table to validate
+ * given constraint by proving that the existing constraints of the table imply
+ * the given constraints.  We include the table's check constraints and NOT
+ * NULL constraints in the list of clauses passed to predicate_implied_by().
+ *
+ * The function returns true if it's safe to skip the scan, false otherwise.
+ */
+static bool
+canSkipPartConstraintValidation(Relation scanRel, List *partConstraint,
+								PartitionKey key)
+{
+	TupleDesc	tupleDesc = RelationGetDescr(scanRel);
+	TupleConstr *scanRel_constr = tupleDesc->constr;
+	int			i;
+	List	   *existConstraint = NIL;
+
+	if (scanRel_constr == NULL)
+		return false;
+
+	if (scanRel_constr->has_not_null)
+	{
+		int			natts = scanRel->rd_att->natts;
+
+		for (i = 1; i <= natts; i++)
+		{
+			Form_pg_attribute att = scanRel->rd_att->attrs[i - 1];
+
+			if (att->attnotnull && !att->attisdropped)
+			{
+				NullTest   *ntest = makeNode(NullTest);
+
+				ntest->arg = (Expr *) makeVar(1,
+											  i,
+											  att->atttypid,
+											  att->atttypmod,
+											  att->attcollation,
+											  0);
+				ntest->nulltesttype = IS_NOT_NULL;
+
+				/*
+				 * argisrow=false is correct even for a composite column,
+				 * because attnotnull does not represent a SQL-spec IS NOT
+				 * NULL test in such a case, just IS DISTINCT FROM NULL.
+				 */
+				ntest->argisrow = false;
+				ntest->location = -1;
+				existConstraint = lappend(existConstraint, ntest);
+			}
+		}
+	}
+
+	for (i = 0; i < scanRel_constr->num_check; i++)
+	{
+		Node	   *cexpr;
+
+		/*
+		 * If this constraint hasn't been fully validated yet, we must ignore
+		 * it here.
+		 */
+		if (!scanRel_constr->check[i].ccvalid)
+			continue;
+
+		cexpr = stringToNode(scanRel_constr->check[i].ccbin);
+
+		/*
+		 * Run each expression through const-simplification and
+		 * canonicalization.  It is necessary, because we will be comparing it
+		 * to similarly-processed qual clauses, and may fail to detect valid
+		 * matches without this.
+		 */
+		cexpr = eval_const_expressions(NULL, cexpr);
+		cexpr = (Node *) canonicalize_qual((Expr *) cexpr);
+
+		existConstraint = list_concat(existConstraint,
+									  make_ands_implicit((Expr *) cexpr));
+	}
+
+	existConstraint = list_make1(make_ands_explicit(existConstraint));
+
+	/* And away we go ... */
+	return predicate_implied_by(partConstraint, existConstraint, true);
+}
+
+/*
+ * validatePartitionConstraints
+ *
+ * Check whether all rows in the given table obey the given partition
+ * constraint; if so, it can be attached as a partition.�� We do this by
+ * scanning the table (or all of its leaf partitions) row by row, except when
+ * the existing constraints are sufficient to prove that the new partitioning
+ * constraint must already hold.
+ */
+static void
+validatePartitionConstraints(List **wqueue, Relation scanRel,
+							 List *partConstraint, Relation rel)
+{
+	PartitionKey key = RelationGetPartitionKey(rel);
+	List	   *all_parts;
+	ListCell   *lc;
+
+	/* If possible, skip the validation scan. */
+	if (canSkipPartConstraintValidation(scanRel, partConstraint, key))
+	{
+		ereport(INFO,
+				(errmsg("partition constraint for table \"%s\" is implied by existing constraints",
+						RelationGetRelationName(scanRel))));
+		return;
+	}
+
+	/*
+	 * Prepare to scan the default partition (or, if it is itself partitioned,
+	 * all of its leaf partitions).
+	 *
+	 * Take an exclusive lock on the partitions to be checked.
+	 */
+	if (scanRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		all_parts = find_all_inheritors(RelationGetRelid(scanRel),
+										AccessExclusiveLock, NULL);
+	else
+		all_parts = list_make1_oid(RelationGetRelid(scanRel));
+
+	foreach(lc, all_parts)
+	{
+		AlteredTableInfo *tab;
+		Oid			part_relid = lfirst_oid(lc);
+		Relation	part_rel;
+		Expr	   *constr;
+
+		/* Lock already taken */
+		if (part_relid != RelationGetRelid(scanRel))
+			part_rel = heap_open(part_relid, NoLock);
+		else
+			part_rel = scanRel;
+
+		/*
+		 * Skip if it's a partitioned table.  Only RELKIND_RELATION relations
+		 * (ie, leaf partitions) need to be scanned.
+		 */
+		if (part_rel != scanRel &&
+			part_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			heap_close(part_rel, NoLock);
+			continue;
+		}
+
+		/* Grab a work queue entry */
+		tab = ATGetQueueEntry(wqueue, part_rel);
+
+		/* Adjust constraint to match this partition */
+		constr = linitial(partConstraint);
+		tab->partition_constraint = (Expr *)
+			map_partition_varattnos((List *) constr, 1,
+									part_rel, rel);
+		/* keep our lock until commit */
+		if (part_rel != scanRel)
+			heap_close(part_rel, NoLock);
+	}
+}
+
+/*
  * ALTER TABLE <name> ATTACH PARTITION <partition-name> FOR VALUES
  *
  * Return the address of the newly attached partition.
@@ -13422,15 +13584,12 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	Relation	attachRel,
 				catalog;
 	List	   *childrels;
-	TupleConstr *attachRel_constr;
-	List	   *partConstraint,
-			   *existConstraint;
+	List	   *partConstraint;
 	SysScanDesc scan;
 	ScanKeyData skey;
 	AttrNumber	attno;
 	int			natts;
 	TupleDesc	tupleDesc;
-	bool		skip_validate = false;
 	ObjectAddress address;
 	const char *trigger_name;
 
@@ -13602,148 +13761,8 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
 	partConstraint = list_make1(make_ands_explicit(partConstraint));
 
-	/*
-	 * Check if we can do away with having to scan the table being attached to
-	 * validate the partition constraint, by *proving* that the existing
-	 * constraints of the table *imply* the partition predicate.  We include
-	 * the table's check constraints and NOT NULL constraints in the list of
-	 * clauses passed to predicate_implied_by().
-	 *
-	 * There is a case in which we cannot rely on just the result of the
-	 * proof.
-	 */
-	attachRel_constr = tupleDesc->constr;
-	existConstraint = NIL;
-	if (attachRel_constr != NULL)
-	{
-		int			num_check = attachRel_constr->num_check;
-		int			i;
-
-		if (attachRel_constr->has_not_null)
-		{
-			int			natts = attachRel->rd_att->natts;
-
-			for (i = 1; i <= natts; i++)
-			{
-				Form_pg_attribute att = attachRel->rd_att->attrs[i - 1];
-
-				if (att->attnotnull && !att->attisdropped)
-				{
-					NullTest   *ntest = makeNode(NullTest);
-
-					ntest->arg = (Expr *) makeVar(1,
-												  i,
-												  att->atttypid,
-												  att->atttypmod,
-												  att->attcollation,
-												  0);
-					ntest->nulltesttype = IS_NOT_NULL;
-
-					/*
-					 * argisrow=false is correct even for a composite column,
-					 * because attnotnull does not represent a SQL-spec IS NOT
-					 * NULL test in such a case, just IS DISTINCT FROM NULL.
-					 */
-					ntest->argisrow = false;
-					ntest->location = -1;
-					existConstraint = lappend(existConstraint, ntest);
-				}
-			}
-		}
-
-		for (i = 0; i < num_check; i++)
-		{
-			Node	   *cexpr;
-
-			/*
-			 * If this constraint hasn't been fully validated yet, we must
-			 * ignore it here.
-			 */
-			if (!attachRel_constr->check[i].ccvalid)
-				continue;
-
-			cexpr = stringToNode(attachRel_constr->check[i].ccbin);
-
-			/*
-			 * Run each expression through const-simplification and
-			 * canonicalization.  It is necessary, because we will be
-			 * comparing it to similarly-processed qual clauses, and may fail
-			 * to detect valid matches without this.
-			 */
-			cexpr = eval_const_expressions(NULL, cexpr);
-			cexpr = (Node *) canonicalize_qual((Expr *) cexpr);
-
-			existConstraint = list_concat(existConstraint,
-										  make_ands_implicit((Expr *) cexpr));
-		}
-
-		existConstraint = list_make1(make_ands_explicit(existConstraint));
-
-		/* And away we go ... */
-		if (predicate_implied_by(partConstraint, existConstraint, true))
-			skip_validate = true;
-	}
-
-	/* It's safe to skip the validation scan after all */
-	if (skip_validate)
-		ereport(INFO,
-				(errmsg("partition constraint for table \"%s\" is implied by existing constraints",
-						RelationGetRelationName(attachRel))));
-
-	/*
-	 * Set up to have the table be scanned to validate the partition
-	 * constraint (see partConstraint above).  If it's a partitioned table, we
-	 * instead schedule its leaf partitions to be scanned.
-	 */
-	if (!skip_validate)
-	{
-		List	   *all_parts;
-		ListCell   *lc;
-
-		/* Take an exclusive lock on the partitions to be checked */
-		if (attachRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
-			all_parts = find_all_inheritors(RelationGetRelid(attachRel),
-											AccessExclusiveLock, NULL);
-		else
-			all_parts = list_make1_oid(RelationGetRelid(attachRel));
-
-		foreach(lc, all_parts)
-		{
-			AlteredTableInfo *tab;
-			Oid			part_relid = lfirst_oid(lc);
-			Relation	part_rel;
-			Expr	   *constr;
-
-			/* Lock already taken */
-			if (part_relid != RelationGetRelid(attachRel))
-				part_rel = heap_open(part_relid, NoLock);
-			else
-				part_rel = attachRel;
-
-			/*
-			 * Skip if it's a partitioned table.  Only RELKIND_RELATION
-			 * relations (ie, leaf partitions) need to be scanned.
-			 */
-			if (part_rel != attachRel &&
-				part_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
-			{
-				heap_close(part_rel, NoLock);
-				continue;
-			}
-
-			/* Grab a work queue entry */
-			tab = ATGetQueueEntry(wqueue, part_rel);
-
-			/* Adjust constraint to match this partition */
-			constr = linitial(partConstraint);
-			tab->partition_constraint = (Expr *)
-				map_partition_varattnos((List *) constr, 1,
-										part_rel, rel);
-			/* keep our lock until commit */
-			if (part_rel != attachRel)
-				heap_close(part_rel, NoLock);
-		}
-	}
+	/* Validate partition constraints against the table being attached. */
+	validatePartitionConstraints(wqueue, attachRel, partConstraint, rel);
 
 	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
 
-- 
1.9.1

0002-ATExecAttachPartition-refactor-patch-comments.patch0000664000175000017500000000646313123017100022632 0ustar  jeevanjeevanFrom 82bb2dc3ad2f915a45c92534ee91e4ab2c088b88 Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Fri, 23 Jun 2017 00:51:44 +0530
Subject: [PATCH 2/3] ATExecAttachPartition refactor patch comments

Address Robert's comments[1] on refactoring of ATExecAttachPartition
patch.

Jeevan Ladhe.
---
 src/backend/commands/tablecmds.c | 27 ++++++++++++---------------
 1 file changed, 12 insertions(+), 15 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 3893b99..3169c60 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13400,10 +13400,10 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
 /*
  * canSkipPartConstraintValidation
  *
- * Check if we can do away with having to scan the given table to validate given
- * constraint, by *proving* that the existing constraints of the table
- * *imply* the given constraints.  We include the table's check constraints and
- * NOT NULL constraints in the list of clauses passed to predicate_implied_by().
+ * Check if we can do away with having to scan the given table to validate
+ * given constraint by proving that the existing constraints of the table imply
+ * the given constraints.  We include the table's check constraints and NOT
+ * NULL constraints in the list of clauses passed to predicate_implied_by().
  *
  * The function returns true if it's safe to skip the scan, false otherwise.
  */
@@ -13486,13 +13486,11 @@ canSkipPartConstraintValidation(Relation scanRel, List *partConstraint,
 /*
  * validatePartitionConstraints
  *
- * Check whether all rows in the given table (scanRel) obey given partition
- * constraints.  The function is used to check if the given table is fit to be
- * attached as a partition of a partitioned table (rel). If the existing
- * constraints on the table imply the given partition constraints, any row in
- * the table will comply with the given partition constraints. Otherwise, this
- * function creates work queue entries to scan the table and its partitions (if
- * any) for validating the partition constraints.
+ * Check whether all rows in the given table obey the given partition
+ * constraint; if so, it can be attached as a partition.�� We do this by
+ * scanning the table (or all of its leaf partitions) row by row, except when
+ * the existing constraints are sufficient to prove that the new partitioning
+ * constraint must already hold.
  */
 static void
 validatePartitionConstraints(List **wqueue, Relation scanRel,
@@ -13502,7 +13500,7 @@ validatePartitionConstraints(List **wqueue, Relation scanRel,
 	List	   *all_parts;
 	ListCell   *lc;
 
-	/* Check if we can do away with having to scan the table being attached. */
+	/* If possible, skip the validation scan. */
 	if (canSkipPartConstraintValidation(scanRel, partConstraint, key))
 	{
 		ereport(INFO,
@@ -13512,9 +13510,8 @@ validatePartitionConstraints(List **wqueue, Relation scanRel,
 	}
 
 	/*
-	 * Set up to have the table be scanned to validate the partition
-	 * constraint If it's a partitioned table, we instead schedule its leaf
-	 * partitions to be scanned.
+	 * Prepare to scan the default partition (or, if it is itself partitioned,
+	 * all of its leaf partitions).
 	 *
 	 * Take an exclusive lock on the partitions to be checked.
 	 */
-- 
1.9.1

0002-Fix-assumptions-that-get_qual_from_partbound-cannot-.patch0000664000175000017500000000511513134125753024274 0ustar  jeevanjeevanFrom a97d04f2914600587129db619fd8817f2461706f Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Thu, 20 Jul 2017 18:36:16 +0530
Subject: [PATCH 2/8] Fix assumptions that get_qual_from_partbound() cannot
 return NIL list.

Current partitioning code assumes that there cannot be any partition
without partition constraints, but in future this assumption might
not hold true.
e.g. if we introduce support for default partition, then default
partition will not have any constraints in case it is the only
partition of its parent.

Jeevan Ladhe.
---
 src/backend/catalog/partition.c  |  5 ++++-
 src/backend/commands/tablecmds.c | 17 +++++++++++------
 2 files changed, 15 insertions(+), 7 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 43b8924..96e602d 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -974,7 +974,10 @@ get_partition_qual_relid(Oid relid)
 	if (rel->rd_rel->relispartition)
 	{
 		and_args = generate_partition_qual(rel);
-		if (list_length(and_args) > 1)
+
+		if (!and_args)
+			result = NULL;
+		else if (list_length(and_args) > 1)
 			result = makeBoolExpr(AND_EXPR, and_args, -1);
 		else
 			result = linitial(and_args);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 7964770..f24602d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13756,13 +13756,18 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	partConstraint = list_concat(get_qual_from_partbound(attachRel, rel,
 														 cmd->bound),
 								 RelationGetPartitionQual(rel));
-	partConstraint = (List *) eval_const_expressions(NULL,
-													 (Node *) partConstraint);
-	partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
-	partConstraint = list_make1(make_ands_explicit(partConstraint));
 
-	/* Validate partition constraints against the table being attached. */
-	validatePartitionConstraints(wqueue, attachRel, partConstraint, rel);
+	/* Skip validation if there are no constraints to validate. */
+	if (partConstraint)
+	{
+		partConstraint =
+			(List *) eval_const_expressions(NULL, (Node *) partConstraint);
+		partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
+		partConstraint = list_make1(make_ands_explicit(partConstraint));
+
+		/* Validate partition constraints against the table being attached. */
+		validatePartitionConstraints(wqueue, attachRel, partConstraint, rel);
+	}
 
 	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
 
-- 
1.9.1

0003-Implement-default-partition-support.patch0000664000175000017500000012216113134125753021067 0ustar  jeevanjeevanFrom 1aa266dd741f1ef6b50a0d699d66e0f354836727 Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Thu, 20 Jul 2017 18:36:22 +0530
Subject: [PATCH 3/8] Implement default partition support

This patch introduces default partition for a list partitioned
table. However, in this version of patch no other partition can
be attached to a list partitioned table if it has a default
partition.

Jeevan Ladhe.
---
 src/backend/catalog/heap.c                 |  33 +++++-
 src/backend/catalog/partition.c            | 159 ++++++++++++++++++++++++++---
 src/backend/commands/tablecmds.c           |  39 ++++++-
 src/backend/nodes/copyfuncs.c              |   1 +
 src/backend/nodes/equalfuncs.c             |   1 +
 src/backend/nodes/outfuncs.c               |   1 +
 src/backend/nodes/readfuncs.c              |   1 +
 src/backend/parser/gram.y                  |  27 +++--
 src/backend/parser/parse_utilcmd.c         |  19 ++++
 src/backend/utils/adt/ruleutils.c          |  12 ++-
 src/bin/psql/tab-complete.c                |   4 +-
 src/include/catalog/partition.h            |   2 +
 src/include/nodes/parsenodes.h             |   1 +
 src/test/regress/expected/alter_table.out  |  14 +++
 src/test/regress/expected/create_table.out |  10 ++
 src/test/regress/expected/insert.out       |  56 ++++++++++
 src/test/regress/expected/plancache.out    |  22 ++++
 src/test/regress/expected/update.out       |  15 +++
 src/test/regress/sql/alter_table.sql       |  13 +++
 src/test/regress/sql/create_table.sql      |   8 ++
 src/test/regress/sql/insert.sql            |  32 ++++++
 src/test/regress/sql/plancache.sql         |  19 ++++
 src/test/regress/sql/update.sql            |  15 +++
 23 files changed, 474 insertions(+), 30 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index a376b99..068c3bd 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1755,9 +1755,11 @@ RemoveAttrDefaultById(Oid attrdefId)
 void
 heap_drop_with_catalog(Oid relid)
 {
-	Relation	rel;
+	Relation	rel,
+				parentRel;
 	HeapTuple	tuple;
-	Oid			parentOid = InvalidOid;
+	Oid			parentOid = InvalidOid,
+				defaultPartOid = InvalidOid;
 
 	/*
 	 * To drop a partition safely, we must grab exclusive lock on its parent,
@@ -1772,7 +1774,22 @@ heap_drop_with_catalog(Oid relid)
 	if (((Form_pg_class) GETSTRUCT(tuple))->relispartition)
 	{
 		parentOid = get_partition_parent(relid);
-		LockRelationOid(parentOid, AccessExclusiveLock);
+		parentRel = heap_open(parentOid, AccessExclusiveLock);
+
+		/*
+		 * The partition constraint of the default partition depends on the
+		 * partition bounds of every other partition. It is possible that
+		 * other backend might be about to execute a query on the default
+		 * partition table and the query relies on previously cached default
+		 * partition constraints, which won't be correct after removal of a
+		 * partition. We must therefore take a table lock strong enough to
+		 * prevent all queries on the default partition from proceeding until
+		 * we commit and send out a shared-cache-inval notice that will make
+		 * them update their index lists.
+		 */
+		defaultPartOid = get_default_partition_oid(parentRel);
+		if (OidIsValid(defaultPartOid))
+			LockRelationOid(defaultPartOid, AccessExclusiveLock);
 	}
 
 	ReleaseSysCache(tuple);
@@ -1883,11 +1900,21 @@ heap_drop_with_catalog(Oid relid)
 	if (OidIsValid(parentOid))
 	{
 		/*
+		 * The partition constraint for the default partition depends on the
+		 * partition bounds of every other partition, so we must invalidate
+		 * the relcache entry for that partition every time a partition is
+		 * added or removed.
+		 */
+		if (OidIsValid(defaultPartOid))
+			CacheInvalidateRelcacheByRelid(defaultPartOid);
+
+		/*
 		 * Invalidate the parent's relcache so that the partition is no longer
 		 * included in its partition descriptor.
 		 */
 		CacheInvalidateRelcacheByRelid(parentOid);
 		/* keep the lock */
+		heap_close(parentRel, NoLock);
 	}
 }
 
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 96e602d..a69015a 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -89,9 +89,12 @@ typedef struct PartitionBoundInfoData
 								 * partitioned table) */
 	int			null_index;		/* Index of the null-accepting partition; -1
 								 * if there isn't one */
+	int			default_index;	/* Index of the default partition; -1 if there
+								 * isn't one */
 } 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
@@ -129,7 +132,7 @@ static void get_range_key_properties(PartitionKey key, int keynum,
 						 ListCell **partexprs_item,
 						 Expr **keyCol,
 						 Const **lower_val, Const **upper_val);
-static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_list(Relation parent, PartitionBoundSpec *spec);
 static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
 static List *generate_partition_qual(Relation rel);
 
@@ -175,6 +178,7 @@ RelationBuildPartitionDesc(Relation rel)
 	/* List partitioning specific */
 	PartitionListValue **all_values = NULL;
 	int			null_index = -1;
+	int			default_index = -1;
 
 	/* Range partitioning specific */
 	PartitionRangeBound **rbounds = NULL;
@@ -255,6 +259,18 @@ RelationBuildPartitionDesc(Relation rel)
 				if (spec->strategy != PARTITION_STRATEGY_LIST)
 					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 list of non-null
+				 * datums for this partition.
+				 */
+				if (spec->is_default)
+				{
+					default_index = i;
+					i++;
+					continue;
+				}
+
 				foreach(c, spec->listdatums)
 				{
 					Const	   *val = castNode(Const, lfirst(c));
@@ -454,6 +470,7 @@ RelationBuildPartitionDesc(Relation rel)
 		boundinfo = (PartitionBoundInfoData *)
 			palloc0(sizeof(PartitionBoundInfoData));
 		boundinfo->strategy = key->strategy;
+		boundinfo->default_index = -1;
 		boundinfo->ndatums = ndatums;
 		boundinfo->null_index = -1;
 		boundinfo->datums = (Datum **) palloc0(ndatums * sizeof(Datum *));
@@ -506,6 +523,20 @@ RelationBuildPartitionDesc(Relation rel)
 						boundinfo->null_index = mapping[null_index];
 					}
 
+					/* Assign mapped index for the default partition. */
+					if (default_index != -1)
+					{
+						/*
+						 * The default partition accepts any non-specified
+						 * value, hence it should not get a mapped index while
+						 * assigning those for non-null datums.
+						 */
+						Assert(default_index >= 0 &&
+							   mapping[default_index] == -1);
+						mapping[default_index] = next_index++;
+						boundinfo->default_index = mapping[default_index];
+					}
+
 					/* All partition must now have a valid mapping */
 					Assert(next_index == nparts);
 					break;
@@ -610,6 +641,9 @@ partition_bounds_equal(PartitionKey key,
 	if (b1->null_index != b2->null_index)
 		return false;
 
+	if (b1->default_index != b2->default_index)
+		return false;
+
 	for (i = 0; i < b1->ndatums; i++)
 	{
 		int			j;
@@ -890,7 +924,7 @@ get_qual_from_partbound(Relation rel, Relation parent,
 	{
 		case PARTITION_STRATEGY_LIST:
 			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
-			my_qual = get_qual_for_list(key, spec);
+			my_qual = get_qual_for_list(parent, spec);
 			break;
 
 		case PARTITION_STRATEGY_RANGE:
@@ -1288,10 +1322,14 @@ make_partition_op_expr(PartitionKey key, int keynum,
  *
  * Returns an implicit-AND list of expressions to use as a list partition's
  * constraint, given the partition key and bound structures.
+ *
+ * The function returns NIL for a default partition when it's the only
+ * partition since it can not have any partition constraint.
  */
 static List *
-get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
+get_qual_for_list(Relation parent, PartitionBoundSpec *spec)
 {
+	PartitionKey key = RelationGetPartitionKey(parent);
 	List	   *result;
 	Expr	   *keyCol;
 	ArrayExpr  *arr;
@@ -1318,15 +1356,63 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 	else
 		keyCol = (Expr *) copyObject(linitial(key->partexprs));
 
-	/* Create list of Consts for the allowed values, excluding any nulls */
-	foreach(cell, spec->listdatums)
+	/*
+	 * For default list partition, collect datums for all the partitions. The
+	 * default partition constraint should check that the partition key is
+	 * equal to none of those.
+	 */
+	if (spec->is_default)
 	{
-		Const	   *val = castNode(Const, lfirst(cell));
+		int			i;
+		int			ndatums = 0;
+		PartitionDesc pdesc = RelationGetPartitionDesc(parent);
+		PartitionBoundInfo boundinfo = pdesc->boundinfo;
 
-		if (val->constisnull)
-			list_has_null = true;
-		else
-			arrelems = lappend(arrelems, copyObject(val));
+		if (boundinfo)
+		{
+			ndatums = boundinfo->ndatums;
+
+			if (partition_bound_accepts_nulls(boundinfo))
+				list_has_null = true;
+		}
+
+		/*
+		 * If default is the only partition, there need not be any partition
+		 * constraint on it.
+		 */
+		if (ndatums == 0 && !list_has_null)
+			return NIL;
+
+		for (i = 0; i < ndatums; i++)
+		{
+			Const	   *val;
+
+			/* Construct const from datum */
+			val = makeConst(key->parttypid[0],
+							key->parttypmod[0],
+							key->parttypcoll[0],
+							key->parttyplen[0],
+							*boundinfo->datums[i],
+							false,	/* isnull */
+							key->parttypbyval[0]);
+
+			arrelems = lappend(arrelems, val);
+		}
+	}
+	else
+	{
+		/*
+		 * Create list of Consts for the allowed values, excluding any nulls.
+		 */
+		foreach(cell, spec->listdatums)
+		{
+			Const	   *val = castNode(Const, lfirst(cell));
+
+			if (val->constisnull)
+				list_has_null = true;
+			else
+				arrelems = lappend(arrelems, copyObject(val));
+		}
 	}
 
 	if (arrelems)
@@ -1390,6 +1476,25 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 			result = list_make1(nulltest);
 	}
 
+	/*
+	 * In case of the default partition, the constraint is of the form
+	 * "!(result)" i.e. one of the following two forms:
+	 *
+	 * 1. NOT ((keycol IS NULL) OR (keycol = ANY (arr)))
+	 *
+	 * 2. NOT ((keycol IS NOT NULL) AND (keycol = ANY (arr)))
+	 *
+	 * Note that, in general, applying NOT to a constraint expression doesn't
+	 * necessarily invert the set of rows it accepts, because NOT (NULL) is
+	 * NULL.  However, the partition constraints we construct here never
+	 * evaluate to NULL, so applying NOT works as intended.
+	 */
+	if (spec->is_default)
+	{
+		result = list_make1(make_ands_explicit(result));
+		result = list_make1(makeBoolExpr(NOT_EXPR, result, -1));
+	}
+
 	return result;
 }
 
@@ -1997,8 +2102,8 @@ get_partition_for_tuple(PartitionDispatch *pd,
 		}
 
 		/*
-		 * A null partition key is only acceptable if null-accepting list
-		 * partition exists.
+		 * If this is a NULL, route it to the null-accepting partition.��
+		 * Otherwise, route by searching the array of partition bounds.
 		 */
 		cur_index = -1;
 		if (isnull[0] && partition_bound_accepts_nulls(partdesc->boundinfo))
@@ -2036,11 +2141,17 @@ get_partition_for_tuple(PartitionDispatch *pd,
 		}
 
 		/*
-		 * cur_index < 0 means we failed to find a partition of this parent.
-		 * cur_index >= 0 means we either found the leaf partition, or the
-		 * next parent to find a partition of.
+		 * cur_index < 0 means we could not find a non-default partition of
+		 * this parent. cur_index >= 0 means we either found the leaf
+		 * partition, or the next parent to find a partition of.
+		 *
+		 * If we couldn't find a non-default partition check if the default
+		 * partition exists, if it does, get its index.
 		 */
 		if (cur_index < 0)
+			cur_index = partdesc->boundinfo->default_index;
+
+		if (cur_index < 0)
 		{
 			result = -1;
 			*failed_at = parent;
@@ -2336,3 +2447,21 @@ partition_bound_bsearch(PartitionKey key, PartitionBoundInfo boundinfo,
 
 	return lo;
 }
+
+/*
+ * get_default_partition_oid
+ *
+ * If the given relation has a default partition return the OID of the default
+ * partition, otherwise return InvalidOid.
+ */
+Oid
+get_default_partition_oid(Relation parent)
+{
+	PartitionDesc partdesc = RelationGetPartitionDesc(parent);
+
+	if (partdesc && partdesc->boundinfo &&
+		partition_bound_has_default(partdesc->boundinfo))
+		return partdesc->oids[partdesc->boundinfo->default_index];
+
+	return InvalidOid;
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f24602d..bcca46b 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -767,7 +767,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	{
 		PartitionBoundSpec *bound;
 		ParseState *pstate;
-		Oid			parentId = linitial_oid(inheritOids);
+		Oid			parentId = linitial_oid(inheritOids),
+					defaultPartOid;
 		Relation	parent;
 
 		/* Already have strong enough lock on the parent */
@@ -783,6 +784,17 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 					 errmsg("\"%s\" is not partitioned",
 							RelationGetRelationName(parent))));
 
+		/*
+		 * A table cannot be created as a partition of a parent already having
+		 * a default partition.
+		 */
+		defaultPartOid = get_default_partition_oid(parent);
+		if (OidIsValid(defaultPartOid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("cannot add a new partition to table \"%s\" having a default partition",
+							RelationGetRelationName(parent))));
+
 		/* Tranform the bound values */
 		pstate = make_parsestate(NULL);
 		pstate->p_sourcetext = queryString;
@@ -13591,8 +13603,17 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	int			natts;
 	TupleDesc	tupleDesc;
 	ObjectAddress address;
+	Oid			defaultPartOid;
 	const char *trigger_name;
 
+	/* A partition cannot be attached if there exists a default partition */
+	defaultPartOid = get_default_partition_oid(rel);
+	if (OidIsValid(defaultPartOid))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot attach a new partition to table \"%s\" having a default partition",
+						RelationGetRelationName(rel))));
+
 	attachRel = heap_openrv(cmd->name, AccessExclusiveLock);
 
 	/*
@@ -13794,6 +13815,15 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 				new_null[Natts_pg_class],
 				new_repl[Natts_pg_class];
 	ObjectAddress address;
+	Oid			defaultPartOid;
+
+	/*
+	 * We must also lock the default partition, for the same reasons explained
+	 * in heap_drop_with_catalog().
+	 */
+	defaultPartOid = get_default_partition_oid(rel);
+	if (OidIsValid(defaultPartOid))
+		LockRelationOid(defaultPartOid, AccessExclusiveLock);
 
 	partRel = heap_openrv(name, AccessShareLock);
 
@@ -13826,6 +13856,13 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 	heap_close(classRel, RowExclusiveLock);
 
 	/*
+	 * We must invalidate default partition's relcache, for the same reasons
+	 * explained in heap_drop_with_catalog().
+	 */
+	if (OidIsValid(defaultPartOid))
+		CacheInvalidateRelcacheByRelid(defaultPartOid);
+
+	/*
 	 * Invalidate the parent's relcache so that the partition is no longer
 	 * included in its partition descriptor.
 	 */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 67ac814..a4ba58a 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4445,6 +4445,7 @@ _copyPartitionBoundSpec(const PartitionBoundSpec *from)
 	PartitionBoundSpec *newnode = makeNode(PartitionBoundSpec);
 
 	COPY_SCALAR_FIELD(strategy);
+	COPY_SCALAR_FIELD(is_default);
 	COPY_NODE_FIELD(listdatums);
 	COPY_NODE_FIELD(lowerdatums);
 	COPY_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 91d64b7..373f050 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2838,6 +2838,7 @@ static bool
 _equalPartitionBoundSpec(const PartitionBoundSpec *a, const PartitionBoundSpec *b)
 {
 	COMPARE_SCALAR_FIELD(strategy);
+	COMPARE_SCALAR_FIELD(is_default);
 	COMPARE_NODE_FIELD(listdatums);
 	COMPARE_NODE_FIELD(lowerdatums);
 	COMPARE_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 21e39a0..e5a02ba 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3571,6 +3571,7 @@ _outPartitionBoundSpec(StringInfo str, const PartitionBoundSpec *node)
 	WRITE_NODE_TYPE("PARTITIONBOUNDSPEC");
 
 	WRITE_CHAR_FIELD(strategy);
+	WRITE_BOOL_FIELD(is_default);
 	WRITE_NODE_FIELD(listdatums);
 	WRITE_NODE_FIELD(lowerdatums);
 	WRITE_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 8ab09d7..2c09767 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2388,6 +2388,7 @@ _readPartitionBoundSpec(void)
 	READ_LOCALS(PartitionBoundSpec);
 
 	READ_CHAR_FIELD(strategy);
+	READ_BOOL_FIELD(is_default);
 	READ_NODE_FIELD(listdatums);
 	READ_NODE_FIELD(lowerdatums);
 	READ_NODE_FIELD(upperdatums);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0f3998f..93c42bc 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -575,7 +575,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>			part_strategy
 %type <partelem>	part_elem
 %type <list>		part_params
-%type <partboundspec> ForValues
+%type <partboundspec> PartitionBoundSpec
 %type <node>		partbound_datum PartitionRangeDatum
 %type <list>		partbound_datum_list range_datum_list
 
@@ -2011,7 +2011,7 @@ alter_table_cmds:
 
 partition_cmd:
 			/* ALTER TABLE <name> ATTACH PARTITION <table_name> FOR VALUES */
-			ATTACH PARTITION qualified_name ForValues
+			ATTACH PARTITION qualified_name PartitionBoundSpec
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
 					PartitionCmd *cmd = makeNode(PartitionCmd);
@@ -2650,13 +2650,14 @@ alter_identity_column_option:
 				}
 		;
 
-ForValues:
+PartitionBoundSpec:
 			/* a LIST partition */
 			FOR VALUES IN_P '(' partbound_datum_list ')'
 				{
 					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
 
 					n->strategy = PARTITION_STRATEGY_LIST;
+					n->is_default = false;
 					n->listdatums = $5;
 					n->location = @3;
 
@@ -2669,12 +2670,24 @@ ForValues:
 					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
 
 					n->strategy = PARTITION_STRATEGY_RANGE;
+					n->is_default = false;
 					n->lowerdatums = $5;
 					n->upperdatums = $9;
 					n->location = @3;
 
 					$$ = n;
 				}
+
+			/* a DEFAULT partition */
+			| DEFAULT
+				{
+					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+					n->is_default = true;
+					n->location = @1;
+
+					$$ = n;
+				}
 		;
 
 partbound_datum:
@@ -3135,7 +3148,7 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
-			OptTypedTableElementList ForValues OptPartitionSpec OptWith
+			OptTypedTableElementList PartitionBoundSpec OptPartitionSpec OptWith
 			OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
@@ -3154,7 +3167,7 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
-			qualified_name OptTypedTableElementList ForValues OptPartitionSpec
+			qualified_name OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
 			OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
@@ -4869,7 +4882,7 @@ CreateForeignTableStmt:
 					$$ = (Node *) n;
 				}
 		| CREATE FOREIGN TABLE qualified_name
-			PARTITION OF qualified_name OptTypedTableElementList ForValues
+			PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
 			SERVER name create_generic_options
 				{
 					CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
@@ -4890,7 +4903,7 @@ CreateForeignTableStmt:
 					$$ = (Node *) n;
 				}
 		| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
-			PARTITION OF qualified_name OptTypedTableElementList ForValues
+			PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
 			SERVER name create_generic_options
 				{
 					CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index ee5f3a3..57c09c2 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -60,6 +61,7 @@
 #include "parser/parse_utilcmd.h"
 #include "parser/parser.h"
 #include "rewrite/rewriteManip.h"
+#include "storage/lmgr.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
@@ -3303,6 +3305,23 @@ transformPartitionBound(ParseState *pstate, Relation parent,
 	/* Avoid scribbling on input */
 	result_spec = copyObject(spec);
 
+	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
+		 * partition bound spec.
+		 */
+		result_spec->strategy = strategy;
+
+		return result_spec;
+	}
+
 	if (strategy == PARTITION_STRATEGY_LIST)
 	{
 		ListCell   *cell;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index d2fb20d..0ce94ed 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8679,10 +8679,18 @@ get_rule_expr(Node *node, deparse_context *context,
 		case T_PartitionBoundSpec:
 			{
 				PartitionBoundSpec *spec = (PartitionBoundSpec *) node;
+				char		strategy = spec->strategy;
 				ListCell   *cell;
 				char	   *sep;
 
-				switch (spec->strategy)
+				if (spec->is_default)
+				{
+					Assert(strategy == PARTITION_STRATEGY_LIST);
+					appendStringInfoString(buf, "DEFAULT");
+					break;
+				}
+
+				switch (strategy)
 				{
 					case PARTITION_STRATEGY_LIST:
 						Assert(spec->listdatums != NIL);
@@ -8748,7 +8756,7 @@ get_rule_expr(Node *node, deparse_context *context,
 
 					default:
 						elog(ERROR, "unrecognized partition strategy: %d",
-							 (int) spec->strategy);
+							 (int) strategy);
 						break;
 				}
 			}
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index e9fdc90..db937a8 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2050,7 +2050,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("ATTACH", "PARTITION", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 	else if (TailMatches2("FOR", "VALUES"))
 		COMPLETE_WITH_LIST2("FROM (", "IN (");
 
@@ -2489,7 +2489,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("PARTITION", "OF", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 
 /* CREATE TABLESPACE */
 	else if (Matches3("CREATE", "TABLESPACE", MatchAny))
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index f10879a..707fca4 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -98,4 +98,6 @@ extern int get_partition_for_tuple(PartitionDispatch *pd,
 						EState *estate,
 						PartitionDispatchData **failed_at,
 						TupleTableSlot **failed_slot);
+extern Oid	get_default_partition_oid(Relation parent);
+
 #endif							/* PARTITION_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 1d96169..387ce05 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -797,6 +797,7 @@ typedef struct PartitionBoundSpec
 	NodeTag		type;
 
 	char		strategy;		/* see PARTITION_STRATEGY codes above */
+	bool		is_default;		/* is it a default partition bound? */
 
 	/* Partitioning info for LIST strategy: */
 	List	   *listdatums;		/* List of Consts (or A_Consts in raw tree) */
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 13d6a4b..0acf7ef 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3273,6 +3273,11 @@ SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::reg
 CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
 ERROR:  partition "fail_part" would overlap partition "part_1"
+-- attaching default partition overlaps if the default partition already exists
+CREATE TABLE def_part PARTITION OF list_parted DEFAULT;
+CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT;
+ERROR:  cannot attach a new partition to table "list_parted" having a default partition
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
 	a int,
@@ -3286,6 +3291,15 @@ ERROR:  partition constraint is violated by some row
 -- should be ok after deleting the bad row
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+-- check partition cannot be attached if default exists
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
+INSERT INTO list_parted2_def VALUES (11, 'z');
+CREATE TABLE part_3 (LIKE list_parted2);
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+ERROR:  cannot attach a new partition to table "list_parted2" having a default partition
+-- should be ok after detaching a default partition
+ALTER TABLE list_parted2 DETACH PARTITION list_parted2_def;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
 CREATE TABLE part_3_4 (
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 0b2f399..cab4d6d 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -467,6 +467,13 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
 ERROR:  invalid bound specification for a list partition
 LINE 1: ...BLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) T...
                                                              ^
+-- check default partition cannot be created more than once
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
+ERROR:  cannot add a new partition to table "list_parted" having a default partition
+-- check that no partition can be created after default partition
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (3);
+ERROR:  cannot add a new partition to table "list_parted" having a default partition
 -- specified literal can't be cast to the partition column data type
 CREATE TABLE bools (
 	a bool
@@ -514,6 +521,9 @@ 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);
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index dd5dddb..4ff84a0 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -219,17 +219,56 @@ insert into part_null values (null, 0);
 create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b);
 create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
 create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
+-- test default partition
+create table part_default partition of list_parted default;
+-- fail
+insert into part_default values ('aa', 2);
+ERROR:  new row for relation "part_default" violates partition constraint
+DETAIL:  Failing row contains (aa, 2).
+insert into part_default values (null, 2);
+ERROR:  new row for relation "part_default" violates partition constraint
+DETAIL:  Failing row contains (null, 2).
+-- ok
+insert into part_default values ('Zz', 2);
+-- check in case of multi-level default partitioned table
+drop table part_default;
+create table part_default partition of list_parted default partition by range(b);
+create table part_default_p1 partition of part_default for values from (20) to (30);
+create table part_default_p2 partition of part_default for values from (30) to (40);
 -- fail
 insert into part_ee_ff1 values ('EE', 11);
 ERROR:  new row for relation "part_ee_ff1" violates partition constraint
 DETAIL:  Failing row contains (EE, 11).
+insert into part_default_p2 values ('gg', 43);
+ERROR:  new row for relation "part_default_p2" violates partition constraint
+DETAIL:  Failing row contains (gg, 43).
 -- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
 insert into part_ee_ff1 values ('cc', 1);
 ERROR:  new row for relation "part_ee_ff1" violates partition constraint
 DETAIL:  Failing row contains (cc, 1).
+insert into part_default values ('gg', 43);
+ERROR:  no partition of relation "part_default" found for row
+DETAIL:  Partition key of the failing row contains (b) = (43).
 -- ok
 insert into part_ee_ff1 values ('ff', 1);
 insert into part_ee_ff2 values ('ff', 11);
+insert into part_default_p1 values ('cd', 25);
+insert into part_default_p2 values ('de', 35);
+insert into list_parted values ('ab', 21);
+-- drop default, as we need to add some more partitions to test tuple routing
+select tableoid::regclass, * from list_parted;
+    tableoid     | a  | b  
+-----------------+----+----
+ part_cc_dd      | cC |  1
+ part_null       |    |  0
+ part_ee_ff1     | ff |  1
+ part_ee_ff2     | ff | 11
+ part_default_p1 | cd | 25
+ part_default_p1 | ab | 21
+ part_default_p2 | de | 35
+(7 rows)
+
+drop table part_default;
 -- Check tuple routing for partitioned tables
 -- fail
 insert into range_parted values ('a', 0);
@@ -316,6 +355,23 @@ select tableoid::regclass::text, a, min(b) as min_b, max(b) as max_b from list_p
 
 -- cleanup
 drop table range_parted, list_parted;
+-- test adding default partition as first partition accepts any value including
+-- null
+create table list_parted (a int) partition by list (a);
+create table part_default partition of list_parted default;
+insert into part_default values (null);
+insert into part_default values (1);
+insert into part_default values (-1);
+select tableoid::regclass, a from list_parted;
+   tableoid   | a  
+--------------+----
+ part_default |   
+ part_default |  1
+ part_default | -1
+(3 rows)
+
+-- cleanup
+drop table list_parted;
 -- more tests for certain multi-level partitioning scenarios
 create table mlparted (a int, b int) partition by range (a, b);
 create table mlparted1 (b int not null, a int not null) partition by range ((b+0));
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
index 3f3db33..53fe3d4 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -252,3 +252,25 @@ NOTICE:  3
  
 (1 row)
 
+-- Check that addition or removal of any partition is correctly dealt with by
+-- default partition table when it is being used in cached plan.
+create table list_parted (a int) partition by list(a);
+create table list_part_null partition of list_parted for values in (null);
+create table list_part_1 partition of list_parted for values in (1);
+create table list_part_def partition of list_parted default;
+prepare pstmt_def_insert (int) as insert into list_part_def values($1);
+-- should fail
+execute pstmt_def_insert(null);
+ERROR:  new row for relation "list_part_def" violates partition constraint
+DETAIL:  Failing row contains (null).
+execute pstmt_def_insert(1);
+ERROR:  new row for relation "list_part_def" violates partition constraint
+DETAIL:  Failing row contains (1).
+alter table list_parted detach partition list_part_null;
+-- should be ok
+execute pstmt_def_insert(null);
+drop table list_part_1;
+-- should be ok
+execute pstmt_def_insert(1);
+drop table list_parted;
+deallocate pstmt_def_insert;
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index 9366f04..9912ef2 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -218,5 +218,20 @@ 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;
+create table list_parted (
+	a text,
+	b int
+) partition by list (a);
+create table list_part1  partition of list_parted for values in ('a', 'b');
+create table list_default partition of list_parted default;
+insert into list_part1 values ('a', 1);
+insert into list_default values ('d', 10);
+-- fail
+update list_default set a = 'a' where a = 'd';
+ERROR:  new row for relation "list_default" violates partition constraint
+DETAIL:  Failing row contains (a, 10).
+-- ok
+update list_default set a = 'x' where a = 'd';
 -- cleanup
 drop table range_parted;
+drop table list_parted;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 5dd1402..2346c1f 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2095,6 +2095,10 @@ SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::reg
 -- check that the new partition won't overlap with an existing partition
 CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+-- attaching default partition overlaps if the default partition already exists
+CREATE TABLE def_part PARTITION OF list_parted DEFAULT;
+CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT;
 
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
@@ -2111,6 +2115,15 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 
+-- check partition cannot be attached if default exists
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
+INSERT INTO list_parted2_def VALUES (11, 'z');
+CREATE TABLE part_3 (LIKE list_parted2);
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+-- should be ok after detaching a default partition
+ALTER TABLE list_parted2 DETACH PARTITION list_parted2_def;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
 CREATE TABLE part_3_4 (
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index cb7aa5b..650c1f5 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -447,6 +447,12 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
 -- trying to specify range for list partitioned table
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
 
+-- check default partition cannot be created more than once
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
+-- check that no partition can be created after default partition
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (3);
+
 -- specified literal can't be cast to the partition column data type
 CREATE TABLE bools (
 	a bool
@@ -484,6 +490,8 @@ 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);
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index fe63020..f2131a2 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -132,13 +132,34 @@ create table part_ee_ff partition of list_parted for values in ('ee', 'ff') part
 create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
 create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
 
+-- test default partition
+create table part_default partition of list_parted default;
+-- fail
+insert into part_default values ('aa', 2);
+insert into part_default values (null, 2);
+-- ok
+insert into part_default values ('Zz', 2);
+-- check in case of multi-level default partitioned table
+drop table part_default;
+create table part_default partition of list_parted default partition by range(b);
+create table part_default_p1 partition of part_default for values from (20) to (30);
+create table part_default_p2 partition of part_default for values from (30) to (40);
+
 -- fail
 insert into part_ee_ff1 values ('EE', 11);
+insert into part_default_p2 values ('gg', 43);
 -- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
 insert into part_ee_ff1 values ('cc', 1);
+insert into part_default values ('gg', 43);
 -- ok
 insert into part_ee_ff1 values ('ff', 1);
 insert into part_ee_ff2 values ('ff', 11);
+insert into part_default_p1 values ('cd', 25);
+insert into part_default_p2 values ('de', 35);
+insert into list_parted values ('ab', 21);
+-- drop default, as we need to add some more partitions to test tuple routing
+select tableoid::regclass, * from list_parted;
+drop table part_default;
 
 -- Check tuple routing for partitioned tables
 
@@ -188,6 +209,17 @@ select tableoid::regclass::text, a, min(b) as min_b, max(b) as max_b from list_p
 -- cleanup
 drop table range_parted, list_parted;
 
+-- test adding default partition as first partition accepts any value including
+-- null
+create table list_parted (a int) partition by list (a);
+create table part_default partition of list_parted default;
+insert into part_default values (null);
+insert into part_default values (1);
+insert into part_default values (-1);
+select tableoid::regclass, a from list_parted;
+-- cleanup
+drop table list_parted;
+
 -- more tests for certain multi-level partitioning scenarios
 create table mlparted (a int, b int) partition by range (a, b);
 create table mlparted1 (b int not null, a int not null) partition by range ((b+0));
diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql
index bc20861..89ddf3f 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -156,3 +156,22 @@ end$$ language plpgsql;
 
 select cachebug();
 select cachebug();
+
+-- Check that addition or removal of any partition is correctly dealt with by
+-- default partition table when it is being used in cached plan.
+create table list_parted (a int) partition by list(a);
+create table list_part_null partition of list_parted for values in (null);
+create table list_part_1 partition of list_parted for values in (1);
+create table list_part_def partition of list_parted default;
+prepare pstmt_def_insert (int) as insert into list_part_def values($1);
+-- should fail
+execute pstmt_def_insert(null);
+execute pstmt_def_insert(1);
+alter table list_parted detach partition list_part_null;
+-- should be ok
+execute pstmt_def_insert(null);
+drop table list_part_1;
+-- should be ok
+execute pstmt_def_insert(1);
+drop table list_parted;
+deallocate pstmt_def_insert;
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 6637119..44fb0dc 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -125,5 +125,20 @@ update range_parted set b = b - 1 where b = 10;
 -- ok
 update range_parted set b = b + 1 where b = 10;
 
+create table list_parted (
+	a text,
+	b int
+) partition by list (a);
+create table list_part1  partition of list_parted for values in ('a', 'b');
+create table list_default partition of list_parted default;
+insert into list_part1 values ('a', 1);
+insert into list_default values ('d', 10);
+
+-- fail
+update list_default set a = 'a' where a = 'd';
+-- ok
+update list_default set a = 'x' where a = 'd';
+
 -- cleanup
 drop table range_parted;
+drop table list_parted;
-- 
1.9.1

0004-Store-the-default-partition-OID-in-catalog-pg_partit.patch0000664000175000017500000002465513134125753023723 0ustar  jeevanjeevanFrom 5d5205f48cce0f38a9e659bd4dc497e688a5d6c1 Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Thu, 20 Jul 2017 18:36:32 +0530
Subject: [PATCH 4/8] Store the default partition OID in catalog
 pg_partitioned_table

This patch introduces a new field partdefid to pg_partitioned_table.
This can be used to quickly look up for default partition instead
of looking for every child in pg_inherits, then looking its entry
in pg_class to verify if the partition is default of or not.

Jeevan Ladhe.
---
 src/backend/catalog/heap.c                 | 16 +++++++---
 src/backend/catalog/partition.c            | 50 ++++++++++++++++++++++++++----
 src/backend/commands/tablecmds.c           | 32 ++++++++++++++-----
 src/include/catalog/partition.h            |  3 +-
 src/include/catalog/pg_partitioned_table.h | 13 +++++---
 5 files changed, 90 insertions(+), 24 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 068c3bd..0f6b41c 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1755,8 +1755,7 @@ RemoveAttrDefaultById(Oid attrdefId)
 void
 heap_drop_with_catalog(Oid relid)
 {
-	Relation	rel,
-				parentRel;
+	Relation	rel;
 	HeapTuple	tuple;
 	Oid			parentOid = InvalidOid,
 				defaultPartOid = InvalidOid;
@@ -1774,7 +1773,7 @@ heap_drop_with_catalog(Oid relid)
 	if (((Form_pg_class) GETSTRUCT(tuple))->relispartition)
 	{
 		parentOid = get_partition_parent(relid);
-		parentRel = heap_open(parentOid, AccessExclusiveLock);
+		LockRelationOid(parentOid, AccessExclusiveLock);
 
 		/*
 		 * The partition constraint of the default partition depends on the
@@ -1787,7 +1786,7 @@ heap_drop_with_catalog(Oid relid)
 		 * we commit and send out a shared-cache-inval notice that will make
 		 * them update their index lists.
 		 */
-		defaultPartOid = get_default_partition_oid(parentRel);
+		defaultPartOid = get_default_partition_oid(parentOid);
 		if (OidIsValid(defaultPartOid))
 			LockRelationOid(defaultPartOid, AccessExclusiveLock);
 	}
@@ -1841,6 +1840,13 @@ heap_drop_with_catalog(Oid relid)
 		RemovePartitionKeyByRelId(relid);
 
 	/*
+	 * If the relation being dropped is the default partition itself,
+	 * invalidate its entry in pg_partitioned_table.
+	 */
+	if (relid == defaultPartOid)
+		update_default_partition_oid(parentOid, InvalidOid);
+
+	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -1914,7 +1920,6 @@ heap_drop_with_catalog(Oid relid)
 		 */
 		CacheInvalidateRelcacheByRelid(parentOid);
 		/* keep the lock */
-		heap_close(parentRel, NoLock);
 	}
 }
 
@@ -3163,6 +3168,7 @@ StorePartitionKey(Relation rel,
 	values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
 	values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
 	values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
+	values[Anum_pg_partitioned_table_partdefid - 1] = ObjectIdGetDatum(InvalidOid);
 	values[Anum_pg_partitioned_table_partattrs - 1] = PointerGetDatum(partattrs_vec);
 	values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
 	values[Anum_pg_partitioned_table_partcollation - 1] = PointerGetDatum(partcollation_vec);
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index a69015a..805d6f7 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -27,6 +27,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_inherits_fn.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_type.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
@@ -2455,13 +2456,50 @@ partition_bound_bsearch(PartitionKey key, PartitionBoundInfo boundinfo,
  * partition, otherwise return InvalidOid.
  */
 Oid
-get_default_partition_oid(Relation parent)
+get_default_partition_oid(Oid parentId)
 {
-	PartitionDesc partdesc = RelationGetPartitionDesc(parent);
+	HeapTuple	tuple;
+	Oid			defaultPartId = InvalidOid;
+
+	tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(parentId));
+
+	if (HeapTupleIsValid(tuple))
+	{
+		Form_pg_partitioned_table part_table_form;
+
+		part_table_form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+		defaultPartId = part_table_form->partdefid;
+	}
+
+	ReleaseSysCache(tuple);
+	return defaultPartId;
+}
+
+/*
+ * update_default_partition_oid
+ *
+ * Updates the pg_partition_table catalog partdefid field for the given parent
+ * with the given default partition oid.
+ */
+void
+update_default_partition_oid(Oid parentId, Oid defaultPartId)
+{
+	HeapTuple	tuple;
+	Relation	pg_partitioned_table;
+	Form_pg_partitioned_table part_table_form;
+
+	pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCacheCopy1(PARTRELID, ObjectIdGetDatum(parentId));
+
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of relation %u",
+			 parentId);
 
-	if (partdesc && partdesc->boundinfo &&
-		partition_bound_has_default(partdesc->boundinfo))
-		return partdesc->oids[partdesc->boundinfo->default_index];
+	part_table_form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+	part_table_form->partdefid = defaultPartId;
+	CatalogTupleUpdate(pg_partitioned_table, &tuple->t_self, tuple);
 
-	return InvalidOid;
+	heap_freetuple(tuple);
+	heap_close(pg_partitioned_table, RowExclusiveLock);
 }
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index bcca46b..fc8a6f6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -788,7 +788,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		 * A table cannot be created as a partition of a parent already having
 		 * a default partition.
 		 */
-		defaultPartOid = get_default_partition_oid(parent);
+		defaultPartOid = get_default_partition_oid(RelationGetRelid(parent));
 		if (OidIsValid(defaultPartOid))
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
@@ -811,6 +811,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		/* Update the pg_class entry. */
 		StorePartitionBound(rel, parent, bound);
 
+		/* Update the default partition oid */
+		if (bound->is_default)
+			update_default_partition_oid(RelationGetRelid(parent), relationId);
+
 		heap_close(parent, NoLock);
 
 		/*
@@ -13607,7 +13611,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	const char *trigger_name;
 
 	/* A partition cannot be attached if there exists a default partition */
-	defaultPartOid = get_default_partition_oid(rel);
+	defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
 	if (OidIsValid(defaultPartOid))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
@@ -13769,6 +13773,11 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	/* Update the pg_class entry. */
 	StorePartitionBound(attachRel, rel, cmd->bound);
 
+	/* Update the default partition oid */
+	if (cmd->bound->is_default)
+		update_default_partition_oid(RelationGetRelid(rel),
+									 RelationGetRelid(attachRel));
+
 	/*
 	 * Generate partition constraint from the partition bound specification.
 	 * If the parent itself is a partition, make sure to include its
@@ -13821,7 +13830,7 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 	 * We must also lock the default partition, for the same reasons explained
 	 * in heap_drop_with_catalog().
 	 */
-	defaultPartOid = get_default_partition_oid(rel);
+	defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
 	if (OidIsValid(defaultPartOid))
 		LockRelationOid(defaultPartOid, AccessExclusiveLock);
 
@@ -13855,12 +13864,21 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 	heap_freetuple(newtuple);
 	heap_close(classRel, RowExclusiveLock);
 
-	/*
-	 * We must invalidate default partition's relcache, for the same reasons
-	 * explained in heap_drop_with_catalog().
-	 */
 	if (OidIsValid(defaultPartOid))
+	{
+		/*
+		 * If the detach relation is the default partition itself, invalidate
+		 * its entry in pg_partitioned_table.
+		 */
+		if (RelationGetRelid(partRel) == defaultPartOid)
+			update_default_partition_oid(RelationGetRelid(rel), InvalidOid);
+
+		/*
+		 * We must invalidate default partition's relcache, for the same
+		 * reasons explained in heap_drop_with_catalog().
+		 */
 		CacheInvalidateRelcacheByRelid(defaultPartOid);
+	}
 
 	/*
 	 * Invalidate the parent's relcache so that the partition is no longer
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 707fca4..7800c96 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -98,6 +98,7 @@ extern int get_partition_for_tuple(PartitionDispatch *pd,
 						EState *estate,
 						PartitionDispatchData **failed_at,
 						TupleTableSlot **failed_slot);
-extern Oid	get_default_partition_oid(Relation parent);
+extern Oid	get_default_partition_oid(Oid parentId);
+extern void update_default_partition_oid(Oid parentId, Oid defaultPartId);
 
 #endif							/* PARTITION_H */
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
index 38d64d6..78c7ee9 100644
--- a/src/include/catalog/pg_partitioned_table.h
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -32,6 +32,8 @@ CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
 	Oid			partrelid;		/* partitioned table oid */
 	char		partstrat;		/* partitioning strategy */
 	int16		partnatts;		/* number of partition key columns */
+	Oid			partdefid;		/* default partition oid; InvalidOid if there
+								 * isn't one */
 
 	/*
 	 * variable-length fields start here, but we allow direct access to
@@ -62,13 +64,14 @@ typedef FormData_pg_partitioned_table *Form_pg_partitioned_table;
  *		compiler constants for pg_partitioned_table
  * ----------------
  */
-#define Natts_pg_partitioned_table				7
+#define Natts_pg_partitioned_table				8
 #define Anum_pg_partitioned_table_partrelid		1
 #define Anum_pg_partitioned_table_partstrat		2
 #define Anum_pg_partitioned_table_partnatts		3
-#define Anum_pg_partitioned_table_partattrs		4
-#define Anum_pg_partitioned_table_partclass		5
-#define Anum_pg_partitioned_table_partcollation 6
-#define Anum_pg_partitioned_table_partexprs		7
+#define Anum_pg_partitioned_table_partdefid		4
+#define Anum_pg_partitioned_table_partattrs		5
+#define Anum_pg_partitioned_table_partclass		6
+#define Anum_pg_partitioned_table_partcollation	7
+#define Anum_pg_partitioned_table_partexprs		8
 
 #endif							/* PG_PARTITIONED_TABLE_H */
-- 
1.9.1

0005-Default-partition-extended-for-create-and-attach.patch0000664000175000017500000010377013134125753023221 0ustar  jeevanjeevanFrom d841530cb280daf89cb64d57f92a923e3142da58 Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Thu, 20 Jul 2017 18:36:41 +0530
Subject: [PATCH 5/8] Default partition extended for create and attach

This patch extends the previous patch in this series to allow a
new partition to be created even when a default partition on a list
partition exists. Also, extends the regression tests to test this
functionality.

Jeevan Ladhe.
---
 src/backend/catalog/heap.c                 |  33 +++---
 src/backend/catalog/partition.c            | 162 ++++++++++++++++++++++++++++-
 src/backend/commands/tablecmds.c           |  41 +++++---
 src/test/regress/expected/alter_table.out  |  22 +++-
 src/test/regress/expected/create_table.out |  15 +--
 src/test/regress/expected/insert.out       |  56 ++++------
 src/test/regress/expected/plancache.out    |  20 ++--
 src/test/regress/sql/alter_table.sql       |  18 +++-
 src/test/regress/sql/create_table.sql      |  12 +--
 src/test/regress/sql/insert.sql            |  15 +--
 src/test/regress/sql/plancache.sql         |  16 ++-
 11 files changed, 300 insertions(+), 110 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 0f6b41c..c1cb5c6 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1776,15 +1776,8 @@ heap_drop_with_catalog(Oid relid)
 		LockRelationOid(parentOid, AccessExclusiveLock);
 
 		/*
-		 * The partition constraint of the default partition depends on the
-		 * partition bounds of every other partition. It is possible that
-		 * other backend might be about to execute a query on the default
-		 * partition table and the query relies on previously cached default
-		 * partition constraints, which won't be correct after removal of a
-		 * partition. We must therefore take a table lock strong enough to
-		 * prevent all queries on the default partition from proceeding until
-		 * we commit and send out a shared-cache-inval notice that will make
-		 * them update their index lists.
+		 * We must also lock the default partition, for the same reasons
+		 * explained in DefineRelation().
 		 */
 		defaultPartOid = get_default_partition_oid(parentOid);
 		if (OidIsValid(defaultPartOid))
@@ -1906,10 +1899,8 @@ heap_drop_with_catalog(Oid relid)
 	if (OidIsValid(parentOid))
 	{
 		/*
-		 * The partition constraint for the default partition depends on the
-		 * partition bounds of every other partition, so we must invalidate
-		 * the relcache entry for that partition every time a partition is
-		 * added or removed.
+		 * We must invalidate default partition's relcache, for the same
+		 * reasons explained in StorePartitionBound().
 		 */
 		if (OidIsValid(defaultPartOid))
 			CacheInvalidateRelcacheByRelid(defaultPartOid);
@@ -3253,8 +3244,9 @@ RemovePartitionKeyByRelId(Oid relid)
  *		Update pg_class tuple of rel to store the partition bound and set
  *		relispartition to true
  *
- * Also, invalidate the parent's relcache, so that the next rebuild will load
- * the new partition's info into its partition descriptor.
+ * Also, invalidate the parent's relcache entry, so that the next rebuild will
+ * load he new partition's info into its partition descriptor.�� If there is a
+ * default partition, we must invalidate its relcache entry as well.
  */
 void
 StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
@@ -3265,6 +3257,7 @@ StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
 	Datum		new_val[Natts_pg_class];
 	bool		new_null[Natts_pg_class],
 				new_repl[Natts_pg_class];
+	Oid			defaultPartOid;
 
 	/* Update pg_class tuple */
 	classRel = heap_open(RelationRelationId, RowExclusiveLock);
@@ -3302,5 +3295,15 @@ StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
 	heap_freetuple(newtuple);
 	heap_close(classRel, RowExclusiveLock);
 
+	/*
+	 * The partition constraint for the default partition depends on the
+	 * partition bounds of every other partition, so we must invalidate the
+	 * relcache entry for that partition every time a partition is added or
+	 * removed.
+	 */
+	defaultPartOid = get_default_partition_oid(RelationGetRelid(parent));
+	if (OidIsValid(defaultPartOid))
+		CacheInvalidateRelcacheByRelid(defaultPartOid);
+
 	CacheInvalidateRelcache(parent);
 }
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 805d6f7..0b747eb 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -36,6 +36,7 @@
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planmain.h"
+#include "optimizer/prep.h"
 #include "optimizer/var.h"
 #include "rewrite/rewriteManip.h"
 #include "storage/lmgr.h"
@@ -152,6 +153,8 @@ static int32 partition_bound_cmp(PartitionKey key,
 static int partition_bound_bsearch(PartitionKey key,
 						PartitionBoundInfo boundinfo,
 						void *probe, bool probe_is_bound, bool *is_equal);
+static void check_default_allows_bound(Relation parent,
+						   PartitionBoundSpec *new_spec);
 
 /*
  * RelationBuildPartitionDesc
@@ -707,10 +710,27 @@ check_new_partition_bound(char *relname, Relation parent,
 {
 	PartitionKey key = RelationGetPartitionKey(parent);
 	PartitionDesc partdesc = RelationGetPartitionDesc(parent);
+	PartitionBoundInfo boundinfo = partdesc->boundinfo;
 	ParseState *pstate = make_parsestate(NULL);
 	int			with = -1;
 	bool		overlap = false;
 
+	if (spec->is_default)
+	{
+		/* Default partition cannot be added if there already exists one. */
+		if (partdesc->nparts > 0 && partition_bound_has_default(boundinfo))
+		{
+			with = boundinfo->default_index;
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("partition \"%s\" conflicts with existing default partition \"%s\"",
+							relname, get_rel_name(partdesc->oids[with])),
+					 parser_errposition(pstate, spec->location)));
+		}
+
+		return;
+	}
+
 	switch (key->strategy)
 	{
 		case PARTITION_STRATEGY_LIST:
@@ -719,13 +739,13 @@ check_new_partition_bound(char *relname, Relation parent,
 
 				if (partdesc->nparts > 0)
 				{
-					PartitionBoundInfo boundinfo = partdesc->boundinfo;
 					ListCell   *cell;
 
 					Assert(boundinfo &&
 						   boundinfo->strategy == PARTITION_STRATEGY_LIST &&
 						   (boundinfo->ndatums > 0 ||
-							partition_bound_accepts_nulls(boundinfo)));
+							partition_bound_accepts_nulls(boundinfo) ||
+							partition_bound_has_default(boundinfo)));
 
 					foreach(cell, spec->listdatums)
 					{
@@ -859,6 +879,144 @@ check_new_partition_bound(char *relname, Relation parent,
 						relname, get_rel_name(partdesc->oids[with])),
 				 parser_errposition(pstate, spec->location)));
 	}
+
+	/*
+	 * If the default partition exists, its partition constraints will change
+	 * after the addition of this new partition such that it won't allow any
+	 * row that qualifies for this new partition. So, check if the existing
+	 * data in the default partition satisfies this *would be* default
+	 * partition constraint. In case the new partition bound being checked
+	 * itself is a DEFAULT bound, this check shouldn't be triggered as there
+	 * won't already exists the default partition in such a case.
+	 */
+	if (boundinfo && partition_bound_has_default(boundinfo))
+		check_default_allows_bound(parent, spec);
+}
+
+/*
+ * check_default_allows_bound
+ *
+ * This function checks if there exists a row in the default partition that
+ * fits in the new partition and throws an error if it finds one.
+ */
+static void
+check_default_allows_bound(Relation parent, PartitionBoundSpec *new_spec)
+{
+	Relation	default_rel;
+	List	   *new_part_constraints = NIL;
+	List	   *all_parts;
+	ListCell   *lc;
+	PartitionDescData *part_desc = RelationGetPartitionDesc(parent);
+
+	/* 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 = (List *) eval_const_expressions(NULL,
+														   (Node *) new_part_constraints);
+	new_part_constraints =
+		(List *) canonicalize_qual((Expr *) new_part_constraints);
+	new_part_constraints = list_make1(make_ands_explicit(new_part_constraints));
+
+	/*
+	 * Generate the constraint and default execution states.
+	 *
+	 * The default partition must be already having an AccessExclusiveLock.
+	 */
+	default_rel = heap_open(get_default_partition_oid(RelationGetRelid(parent)),
+							NoLock);
+
+	if (default_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		all_parts = find_all_inheritors(RelationGetRelid(default_rel),
+										AccessExclusiveLock, NULL);
+	else
+		all_parts = list_make1_oid(RelationGetRelid(default_rel));
+
+	foreach(lc, all_parts)
+	{
+		Oid			part_relid = lfirst_oid(lc);
+		Relation	part_rel;
+		Expr	   *constr;
+		Expr	   *partition_constraint;
+		EState	   *estate;
+		HeapTuple	tuple;
+		ExprState  *partqualstate = NULL;
+		Snapshot	snapshot;
+		TupleDesc	tupdesc;
+		ExprContext *econtext;
+		HeapScanDesc scan;
+		MemoryContext oldCxt;
+		TupleTableSlot *tupslot;
+
+		/* Lock already taken above. */
+		if (part_relid != RelationGetRelid(default_rel))
+			part_rel = heap_open(part_relid, NoLock);
+		else
+			part_rel = default_rel;
+
+		/*
+		 * Only RELKIND_RELATION relations (i.e. leaf partitions) need to be
+		 * scanned.
+		 */
+		if (part_rel->rd_rel->relkind != RELKIND_RELATION)
+		{
+			if (part_rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+				ereport(WARNING,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("skipped scanning foreign table \"%s\" which is a partition of default partition \"%s\"",
+								RelationGetRelationName(part_rel),
+								RelationGetRelationName(default_rel))));
+
+			heap_close(part_rel, NoLock);
+			continue;
+		}
+
+		tupdesc = CreateTupleDescCopy(RelationGetDescr(part_rel));
+		constr = linitial(new_part_constraints);
+		partition_constraint = (Expr *) map_partition_varattnos((List *) constr,
+																1, part_rel, parent);
+		estate = CreateExecutorState();
+
+		/* Build expression execution states for partition check quals */
+		partqualstate = ExecPrepareExpr(partition_constraint, estate);
+
+		econtext = GetPerTupleExprContext(estate);
+		snapshot = RegisterSnapshot(GetLatestSnapshot());
+		scan = heap_beginscan(part_rel, snapshot, 0, NULL);
+		tupslot = MakeSingleTupleTableSlot(tupdesc);
+
+		/*
+		 * Switch to per-tuple memory context and reset it for each tuple
+		 * produced, so we don't leak memory.
+		 */
+		oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		{
+			ExecStoreTuple(tuple, tupslot, InvalidBuffer, false);
+			econtext->ecxt_scantuple = tupslot;
+
+			if (ExecCheck(partqualstate, econtext))
+				ereport(ERROR,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("updated partition constraint for default partition \"%s\" would be violated by some row",
+								RelationGetRelationName(default_rel))));
+
+			ResetExprContext(econtext);
+			CHECK_FOR_INTERRUPTS();
+		}
+
+		MemoryContextSwitchTo(oldCxt);
+		heap_endscan(scan);
+		UnregisterSnapshot(snapshot);
+		ExecDropSingleTupleTableSlot(tupslot);
+		FreeExecutorState(estate);
+		/* keep our lock until commit */
+		heap_close(part_rel, NoLock);
+	}
 }
 
 /*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index fc8a6f6..b875dbe 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -785,15 +785,28 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 							RelationGetRelationName(parent))));
 
 		/*
-		 * A table cannot be created as a partition of a parent already having
-		 * a default partition.
+		 * The partition constraint of the default partition depends on the
+		 * partition bounds of every other partition. It is possible that
+		 * other backend might be about to execute a query on the default
+		 * partition table and the query relies on previously cached default
+		 * partition constraints, which won't be correct after removal of a
+		 * partition. We must therefore take a table lock strong enough to
+		 * prevent all queries on the default partition from proceeding until
+		 * we commit and send out a shared-cache-inval notice that will make
+		 * them update their index lists.
+		 *
+		 * Order of locking: The relation being added won't be visible to
+		 * other backends until it is committed, hence here in
+		 * DefineRelation() the order of locking the default partition and the
+		 * relation being added does not matter. But at all other places we
+		 * need to lock the default relation before we lock the relation being
+		 * added or removed i.e. we should take the lock in same order at all
+		 * the places such that lock parent, lock default partition and then
+		 * lock the partition so as to avoid a deadlock.
 		 */
 		defaultPartOid = get_default_partition_oid(RelationGetRelid(parent));
 		if (OidIsValid(defaultPartOid))
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-					 errmsg("cannot add a new partition to table \"%s\" having a default partition",
-							RelationGetRelationName(parent))));
+			LockRelationOid(defaultPartOid, AccessExclusiveLock);
 
 		/* Tranform the bound values */
 		pstate = make_parsestate(NULL);
@@ -13610,13 +13623,13 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	Oid			defaultPartOid;
 	const char *trigger_name;
 
-	/* A partition cannot be attached if there exists a default partition */
+	/*
+	 * We must lock the default partition, for the same reasons explained in
+	 * DefineRelation().
+	 */
 	defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
 	if (OidIsValid(defaultPartOid))
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-				 errmsg("cannot attach a new partition to table \"%s\" having a default partition",
-						RelationGetRelationName(rel))));
+		LockRelationOid(defaultPartOid, AccessExclusiveLock);
 
 	attachRel = heap_openrv(cmd->name, AccessExclusiveLock);
 
@@ -13827,8 +13840,8 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 	Oid			defaultPartOid;
 
 	/*
-	 * We must also lock the default partition, for the same reasons explained
-	 * in heap_drop_with_catalog().
+	 * We must lock the default partition, for the same reasons explained in
+	 * DefineRelation().
 	 */
 	defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
 	if (OidIsValid(defaultPartOid))
@@ -13875,7 +13888,7 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 
 		/*
 		 * We must invalidate default partition's relcache, for the same
-		 * reasons explained in heap_drop_with_catalog().
+		 * reasons explained in StorePartitionBound().
 		 */
 		CacheInvalidateRelcacheByRelid(defaultPartOid);
 	}
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 0acf7ef..938fe28 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3277,7 +3277,7 @@ ERROR:  partition "fail_part" would overlap partition "part_1"
 CREATE TABLE def_part PARTITION OF list_parted DEFAULT;
 CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT;
-ERROR:  cannot attach a new partition to table "list_parted" having a default partition
+ERROR:  partition "fail_def_part" conflicts with existing default partition "def_part"
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
 	a int,
@@ -3291,14 +3291,14 @@ ERROR:  partition constraint is violated by some row
 -- should be ok after deleting the bad row
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
--- check partition cannot be attached if default exists
+-- check partition cannot be attached if default has some row for its values
 CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 INSERT INTO list_parted2_def VALUES (11, 'z');
 CREATE TABLE part_3 (LIKE list_parted2);
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
-ERROR:  cannot attach a new partition to table "list_parted2" having a default partition
--- should be ok after detaching a default partition
-ALTER TABLE list_parted2 DETACH PARTITION list_parted2_def;
+ERROR:  updated partition constraint for default partition "list_parted2_def" would be violated by some row
+-- should be ok after deleting the bad row
+DELETE FROM list_parted2_def WHERE a = 11;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
@@ -3361,6 +3361,18 @@ ALTER TABLE part_5 DROP CONSTRAINT check_a;
 ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT NULL;
 ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
 INFO:  partition constraint for table "part_5" is implied by existing constraints
+-- check that leaf partitions of default partition are scanned when
+-- attaching a partitioned table.
+ALTER TABLE part_5 DROP CONSTRAINT check_a;
+CREATE TABLE part5_def PARTITION OF part_5 DEFAULT PARTITION BY LIST(a);
+CREATE TABLE part5_def_p1 PARTITION OF part5_def FOR VALUES IN (5);
+INSERT INTO part5_def_p1 VALUES (5, 'y');
+CREATE TABLE part5_p1 (LIKE part_5);
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+ERROR:  updated partition constraint for default partition "part5_def" would be violated by some row
+-- should be ok after deleting the bad row
+DELETE FROM part5_def_p1 WHERE b = 'y';
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
 -- check that the table being attached is not already a partition
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 ERROR:  "part_2" is already a partition
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index cab4d6d..c8e0bf4 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -449,6 +449,7 @@ CREATE TABLE list_parted (
 CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
 CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
 CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
 ERROR:  syntax error at or near "int"
 LINE 1: ... fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
@@ -457,6 +458,8 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
 ERROR:  syntax error at or near "::"
 LINE 1: ...fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
                                                                 ^
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
+ERROR:  partition "fail_default_part" conflicts with existing default partition "part_default"
 -- syntax does not allow empty list of values for list partitions
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
 ERROR:  syntax error at or near ")"
@@ -467,13 +470,6 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
 ERROR:  invalid bound specification for a list partition
 LINE 1: ...BLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) T...
                                                              ^
--- check default partition cannot be created more than once
-CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
-CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
-ERROR:  cannot add a new partition to table "list_parted" having a default partition
--- check that no partition can be created after default partition
-CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (3);
-ERROR:  cannot add a new partition to table "list_parted" having a default partition
 -- specified literal can't be cast to the partition column data type
 CREATE TABLE bools (
 	a bool
@@ -575,10 +571,15 @@ CREATE TABLE list_parted2 (
 ) PARTITION BY LIST (a);
 CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
 CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
 ERROR:  partition "fail_part" would overlap partition "part_null_z"
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
 ERROR:  partition "fail_part" would overlap partition "part_ab"
+-- check default partition overlap
+INSERT INTO list_parted2 VALUES('X');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y');
+ERROR:  updated partition constraint for default partition "list_parted2_def" would be violated by some row
 CREATE TABLE range_parted2 (
 	a int
 ) PARTITION BY RANGE (a);
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 4ff84a0..c336a78 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -202,6 +202,7 @@ create table list_parted (
 create table part_aa_bb partition of list_parted FOR VALUES IN ('aa', 'bb');
 create table part_cc_dd partition of list_parted FOR VALUES IN ('cc', 'dd');
 create table part_null partition of list_parted FOR VALUES IN (null);
+create table part_default partition of list_parted default;
 -- fail
 insert into part_aa_bb values ('cc', 1);
 ERROR:  new row for relation "part_aa_bb" violates partition constraint
@@ -212,16 +213,6 @@ DETAIL:  Failing row contains (AAa, 1).
 insert into part_aa_bb values (null);
 ERROR:  new row for relation "part_aa_bb" violates partition constraint
 DETAIL:  Failing row contains (null, null).
--- ok
-insert into part_cc_dd values ('cC', 1);
-insert into part_null values (null, 0);
--- check in case of multi-level partitioned table
-create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b);
-create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
-create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
--- test default partition
-create table part_default partition of list_parted default;
--- fail
 insert into part_default values ('aa', 2);
 ERROR:  new row for relation "part_default" violates partition constraint
 DETAIL:  Failing row contains (aa, 2).
@@ -229,7 +220,13 @@ insert into part_default values (null, 2);
 ERROR:  new row for relation "part_default" violates partition constraint
 DETAIL:  Failing row contains (null, 2).
 -- ok
+insert into part_cc_dd values ('cC', 1);
+insert into part_null values (null, 0);
 insert into part_default values ('Zz', 2);
+-- check in case of multi-level partitioned table
+create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b);
+create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
+create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
 -- check in case of multi-level default partitioned table
 drop table part_default;
 create table part_default partition of list_parted default partition by range(b);
@@ -255,20 +252,6 @@ insert into part_ee_ff2 values ('ff', 11);
 insert into part_default_p1 values ('cd', 25);
 insert into part_default_p2 values ('de', 35);
 insert into list_parted values ('ab', 21);
--- drop default, as we need to add some more partitions to test tuple routing
-select tableoid::regclass, * from list_parted;
-    tableoid     | a  | b  
------------------+----+----
- part_cc_dd      | cC |  1
- part_null       |    |  0
- part_ee_ff1     | ff |  1
- part_ee_ff2     | ff | 11
- part_default_p1 | cd | 25
- part_default_p1 | ab | 21
- part_default_p2 | de | 35
-(7 rows)
-
-drop table part_default;
 -- Check tuple routing for partitioned tables
 -- fail
 insert into range_parted values ('a', 0);
@@ -313,17 +296,20 @@ DETAIL:  Partition key of the failing row contains (b) = (0).
 insert into list_parted values ('EE', 1);
 insert into part_ee_ff values ('EE', 10);
 select tableoid::regclass, * from list_parted;
-  tableoid   | a  | b  
--------------+----+----
- part_aa_bb  | aA |   
- part_cc_dd  | cC |  1
- part_null   |    |  0
- part_null   |    |  1
- part_ee_ff1 | ff |  1
- part_ee_ff1 | EE |  1
- part_ee_ff2 | ff | 11
- part_ee_ff2 | EE | 10
-(8 rows)
+    tableoid     | a  | b  
+-----------------+----+----
+ part_aa_bb      | aA |   
+ part_cc_dd      | cC |  1
+ part_null       |    |  0
+ part_null       |    |  1
+ part_ee_ff1     | ff |  1
+ part_ee_ff1     | EE |  1
+ part_ee_ff2     | ff | 11
+ part_ee_ff2     | EE | 10
+ part_default_p1 | cd | 25
+ part_default_p1 | ab | 21
+ part_default_p2 | de | 35
+(11 rows)
 
 -- some more tests to exercise tuple-routing with multi-level partitioning
 create table part_gg partition of list_parted for values in ('gg') partition by range (b);
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
index 53fe3d4..f0e811f 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -255,22 +255,28 @@ NOTICE:  3
 -- Check that addition or removal of any partition is correctly dealt with by
 -- default partition table when it is being used in cached plan.
 create table list_parted (a int) partition by list(a);
-create table list_part_null partition of list_parted for values in (null);
-create table list_part_1 partition of list_parted for values in (1);
 create table list_part_def partition of list_parted default;
 prepare pstmt_def_insert (int) as insert into list_part_def values($1);
--- should fail
 execute pstmt_def_insert(null);
-ERROR:  new row for relation "list_part_def" violates partition constraint
-DETAIL:  Failing row contains (null).
+execute pstmt_def_insert(1);
+delete from list_parted where a=1;
+create table list_part_1 partition of list_parted for values in (1, 2);
+-- should fail
 execute pstmt_def_insert(1);
 ERROR:  new row for relation "list_part_def" violates partition constraint
 DETAIL:  Failing row contains (1).
-alter table list_parted detach partition list_part_null;
--- should be ok
+delete from list_parted where a is null;
+create table list_part_null (like list_parted);
+alter table list_parted attach partition list_part_null for values in (null);
+-- should fail
 execute pstmt_def_insert(null);
+ERROR:  new row for relation "list_part_def" violates partition constraint
+DETAIL:  Failing row contains (null).
 drop table list_part_1;
 -- should be ok
 execute pstmt_def_insert(1);
+alter table list_parted detach partition list_part_null;
+-- should be ok
+execute pstmt_def_insert(null);
 drop table list_parted;
 deallocate pstmt_def_insert;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 2346c1f..493acaf 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2115,13 +2115,13 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 
--- check partition cannot be attached if default exists
+-- check partition cannot be attached if default has some row for its values
 CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 INSERT INTO list_parted2_def VALUES (11, 'z');
 CREATE TABLE part_3 (LIKE list_parted2);
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
--- should be ok after detaching a default partition
-ALTER TABLE list_parted2 DETACH PARTITION list_parted2_def;
+-- should be ok after deleting the bad row
+DELETE FROM list_parted2_def WHERE a = 11;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
 
 -- adding constraints that describe the desired partition constraint
@@ -2191,6 +2191,18 @@ ALTER TABLE part_5 DROP CONSTRAINT check_a;
 ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT NULL;
 ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
 
+-- check that leaf partitions of default partition are scanned when
+-- attaching a partitioned table.
+ALTER TABLE part_5 DROP CONSTRAINT check_a;
+CREATE TABLE part5_def PARTITION OF part_5 DEFAULT PARTITION BY LIST(a);
+CREATE TABLE part5_def_p1 PARTITION OF part5_def FOR VALUES IN (5);
+INSERT INTO part5_def_p1 VALUES (5, 'y');
+CREATE TABLE part5_p1 (LIKE part_5);
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+-- should be ok after deleting the bad row
+DELETE FROM part5_def_p1 WHERE b = 'y';
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+
 -- check that the table being attached is not already a partition
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 650c1f5..f44c0e0 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -439,20 +439,16 @@ CREATE TABLE list_parted (
 CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
 CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
 CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
 
 -- syntax does not allow empty list of values for list partitions
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
 -- trying to specify range for list partitioned table
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
 
--- check default partition cannot be created more than once
-CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
-CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
--- check that no partition can be created after default partition
-CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (3);
-
 -- specified literal can't be cast to the partition column data type
 CREATE TABLE bools (
 	a bool
@@ -537,9 +533,13 @@ CREATE TABLE list_parted2 (
 ) PARTITION BY LIST (a);
 CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
 CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+-- check default partition overlap
+INSERT INTO list_parted2 VALUES('X');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y');
 
 CREATE TABLE range_parted2 (
 	a int
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index f2131a2..242cd8a 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -118,27 +118,23 @@ create table list_parted (
 create table part_aa_bb partition of list_parted FOR VALUES IN ('aa', 'bb');
 create table part_cc_dd partition of list_parted FOR VALUES IN ('cc', 'dd');
 create table part_null partition of list_parted FOR VALUES IN (null);
+create table part_default partition of list_parted default;
 
 -- fail
 insert into part_aa_bb values ('cc', 1);
 insert into part_aa_bb values ('AAa', 1);
 insert into part_aa_bb values (null);
+insert into part_default values ('aa', 2);
+insert into part_default values (null, 2);
 -- ok
 insert into part_cc_dd values ('cC', 1);
 insert into part_null values (null, 0);
+insert into part_default values ('Zz', 2);
 
 -- check in case of multi-level partitioned table
 create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b);
 create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
 create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
-
--- test default partition
-create table part_default partition of list_parted default;
--- fail
-insert into part_default values ('aa', 2);
-insert into part_default values (null, 2);
--- ok
-insert into part_default values ('Zz', 2);
 -- check in case of multi-level default partitioned table
 drop table part_default;
 create table part_default partition of list_parted default partition by range(b);
@@ -157,9 +153,6 @@ insert into part_ee_ff2 values ('ff', 11);
 insert into part_default_p1 values ('cd', 25);
 insert into part_default_p2 values ('de', 35);
 insert into list_parted values ('ab', 21);
--- drop default, as we need to add some more partitions to test tuple routing
-select tableoid::regclass, * from list_parted;
-drop table part_default;
 
 -- Check tuple routing for partitioned tables
 
diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql
index 89ddf3f..e8814e1 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -160,18 +160,24 @@ select cachebug();
 -- Check that addition or removal of any partition is correctly dealt with by
 -- default partition table when it is being used in cached plan.
 create table list_parted (a int) partition by list(a);
-create table list_part_null partition of list_parted for values in (null);
-create table list_part_1 partition of list_parted for values in (1);
 create table list_part_def partition of list_parted default;
 prepare pstmt_def_insert (int) as insert into list_part_def values($1);
--- should fail
 execute pstmt_def_insert(null);
 execute pstmt_def_insert(1);
-alter table list_parted detach partition list_part_null;
--- should be ok
+delete from list_parted where a=1;
+create table list_part_1 partition of list_parted for values in (1, 2);
+-- should fail
+execute pstmt_def_insert(1);
+delete from list_parted where a is null;
+create table list_part_null (like list_parted);
+alter table list_parted attach partition list_part_null for values in (null);
+-- should fail
 execute pstmt_def_insert(null);
 drop table list_part_1;
 -- should be ok
 execute pstmt_def_insert(1);
+alter table list_parted detach partition list_part_null;
+-- should be ok
+execute pstmt_def_insert(null);
 drop table list_parted;
 deallocate pstmt_def_insert;
-- 
1.9.1

0006-Refactor-default-partitioning-to-re-use-code.patch0000664000175000017500000004152113134125753022417 0ustar  jeevanjeevanFrom 6d05f0521c8eff6296d027641d99ca4ad2eb9ac4 Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Thu, 20 Jul 2017 18:37:03 +0530
Subject: [PATCH 6/8] Refactor default partitioning to re-use code

This patch uses the new functions created in the first patch in this
series.

Ashutosh Bapat, revised by me.
---
 src/backend/catalog/partition.c           | 89 ++++++++++++++++++-------------
 src/backend/commands/tablecmds.c          | 69 ++++++++++++++++++++----
 src/include/catalog/partition.h           |  3 ++
 src/include/commands/tablecmds.h          |  4 ++
 src/test/regress/expected/alter_table.out |  8 ++-
 src/test/regress/sql/alter_table.sql      |  3 ++
 6 files changed, 126 insertions(+), 50 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 0b747eb..cad154c 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -29,6 +29,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_type.h"
+#include "commands/tablecmds.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -153,8 +154,6 @@ static int32 partition_bound_cmp(PartitionKey key,
 static int partition_bound_bsearch(PartitionKey key,
 						PartitionBoundInfo boundinfo,
 						void *probe, bool probe_is_bound, bool *is_equal);
-static void check_default_allows_bound(Relation parent,
-						   PartitionBoundSpec *new_spec);
 
 /*
  * RelationBuildPartitionDesc
@@ -879,56 +878,45 @@ check_new_partition_bound(char *relname, Relation parent,
 						relname, get_rel_name(partdesc->oids[with])),
 				 parser_errposition(pstate, spec->location)));
 	}
-
-	/*
-	 * If the default partition exists, its partition constraints will change
-	 * after the addition of this new partition such that it won't allow any
-	 * row that qualifies for this new partition. So, check if the existing
-	 * data in the default partition satisfies this *would be* default
-	 * partition constraint. In case the new partition bound being checked
-	 * itself is a DEFAULT bound, this check shouldn't be triggered as there
-	 * won't already exists the default partition in such a case.
-	 */
-	if (boundinfo && partition_bound_has_default(boundinfo))
-		check_default_allows_bound(parent, spec);
 }
 
 /*
  * check_default_allows_bound
  *
  * This function checks if there exists a row in the default partition that
- * fits in the new partition and throws an error if it finds one.
+ * fits in the new partition being added and throws an error if it finds one.
  */
-static void
-check_default_allows_bound(Relation parent, PartitionBoundSpec *new_spec)
+void
+check_default_allows_bound(Relation parent, Relation default_rel,
+						   PartitionBoundSpec *new_spec)
 {
-	Relation	default_rel;
-	List	   *new_part_constraints = NIL;
+	List	   *new_part_constraints;
+	List	   *def_part_constraints;
 	List	   *all_parts;
 	ListCell   *lc;
-	PartitionDescData *part_desc = RelationGetPartitionDesc(parent);
 
 	/* 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 = (List *) eval_const_expressions(NULL,
-														   (Node *) new_part_constraints);
-	new_part_constraints =
-		(List *) canonicalize_qual((Expr *) new_part_constraints);
-	new_part_constraints = list_make1(make_ands_explicit(new_part_constraints));
+	def_part_constraints =
+		get_default_part_validation_constraint(new_part_constraints);
 
 	/*
-	 * Generate the constraint and default execution states.
-	 *
-	 * The default partition must be already having an AccessExclusiveLock.
+	 * If the existing constraints on the default partition imply that it will
+	 * not contain any row that would belong to the new partition, we can
+	 * avoid scanning the default partition.
 	 */
-	default_rel = heap_open(get_default_partition_oid(RelationGetRelid(parent)),
-							NoLock);
+	if (canSkipPartConstraintValidation(default_rel, def_part_constraints,
+										RelationGetPartitionKey(parent)))
+	{
+		ereport(INFO,
+				(errmsg("partition constraint for table \"%s\" is implied by existing constraints",
+						RelationGetRelationName(default_rel))));
+		return;
+	}
 
+	/* Bad luck, scan the default partition and its subpartitions, if any. */
 	if (default_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		all_parts = find_all_inheritors(RelationGetRelid(default_rel),
 										AccessExclusiveLock, NULL);
@@ -970,12 +958,14 @@ check_default_allows_bound(Relation parent, PartitionBoundSpec *new_spec)
 								RelationGetRelationName(part_rel),
 								RelationGetRelationName(default_rel))));
 
-			heap_close(part_rel, NoLock);
+			if (RelationGetRelid(default_rel) != RelationGetRelid(part_rel))
+				heap_close(part_rel, NoLock);
+
 			continue;
 		}
 
 		tupdesc = CreateTupleDescCopy(RelationGetDescr(part_rel));
-		constr = linitial(new_part_constraints);
+		constr = linitial(def_part_constraints);
 		partition_constraint = (Expr *) map_partition_varattnos((List *) constr,
 																1, part_rel, parent);
 		estate = CreateExecutorState();
@@ -999,7 +989,7 @@ check_default_allows_bound(Relation parent, PartitionBoundSpec *new_spec)
 			ExecStoreTuple(tuple, tupslot, InvalidBuffer, false);
 			econtext->ecxt_scantuple = tupslot;
 
-			if (ExecCheck(partqualstate, econtext))
+			if (!ExecCheck(partqualstate, econtext))
 				ereport(ERROR,
 						(errcode(ERRCODE_CHECK_VIOLATION),
 						 errmsg("updated partition constraint for default partition \"%s\" would be violated by some row",
@@ -1014,8 +1004,9 @@ check_default_allows_bound(Relation parent, PartitionBoundSpec *new_spec)
 		UnregisterSnapshot(snapshot);
 		ExecDropSingleTupleTableSlot(tupslot);
 		FreeExecutorState(estate);
-		/* keep our lock until commit */
-		heap_close(part_rel, NoLock);
+
+		if (RelationGetRelid(default_rel) != RelationGetRelid(part_rel))
+			heap_close(part_rel, NoLock);	/* keep the lock until commit */
 	}
 }
 
@@ -2661,3 +2652,25 @@ update_default_partition_oid(Oid parentId, Oid defaultPartId)
 	heap_freetuple(tuple);
 	heap_close(pg_partitioned_table, RowExclusiveLock);
 }
+
+/*
+ * get_default_part_validation_constraint
+ *
+ * Given partition constraints, this function returns *would be* default
+ * partition constraint.
+ */
+List *
+get_default_part_validation_constraint(List *new_part_constraints)
+{
+	Expr	   *defPartConstraint;
+
+	defPartConstraint = make_ands_explicit(new_part_constraints);
+	defPartConstraint = makeBoolExpr(NOT_EXPR,
+									 list_make1(defPartConstraint),
+									 -1);
+	defPartConstraint = (Expr *) eval_const_expressions(NULL,
+														(Node *) defPartConstraint);
+	defPartConstraint = canonicalize_qual(defPartConstraint);
+
+	return list_make1(defPartConstraint);
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index b875dbe..5449790 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -168,6 +168,8 @@ typedef struct AlteredTableInfo
 	bool		chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
 	char		newrelpersistence;	/* if above is true */
 	Expr	   *partition_constraint;	/* for attach partition validation */
+	/* true, if is a default partition or a child of default partition */
+	bool		is_default_partition;
 	/* Objects to rebuild after completing ALTER TYPE operations */
 	List	   *changedConstraintOids;	/* OIDs of constraints to rebuild */
 	List	   *changedConstraintDefs;	/* string definitions of same */
@@ -769,7 +771,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		ParseState *pstate;
 		Oid			parentId = linitial_oid(inheritOids),
 					defaultPartOid;
-		Relation	parent;
+		Relation	parent,
+					defaultRel;
 
 		/* Already have strong enough lock on the parent */
 		parent = heap_open(parentId, NoLock);
@@ -806,7 +809,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		 */
 		defaultPartOid = get_default_partition_oid(RelationGetRelid(parent));
 		if (OidIsValid(defaultPartOid))
-			LockRelationOid(defaultPartOid, AccessExclusiveLock);
+			defaultRel = heap_open(defaultPartOid, AccessExclusiveLock);
 
 		/* Tranform the bound values */
 		pstate = make_parsestate(NULL);
@@ -821,6 +824,20 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		 */
 		check_new_partition_bound(relname, parent, bound);
 
+		/*
+		 * If the default partition exists, its partition constraints will
+		 * change after the addition of this new partition such that it won't
+		 * allow any row that qualifies for this new partition. So, check that
+		 * the existing data in the default partition satisfies the constraint
+		 * as it will exist after adding this partition.
+		 */
+		if (OidIsValid(defaultPartOid))
+		{
+			check_default_allows_bound(parent, defaultRel, bound);
+			/* Keep the lock until commit. */
+			heap_close(defaultRel, NoLock);
+		}
+
 		/* Update the pg_class entry. */
 		StorePartitionBound(rel, parent, bound);
 
@@ -4611,9 +4628,16 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 			}
 
 			if (partqualstate && !ExecCheck(partqualstate, econtext))
-				ereport(ERROR,
-						(errcode(ERRCODE_CHECK_VIOLATION),
-						 errmsg("partition constraint is violated by some row")));
+			{
+				if (tab->is_default_partition)
+					ereport(ERROR,
+							(errcode(ERRCODE_CHECK_VIOLATION),
+							 errmsg("updated partition constraint for default partition would be violated by some row")));
+				else
+					ereport(ERROR,
+							(errcode(ERRCODE_CHECK_VIOLATION),
+							 errmsg("partition constraint is violated by some row")));
+			}
 
 			/* Write the tuple out to the new relation */
 			if (newrel)
@@ -13450,7 +13474,7 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
  *
  * The function returns true if it's safe to skip the scan, false otherwise.
  */
-static bool
+bool
 canSkipPartConstraintValidation(Relation scanRel, List *partConstraint,
 								PartitionKey key)
 {
@@ -13537,7 +13561,8 @@ canSkipPartConstraintValidation(Relation scanRel, List *partConstraint,
  */
 static void
 validatePartitionConstraints(List **wqueue, Relation scanRel,
-							 List *partConstraint, Relation rel)
+							 List *partConstraint, Relation rel,
+							 bool scanrel_is_default)
 {
 	PartitionKey key = RelationGetPartitionKey(rel);
 	List	   *all_parts;
@@ -13596,6 +13621,7 @@ validatePartitionConstraints(List **wqueue, Relation scanRel,
 		tab->partition_constraint = (Expr *)
 			map_partition_varattnos((List *) constr, 1,
 									part_rel, rel);
+		tab->is_default_partition = scanrel_is_default;
 		/* keep our lock until commit */
 		if (part_rel != scanRel)
 			heap_close(part_rel, NoLock);
@@ -13621,6 +13647,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	TupleDesc	tupleDesc;
 	ObjectAddress address;
 	Oid			defaultPartOid;
+	List	   *partBoundConstraint;
 	const char *trigger_name;
 
 	/*
@@ -13796,8 +13823,8 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	 * If the parent itself is a partition, make sure to include its
 	 * constraint as well.
 	 */
-	partConstraint = list_concat(get_qual_from_partbound(attachRel, rel,
-														 cmd->bound),
+	partBoundConstraint = get_qual_from_partbound(attachRel, rel, cmd->bound);
+	partConstraint = list_concat(partBoundConstraint,
 								 RelationGetPartitionQual(rel));
 
 	/* Skip validation if there are no constraints to validate. */
@@ -13809,7 +13836,29 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 		partConstraint = list_make1(make_ands_explicit(partConstraint));
 
 		/* Validate partition constraints against the table being attached. */
-		validatePartitionConstraints(wqueue, attachRel, partConstraint, rel);
+		validatePartitionConstraints(wqueue, attachRel, partConstraint, rel, false);
+
+		/*
+		 * Check whether default partition has a row that would fit the
+		 * partition being attached by negating the partition constraint
+		 * derived from the bounds(the partition constraint never evaluates to
+		 * NULL, so negating it like this is safe).
+		 */
+		defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
+		if (OidIsValid(defaultPartOid))
+		{
+			Relation	default_rel;
+			List	   *defPartConstraint;
+
+			/* We already have taken a lock on default partition. */
+			default_rel = heap_open(defaultPartOid, NoLock);
+			defPartConstraint = get_default_part_validation_constraint(partBoundConstraint);
+			validatePartitionConstraints(wqueue, default_rel, defPartConstraint,
+										 rel, true);
+
+			/* keep our lock until commit. */
+			heap_close(default_rel, NoLock);
+		}
 	}
 
 	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 7800c96..c3153d7 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -100,5 +100,8 @@ extern int get_partition_for_tuple(PartitionDispatch *pd,
 						TupleTableSlot **failed_slot);
 extern Oid	get_default_partition_oid(Oid parentId);
 extern void update_default_partition_oid(Oid parentId, Oid defaultPartId);
+extern void check_default_allows_bound(Relation parent, Relation defaultRel,
+						   PartitionBoundSpec *new_spec);
+extern List *get_default_part_validation_constraint(List *new_part_constaints);
 
 #endif							/* PARTITION_H */
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index abd31b6..bb40d88 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -18,6 +18,7 @@
 #include "catalog/dependency.h"
 #include "catalog/objectaddress.h"
 #include "nodes/parsenodes.h"
+#include "catalog/partition.h"
 #include "storage/lock.h"
 #include "utils/relcache.h"
 
@@ -87,4 +88,7 @@ extern void RangeVarCallbackOwnsTable(const RangeVar *relation,
 
 extern void RangeVarCallbackOwnsRelation(const RangeVar *relation,
 							 Oid relId, Oid oldRelId, void *noCatalogs);
+extern bool canSkipPartConstraintValidation(Relation scanRel, List *partConstraint,
+								PartitionKey key);
+
 #endif							/* TABLECMDS_H */
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 938fe28..88e4a89 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3296,7 +3296,7 @@ CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 INSERT INTO list_parted2_def VALUES (11, 'z');
 CREATE TABLE part_3 (LIKE list_parted2);
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
-ERROR:  updated partition constraint for default partition "list_parted2_def" would be violated by some row
+ERROR:  updated partition constraint for default partition would be violated by some row
 -- should be ok after deleting the bad row
 DELETE FROM list_parted2_def WHERE a = 11;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
@@ -3315,6 +3315,10 @@ ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
 ALTER TABLE part_3_4 ALTER a SET NOT NULL;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
 INFO:  partition constraint for table "part_3_4" is implied by existing constraints
+-- check if default partition scan skipped
+ALTER TABLE list_parted2_def ADD CONSTRAINT check_a CHECK (a IN (5, 6));
+CREATE TABLE part_7_8 PARTITION OF list_parted2 FOR VALUES IN (7, 8);
+INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
 -- check validation when attaching range partitions
 CREATE TABLE range_parted (
 	a int,
@@ -3369,7 +3373,7 @@ CREATE TABLE part5_def_p1 PARTITION OF part5_def FOR VALUES IN (5);
 INSERT INTO part5_def_p1 VALUES (5, 'y');
 CREATE TABLE part5_p1 (LIKE part_5);
 ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
-ERROR:  updated partition constraint for default partition "part5_def" would be violated by some row
+ERROR:  updated partition constraint for default partition would be violated by some row
 -- should be ok after deleting the bad row
 DELETE FROM part5_def_p1 WHERE b = 'y';
 ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 493acaf..38655ee 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2141,6 +2141,9 @@ ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
 ALTER TABLE part_3_4 ALTER a SET NOT NULL;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
 
+-- check if default partition scan skipped
+ALTER TABLE list_parted2_def ADD CONSTRAINT check_a CHECK (a IN (5, 6));
+CREATE TABLE part_7_8 PARTITION OF list_parted2 FOR VALUES IN (7, 8);
 
 -- check validation when attaching range partitions
 CREATE TABLE range_parted (
-- 
1.9.1

0007-Check-default-partitition-child-relations-predicate-.patch0000664000175000017500000001106013134125753024066 0ustar  jeevanjeevanFrom 72c6f7d4aa10388c60ad92a164e7b8f4181ee77f Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Thu, 20 Jul 2017 18:37:09 +0530
Subject: [PATCH 7/8] Check default partitition child relations predicate
 implication.

Add code to check_default_allows_bound() such that the default
partition children constraints are checked against new partition
constraints for implication and avoid scan of the child of which
existing constraints are implied by new default partition
constraints. Also, added testcase for the same.

Jeevan Ladhe.
---
 src/backend/catalog/partition.c           | 19 +++++++++++++++++++
 src/test/regress/expected/alter_table.out | 12 ++++++++++++
 src/test/regress/sql/alter_table.sql      |  9 +++++++++
 3 files changed, 40 insertions(+)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index cad154c..b31c272 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -941,7 +941,26 @@ check_default_allows_bound(Relation parent, Relation default_rel,
 
 		/* Lock already taken above. */
 		if (part_relid != RelationGetRelid(default_rel))
+		{
 			part_rel = heap_open(part_relid, NoLock);
+
+			/*
+			 * If the partition constraints on default partition child imply
+			 * that it will not contain any row that would belong to the new
+			 * partition, we can avoid scanning the child table.
+			 */
+			if (canSkipPartConstraintValidation(part_rel,
+												def_part_constraints,
+												RelationGetPartitionKey(parent)))
+			{
+				ereport(INFO,
+						(errmsg("partition constraint for table \"%s\" is implied by existing constraints",
+								RelationGetRelationName(part_rel))));
+
+				heap_close(part_rel, NoLock);
+				continue;
+			}
+		}
 		else
 			part_rel = default_rel;
 
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 88e4a89..9a5f6bf 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3319,6 +3319,18 @@ INFO:  partition constraint for table "part_3_4" is implied by existing constrai
 ALTER TABLE list_parted2_def ADD CONSTRAINT check_a CHECK (a IN (5, 6));
 CREATE TABLE part_7_8 PARTITION OF list_parted2 FOR VALUES IN (7, 8);
 INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
+-- check if default partition child relation scan skipped
+DROP TABLE list_parted2_def;
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT PARTITION BY RANGE (a);
+CREATE TABLE list_parted2_def_p1 PARTITION OF list_parted2_def FOR VALUES FROM (21) TO (30);
+CREATE TABLE list_parted2_def_p2 PARTITION OF list_parted2_def FOR VALUES FROM (31) TO (40);
+ALTER TABLE list_parted2_def_p1 ADD CONSTRAINT check_a CHECK (a IN (21, 22));
+ALTER TABLE list_parted2_def_p2 ADD CONSTRAINT check_a CHECK (a IN (31, 32));
+CREATE TABLE part_9 PARTITION OF list_parted2 FOR VALUES IN (9);
+INFO:  partition constraint for table "list_parted2_def_p1" is implied by existing constraints
+INFO:  partition constraint for table "list_parted2_def_p2" is implied by existing constraints
+CREATE TABLE part_31 PARTITION OF list_parted2 FOR VALUES IN (31);
+INFO:  partition constraint for table "list_parted2_def_p1" is implied by existing constraints
 -- check validation when attaching range partitions
 CREATE TABLE range_parted (
 	a int,
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 38655ee..935b24b 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2144,6 +2144,15 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
 -- check if default partition scan skipped
 ALTER TABLE list_parted2_def ADD CONSTRAINT check_a CHECK (a IN (5, 6));
 CREATE TABLE part_7_8 PARTITION OF list_parted2 FOR VALUES IN (7, 8);
+-- check if default partition child relation scan skipped
+DROP TABLE list_parted2_def;
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT PARTITION BY RANGE (a);
+CREATE TABLE list_parted2_def_p1 PARTITION OF list_parted2_def FOR VALUES FROM (21) TO (30);
+CREATE TABLE list_parted2_def_p2 PARTITION OF list_parted2_def FOR VALUES FROM (31) TO (40);
+ALTER TABLE list_parted2_def_p1 ADD CONSTRAINT check_a CHECK (a IN (21, 22));
+ALTER TABLE list_parted2_def_p2 ADD CONSTRAINT check_a CHECK (a IN (31, 32));
+CREATE TABLE part_9 PARTITION OF list_parted2 FOR VALUES IN (9);
+CREATE TABLE part_31 PARTITION OF list_parted2 FOR VALUES IN (31);
 
 -- check validation when attaching range partitions
 CREATE TABLE range_parted (
-- 
1.9.1

0008-Documentation-for-default-partition.patch0000664000175000017500000002122713134125753021026 0ustar  jeevanjeevanFrom 07f423dc575976d24b3480852a0e366a23b8948c Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Thu, 20 Jul 2017 18:37:58 +0530
Subject: [PATCH 8/8] Documentation for default partition.

Jeevan Ladhe
---
 doc/src/sgml/ref/alter_table.sgml  | 46 +++++++++++++++++++++++++++-----------
 doc/src/sgml/ref/create_table.sgml | 30 ++++++++++++++++++++++---
 2 files changed, 60 insertions(+), 16 deletions(-)

diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 6960032..abea3a2 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -34,7 +34,7 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
 ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable> [ OWNED BY <replaceable class="PARAMETER">role_name</replaceable> [, ... ] ]
     SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable> [ NOWAIT ]
 ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
-    ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+    ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> { DEFAULT | FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> }
 ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
     DETACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable>
 
@@ -765,24 +765,33 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
    </varlistentry>
 
    <varlistentry>
-    <term><literal>ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable></literal></term>
+    <term><literal>ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> { DEFAULT | FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> }</literal></term>
     <listitem>
      <para>
       This form attaches an existing table (which might itself be partitioned)
-      as a partition of the target table using the same syntax for
+      as a partition of the target table. The table can be attached as a
+      default partition by using <literal>DEFAULT</literal> or a partition for
+      specific values using <literal>FOR VALUES</literal>.
+     </para>
+
+     <para>
+      A partition using <literal>FOR VALUES</literal> uses same syntax for
       <replaceable class="PARAMETER">partition_bound_spec</replaceable> as
       <xref linkend="sql-createtable">.  The partition bound specification
       must correspond to the partitioning strategy and partition key of the
-      target table.  The table to be attached must have all the same columns
-      as the target table and no more; moreover, the column types must also
-      match.  Also, it must have all the <literal>NOT NULL</literal> and
-      <literal>CHECK</literal> constraints of the target table.  Currently
-      <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and
-      <literal>FOREIGN KEY</literal> constraints are not considered.
-      If any of the <literal>CHECK</literal> constraints of the table being
-      attached is marked <literal>NO INHERIT</literal>, the command will fail;
-      such a constraint must be recreated without the <literal>NO INHERIT</literal>
-      clause.
+      target table.
+     </para>
+
+     <para>
+      The table to be attached must have all the same columns as the target
+      table and no more; moreover, the column types must also match.  Also, it
+      must have all the <literal>NOT NULL</literal> and <literal>CHECK</literal>
+      constraints of the target table.  Currently <literal>UNIQUE</literal>,
+      <literal>PRIMARY KEY</literal>, and <literal>FOREIGN KEY</literal>
+      constraints are not considered. If any of the <literal>CHECK</literal>
+      constraints of the table being attached is marked <literal>NO INHERIT
+      </literal>, the command will fail; such a constraint must be recreated
+      without the <literal>NO INHERIT</literal> `clause.
      </para>
 
      <para>
@@ -798,6 +807,10 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       a list partition that will not accept <literal>NULL</literal> values,
       also add <literal>NOT NULL</literal> constraint to the partition key
       column, unless it's an expression.
+      Also, if there exists a default partition table for the parent table,
+      then the default partition(if it is a regular table) is scanned to check
+      that no existing row in default partition would fit in the partition that
+      is being attached.
      </para>
 
      <para>
@@ -1396,6 +1409,13 @@ ALTER TABLE cities
 </programlisting></para>
 
   <para>
+   Attach a default partition to a partitioned table:
+<programlisting>
+ALTER TABLE cities
+    ATTACH PARTITION cities_partdef DEFAULT;
+</programlisting></para>
+
+  <para>
    Detach a partition from partitioned table:
 <programlisting>
 ALTER TABLE measurement
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index b15c19d..c7d003f 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -49,7 +49,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
   { <replaceable class="PARAMETER">column_name</replaceable> [ WITH OPTIONS ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
     | <replaceable>table_constraint</replaceable> }
     [, ... ]
-) ] FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+) ] { DEFAULT | FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> }
 [ PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
 [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
 [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
@@ -250,11 +250,13 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
    </varlistentry>
 
    <varlistentry id="SQL-CREATETABLE-PARTITION">
-    <term><literal>PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable> FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable></literal></term>
+    <term><literal>PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable> { DEFAULT | FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> }</literal></term>
     <listitem>
      <para>
       Creates the table as a <firstterm>partition</firstterm> of the specified
-      parent table.
+      parent table. The table can be created either as a default partition
+      using <literal>DEFAULT</literal> or a partition for specific values using
+      <literal>FOR VALUES</literal>.
      </para>
 
      <para>
@@ -310,6 +312,21 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
      </para>
 
      <para>
+     If <literal>DEFAULT</literal> is specified the table will be created as a
+     default partition of the parent table. The parent can either be a list or
+     range partitioned table. A partition key value not fitting into any other
+     partition of the given parent will be routed to the default partition.
+     There can be only one default partition for a given parent table.
+     </para>
+
+     <para>
+     If the given parent is already having a default partition then adding a
+     new partition would result in an error if the default partition contains a
+     record that would fit in the new partition being added. This check is not
+     performed if the default partition is a foreign table.
+     </para>
+
+     <para>
       A partition must have the same column names and types as the partitioned
       table to which it belongs.  If the parent is specified <literal>WITH
       OIDS</literal> then all partitions must have OIDs; the parent's OID
@@ -1646,6 +1663,13 @@ CREATE TABLE cities_ab
 CREATE TABLE cities_ab_10000_to_100000
     PARTITION OF cities_ab FOR VALUES FROM (10000) TO (100000);
 </programlisting></para>
+
+  <para>
+   Create a default partition of partitioned table:
+<programlisting>
+CREATE TABLE cities_partdef
+    PARTITION OF cities DEFAULT;
+</programlisting></para>
  </refsect1>
 
  <refsect1 id="SQL-CREATETABLE-compatibility">
-- 
1.9.1

#152Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Jeevan Ladhe (#151)
1 attachment(s)
Re: Adding support for Default partition in partitioning

Hi,

I have rebased the patches on the latest commit.

PFA.

Regards,
Jeevan Ladhe

On Thu, Jul 20, 2017 at 6:47 PM, Jeevan Ladhe <jeevan.ladhe@enterprisedb.com

Show quoted text

wrote:

Hi,

On Thu, Jul 13, 2017 at 1:01 AM, Jeevan Ladhe <
jeevan.ladhe@enterprisedb.com> wrote:

Hi,

I have worked further on V21 patch set, rebased it on latest master
commit,
addressed the comments given by Robert, Ashutosh and others.

The attached tar has a series of 7 patches.
Here is a brief of these 7 patches:

0001:
Refactoring existing ATExecAttachPartition code so that it can be used
for
default partitioning as well

0002:
This patch teaches the partitioning code to handle the NIL returned by
get_qual_for_list().
This is needed because a default partition will not have any constraints
in case
it is the only partition of its parent.

0003:
Support for default partition with the restriction of preventing addition
of any
new partition after default partition.

0004:
Store the default partition OID in pg_partition_table, this will help us
to
retrieve the OID of default relation when we don't have the relation cache
available. This was also suggested by Amit Langote here[1].

0005:
Extend default partitioning support to allow addition of new partitions.

0006:
Extend default partitioning validation code to reuse the refactored code
in
patch 0001.

0007:
This patch introduces code to check if the scanning of default partition
child
can be skipped if it's constraints are proven.

TODO:
Add documentation.

I have added a documentation patch(patch 0008) to the existing set of
patches.
PFA.

Merge default range partitioning patch.

Beena has created a patch on top of my patches here[1].

[1] /messages/by-id/CAOG9ApGEZxSQD-
ZD3icj_CwTmprSGG7sZ_r3k9m4rmcc6ozr%3Dg%40mail.gmail.com

Regards,
Jeevan Ladhe

Attachments:

default_partition_V24.tarapplication/x-tar; name=default_partition_V24.tarDownload
0001-Refactor-ATExecAttachPartition.patch0000664000175000017500000002532613136103112017653 0ustar  jeevanjeevanFrom 5ca1a9af391c2162c81321fa57a641793ffae4ee Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Wed, 26 Jul 2017 04:52:49 -0700
Subject: [PATCH 1/8] Refactor ATExecAttachPartition

Move code to validate the table being attached against the partition
constraints into set of functions. This will be used for validating
the changed default partition constraints when a new partition is
added.

Ashutosh Bapat, revised by me.
---
 src/backend/commands/tablecmds.c | 311 +++++++++++++++++++++------------------
 1 file changed, 165 insertions(+), 146 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index bb00858..7964770 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13412,6 +13412,168 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
 }
 
 /*
+ * canSkipPartConstraintValidation
+ *
+ * Check if we can do away with having to scan the given table to validate
+ * given constraint by proving that the existing constraints of the table imply
+ * the given constraints.  We include the table's check constraints and NOT
+ * NULL constraints in the list of clauses passed to predicate_implied_by().
+ *
+ * The function returns true if it's safe to skip the scan, false otherwise.
+ */
+static bool
+canSkipPartConstraintValidation(Relation scanRel, List *partConstraint,
+								PartitionKey key)
+{
+	TupleDesc	tupleDesc = RelationGetDescr(scanRel);
+	TupleConstr *scanRel_constr = tupleDesc->constr;
+	int			i;
+	List	   *existConstraint = NIL;
+
+	if (scanRel_constr == NULL)
+		return false;
+
+	if (scanRel_constr->has_not_null)
+	{
+		int			natts = scanRel->rd_att->natts;
+
+		for (i = 1; i <= natts; i++)
+		{
+			Form_pg_attribute att = scanRel->rd_att->attrs[i - 1];
+
+			if (att->attnotnull && !att->attisdropped)
+			{
+				NullTest   *ntest = makeNode(NullTest);
+
+				ntest->arg = (Expr *) makeVar(1,
+											  i,
+											  att->atttypid,
+											  att->atttypmod,
+											  att->attcollation,
+											  0);
+				ntest->nulltesttype = IS_NOT_NULL;
+
+				/*
+				 * argisrow=false is correct even for a composite column,
+				 * because attnotnull does not represent a SQL-spec IS NOT
+				 * NULL test in such a case, just IS DISTINCT FROM NULL.
+				 */
+				ntest->argisrow = false;
+				ntest->location = -1;
+				existConstraint = lappend(existConstraint, ntest);
+			}
+		}
+	}
+
+	for (i = 0; i < scanRel_constr->num_check; i++)
+	{
+		Node	   *cexpr;
+
+		/*
+		 * If this constraint hasn't been fully validated yet, we must ignore
+		 * it here.
+		 */
+		if (!scanRel_constr->check[i].ccvalid)
+			continue;
+
+		cexpr = stringToNode(scanRel_constr->check[i].ccbin);
+
+		/*
+		 * Run each expression through const-simplification and
+		 * canonicalization.  It is necessary, because we will be comparing it
+		 * to similarly-processed qual clauses, and may fail to detect valid
+		 * matches without this.
+		 */
+		cexpr = eval_const_expressions(NULL, cexpr);
+		cexpr = (Node *) canonicalize_qual((Expr *) cexpr);
+
+		existConstraint = list_concat(existConstraint,
+									  make_ands_implicit((Expr *) cexpr));
+	}
+
+	existConstraint = list_make1(make_ands_explicit(existConstraint));
+
+	/* And away we go ... */
+	return predicate_implied_by(partConstraint, existConstraint, true);
+}
+
+/*
+ * validatePartitionConstraints
+ *
+ * Check whether all rows in the given table obey the given partition
+ * constraint; if so, it can be attached as a partition.  We do this by
+ * scanning the table (or all of its leaf partitions) row by row, except when
+ * the existing constraints are sufficient to prove that the new partitioning
+ * constraint must already hold.
+ */
+static void
+validatePartitionConstraints(List **wqueue, Relation scanRel,
+							 List *partConstraint, Relation rel)
+{
+	PartitionKey key = RelationGetPartitionKey(rel);
+	List	   *all_parts;
+	ListCell   *lc;
+
+	/* If possible, skip the validation scan. */
+	if (canSkipPartConstraintValidation(scanRel, partConstraint, key))
+	{
+		ereport(INFO,
+				(errmsg("partition constraint for table \"%s\" is implied by existing constraints",
+						RelationGetRelationName(scanRel))));
+		return;
+	}
+
+	/*
+	 * Prepare to scan the default partition (or, if it is itself partitioned,
+	 * all of its leaf partitions).
+	 *
+	 * Take an exclusive lock on the partitions to be checked.
+	 */
+	if (scanRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		all_parts = find_all_inheritors(RelationGetRelid(scanRel),
+										AccessExclusiveLock, NULL);
+	else
+		all_parts = list_make1_oid(RelationGetRelid(scanRel));
+
+	foreach(lc, all_parts)
+	{
+		AlteredTableInfo *tab;
+		Oid			part_relid = lfirst_oid(lc);
+		Relation	part_rel;
+		Expr	   *constr;
+
+		/* Lock already taken */
+		if (part_relid != RelationGetRelid(scanRel))
+			part_rel = heap_open(part_relid, NoLock);
+		else
+			part_rel = scanRel;
+
+		/*
+		 * Skip if it's a partitioned table.  Only RELKIND_RELATION relations
+		 * (ie, leaf partitions) need to be scanned.
+		 */
+		if (part_rel != scanRel &&
+			part_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			heap_close(part_rel, NoLock);
+			continue;
+		}
+
+		/* Grab a work queue entry */
+		tab = ATGetQueueEntry(wqueue, part_rel);
+
+		/* Adjust constraint to match this partition */
+		constr = linitial(partConstraint);
+		tab->partition_constraint = (Expr *)
+			map_partition_varattnos((List *) constr, 1,
+									part_rel, rel);
+		/* keep our lock until commit */
+		if (part_rel != scanRel)
+			heap_close(part_rel, NoLock);
+	}
+}
+
+/*
  * ALTER TABLE <name> ATTACH PARTITION <partition-name> FOR VALUES
  *
  * Return the address of the newly attached partition.
@@ -13422,15 +13584,12 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	Relation	attachRel,
 				catalog;
 	List	   *childrels;
-	TupleConstr *attachRel_constr;
-	List	   *partConstraint,
-			   *existConstraint;
+	List	   *partConstraint;
 	SysScanDesc scan;
 	ScanKeyData skey;
 	AttrNumber	attno;
 	int			natts;
 	TupleDesc	tupleDesc;
-	bool		skip_validate = false;
 	ObjectAddress address;
 	const char *trigger_name;
 
@@ -13602,148 +13761,8 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
 	partConstraint = list_make1(make_ands_explicit(partConstraint));
 
-	/*
-	 * Check if we can do away with having to scan the table being attached to
-	 * validate the partition constraint, by *proving* that the existing
-	 * constraints of the table *imply* the partition predicate.  We include
-	 * the table's check constraints and NOT NULL constraints in the list of
-	 * clauses passed to predicate_implied_by().
-	 *
-	 * There is a case in which we cannot rely on just the result of the
-	 * proof.
-	 */
-	attachRel_constr = tupleDesc->constr;
-	existConstraint = NIL;
-	if (attachRel_constr != NULL)
-	{
-		int			num_check = attachRel_constr->num_check;
-		int			i;
-
-		if (attachRel_constr->has_not_null)
-		{
-			int			natts = attachRel->rd_att->natts;
-
-			for (i = 1; i <= natts; i++)
-			{
-				Form_pg_attribute att = attachRel->rd_att->attrs[i - 1];
-
-				if (att->attnotnull && !att->attisdropped)
-				{
-					NullTest   *ntest = makeNode(NullTest);
-
-					ntest->arg = (Expr *) makeVar(1,
-												  i,
-												  att->atttypid,
-												  att->atttypmod,
-												  att->attcollation,
-												  0);
-					ntest->nulltesttype = IS_NOT_NULL;
-
-					/*
-					 * argisrow=false is correct even for a composite column,
-					 * because attnotnull does not represent a SQL-spec IS NOT
-					 * NULL test in such a case, just IS DISTINCT FROM NULL.
-					 */
-					ntest->argisrow = false;
-					ntest->location = -1;
-					existConstraint = lappend(existConstraint, ntest);
-				}
-			}
-		}
-
-		for (i = 0; i < num_check; i++)
-		{
-			Node	   *cexpr;
-
-			/*
-			 * If this constraint hasn't been fully validated yet, we must
-			 * ignore it here.
-			 */
-			if (!attachRel_constr->check[i].ccvalid)
-				continue;
-
-			cexpr = stringToNode(attachRel_constr->check[i].ccbin);
-
-			/*
-			 * Run each expression through const-simplification and
-			 * canonicalization.  It is necessary, because we will be
-			 * comparing it to similarly-processed qual clauses, and may fail
-			 * to detect valid matches without this.
-			 */
-			cexpr = eval_const_expressions(NULL, cexpr);
-			cexpr = (Node *) canonicalize_qual((Expr *) cexpr);
-
-			existConstraint = list_concat(existConstraint,
-										  make_ands_implicit((Expr *) cexpr));
-		}
-
-		existConstraint = list_make1(make_ands_explicit(existConstraint));
-
-		/* And away we go ... */
-		if (predicate_implied_by(partConstraint, existConstraint, true))
-			skip_validate = true;
-	}
-
-	/* It's safe to skip the validation scan after all */
-	if (skip_validate)
-		ereport(INFO,
-				(errmsg("partition constraint for table \"%s\" is implied by existing constraints",
-						RelationGetRelationName(attachRel))));
-
-	/*
-	 * Set up to have the table be scanned to validate the partition
-	 * constraint (see partConstraint above).  If it's a partitioned table, we
-	 * instead schedule its leaf partitions to be scanned.
-	 */
-	if (!skip_validate)
-	{
-		List	   *all_parts;
-		ListCell   *lc;
-
-		/* Take an exclusive lock on the partitions to be checked */
-		if (attachRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
-			all_parts = find_all_inheritors(RelationGetRelid(attachRel),
-											AccessExclusiveLock, NULL);
-		else
-			all_parts = list_make1_oid(RelationGetRelid(attachRel));
-
-		foreach(lc, all_parts)
-		{
-			AlteredTableInfo *tab;
-			Oid			part_relid = lfirst_oid(lc);
-			Relation	part_rel;
-			Expr	   *constr;
-
-			/* Lock already taken */
-			if (part_relid != RelationGetRelid(attachRel))
-				part_rel = heap_open(part_relid, NoLock);
-			else
-				part_rel = attachRel;
-
-			/*
-			 * Skip if it's a partitioned table.  Only RELKIND_RELATION
-			 * relations (ie, leaf partitions) need to be scanned.
-			 */
-			if (part_rel != attachRel &&
-				part_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
-			{
-				heap_close(part_rel, NoLock);
-				continue;
-			}
-
-			/* Grab a work queue entry */
-			tab = ATGetQueueEntry(wqueue, part_rel);
-
-			/* Adjust constraint to match this partition */
-			constr = linitial(partConstraint);
-			tab->partition_constraint = (Expr *)
-				map_partition_varattnos((List *) constr, 1,
-										part_rel, rel);
-			/* keep our lock until commit */
-			if (part_rel != attachRel)
-				heap_close(part_rel, NoLock);
-		}
-	}
+	/* Validate partition constraints against the table being attached. */
+	validatePartitionConstraints(wqueue, attachRel, partConstraint, rel);
 
 	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
 
-- 
2.7.4

0002-Fix-assumptions-that-get_qual_from_partbound-cannot.patch0000664000175000017500000000511513136103112024202 0ustar  jeevanjeevanFrom a7c3f8b833241bb3e171a4faba121ad670d02f16 Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Wed, 26 Jul 2017 04:55:22 -0700
Subject: [PATCH 2/8] Fix assumptions that get_qual_from_partbound() cannot
 return NIL list.

Current partitioning code assumes that there cannot be any partition
without partition constraints, but in future this assumption might
not hold true.
e.g. if we introduce support for default partition, then default
partition will not have any constraints in case it is the only
partition of its parent.

Jeevan Ladhe.
---
 src/backend/catalog/partition.c  |  5 ++++-
 src/backend/commands/tablecmds.c | 17 +++++++++++------
 2 files changed, 15 insertions(+), 7 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index e20ddce..599fe2f 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -959,7 +959,10 @@ get_partition_qual_relid(Oid relid)
 	if (rel->rd_rel->relispartition)
 	{
 		and_args = generate_partition_qual(rel);
-		if (list_length(and_args) > 1)
+
+		if (!and_args)
+			result = NULL;
+		else if (list_length(and_args) > 1)
 			result = makeBoolExpr(AND_EXPR, and_args, -1);
 		else
 			result = linitial(and_args);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 7964770..f24602d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13756,13 +13756,18 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	partConstraint = list_concat(get_qual_from_partbound(attachRel, rel,
 														 cmd->bound),
 								 RelationGetPartitionQual(rel));
-	partConstraint = (List *) eval_const_expressions(NULL,
-													 (Node *) partConstraint);
-	partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
-	partConstraint = list_make1(make_ands_explicit(partConstraint));
 
-	/* Validate partition constraints against the table being attached. */
-	validatePartitionConstraints(wqueue, attachRel, partConstraint, rel);
+	/* Skip validation if there are no constraints to validate. */
+	if (partConstraint)
+	{
+		partConstraint =
+			(List *) eval_const_expressions(NULL, (Node *) partConstraint);
+		partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
+		partConstraint = list_make1(make_ands_explicit(partConstraint));
+
+		/* Validate partition constraints against the table being attached. */
+		validatePartitionConstraints(wqueue, attachRel, partConstraint, rel);
+	}
 
 	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
 
-- 
2.7.4

0003-Implement-default-partition-support.patch0000664000175000017500000012157513136103112021062 0ustar  jeevanjeevanFrom d3386c5291d06b2abac6461f856ac54186612654 Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Wed, 26 Jul 2017 04:57:38 -0700
Subject: [PATCH 3/8] Implement default partition support

This patch introduces default partition for a list partitioned
table. However, in this version of patch no other partition can
be attached to a list partitioned table if it has a default
partition.

Jeevan Ladhe.
---
 src/backend/catalog/heap.c                 |  33 +++++-
 src/backend/catalog/partition.c            | 159 ++++++++++++++++++++++++++---
 src/backend/commands/tablecmds.c           |  39 ++++++-
 src/backend/nodes/copyfuncs.c              |   1 +
 src/backend/nodes/equalfuncs.c             |   1 +
 src/backend/nodes/outfuncs.c               |   1 +
 src/backend/nodes/readfuncs.c              |   1 +
 src/backend/parser/gram.y                  |  27 +++--
 src/backend/parser/parse_utilcmd.c         |  19 ++++
 src/backend/utils/adt/ruleutils.c          |  12 ++-
 src/bin/psql/tab-complete.c                |   4 +-
 src/include/catalog/partition.h            |   2 +
 src/include/nodes/parsenodes.h             |   1 +
 src/test/regress/expected/alter_table.out  |  14 +++
 src/test/regress/expected/create_table.out |  10 ++
 src/test/regress/expected/insert.out       |  56 ++++++++++
 src/test/regress/expected/plancache.out    |  22 ++++
 src/test/regress/expected/update.out       |  15 +++
 src/test/regress/sql/alter_table.sql       |  13 +++
 src/test/regress/sql/create_table.sql      |   8 ++
 src/test/regress/sql/insert.sql            |  32 ++++++
 src/test/regress/sql/plancache.sql         |  19 ++++
 src/test/regress/sql/update.sql            |  15 +++
 23 files changed, 474 insertions(+), 30 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index a376b99..068c3bd 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1755,9 +1755,11 @@ RemoveAttrDefaultById(Oid attrdefId)
 void
 heap_drop_with_catalog(Oid relid)
 {
-	Relation	rel;
+	Relation	rel,
+				parentRel;
 	HeapTuple	tuple;
-	Oid			parentOid = InvalidOid;
+	Oid			parentOid = InvalidOid,
+				defaultPartOid = InvalidOid;
 
 	/*
 	 * To drop a partition safely, we must grab exclusive lock on its parent,
@@ -1772,7 +1774,22 @@ heap_drop_with_catalog(Oid relid)
 	if (((Form_pg_class) GETSTRUCT(tuple))->relispartition)
 	{
 		parentOid = get_partition_parent(relid);
-		LockRelationOid(parentOid, AccessExclusiveLock);
+		parentRel = heap_open(parentOid, AccessExclusiveLock);
+
+		/*
+		 * The partition constraint of the default partition depends on the
+		 * partition bounds of every other partition. It is possible that
+		 * other backend might be about to execute a query on the default
+		 * partition table and the query relies on previously cached default
+		 * partition constraints, which won't be correct after removal of a
+		 * partition. We must therefore take a table lock strong enough to
+		 * prevent all queries on the default partition from proceeding until
+		 * we commit and send out a shared-cache-inval notice that will make
+		 * them update their index lists.
+		 */
+		defaultPartOid = get_default_partition_oid(parentRel);
+		if (OidIsValid(defaultPartOid))
+			LockRelationOid(defaultPartOid, AccessExclusiveLock);
 	}
 
 	ReleaseSysCache(tuple);
@@ -1883,11 +1900,21 @@ heap_drop_with_catalog(Oid relid)
 	if (OidIsValid(parentOid))
 	{
 		/*
+		 * The partition constraint for the default partition depends on the
+		 * partition bounds of every other partition, so we must invalidate
+		 * the relcache entry for that partition every time a partition is
+		 * added or removed.
+		 */
+		if (OidIsValid(defaultPartOid))
+			CacheInvalidateRelcacheByRelid(defaultPartOid);
+
+		/*
 		 * Invalidate the parent's relcache so that the partition is no longer
 		 * included in its partition descriptor.
 		 */
 		CacheInvalidateRelcacheByRelid(parentOid);
 		/* keep the lock */
+		heap_close(parentRel, NoLock);
 	}
 }
 
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 599fe2f..361d3a4 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -80,9 +80,12 @@ typedef struct PartitionBoundInfoData
 								 * partitioned table) */
 	int			null_index;		/* Index of the null-accepting partition; -1
 								 * if there isn't one */
+	int			default_index;	/* Index of the default partition; -1 if there
+								 * isn't one */
 } 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
@@ -120,7 +123,7 @@ static void get_range_key_properties(PartitionKey key, int keynum,
 						 ListCell **partexprs_item,
 						 Expr **keyCol,
 						 Const **lower_val, Const **upper_val);
-static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_list(Relation parent, PartitionBoundSpec *spec);
 static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
 static List *generate_partition_qual(Relation rel);
 
@@ -166,6 +169,7 @@ RelationBuildPartitionDesc(Relation rel)
 	/* List partitioning specific */
 	PartitionListValue **all_values = NULL;
 	int			null_index = -1;
+	int			default_index = -1;
 
 	/* Range partitioning specific */
 	PartitionRangeBound **rbounds = NULL;
@@ -246,6 +250,18 @@ RelationBuildPartitionDesc(Relation rel)
 				if (spec->strategy != PARTITION_STRATEGY_LIST)
 					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 list of non-null
+				 * datums for this partition.
+				 */
+				if (spec->is_default)
+				{
+					default_index = i;
+					i++;
+					continue;
+				}
+
 				foreach(c, spec->listdatums)
 				{
 					Const	   *val = castNode(Const, lfirst(c));
@@ -441,6 +457,7 @@ RelationBuildPartitionDesc(Relation rel)
 		boundinfo = (PartitionBoundInfoData *)
 			palloc0(sizeof(PartitionBoundInfoData));
 		boundinfo->strategy = key->strategy;
+		boundinfo->default_index = -1;
 		boundinfo->ndatums = ndatums;
 		boundinfo->null_index = -1;
 		boundinfo->datums = (Datum **) palloc0(ndatums * sizeof(Datum *));
@@ -493,6 +510,20 @@ RelationBuildPartitionDesc(Relation rel)
 						boundinfo->null_index = mapping[null_index];
 					}
 
+					/* Assign mapped index for the default partition. */
+					if (default_index != -1)
+					{
+						/*
+						 * The default partition accepts any non-specified
+						 * value, hence it should not get a mapped index while
+						 * assigning those for non-null datums.
+						 */
+						Assert(default_index >= 0 &&
+							   mapping[default_index] == -1);
+						mapping[default_index] = next_index++;
+						boundinfo->default_index = mapping[default_index];
+					}
+
 					/* All partition must now have a valid mapping */
 					Assert(next_index == nparts);
 					break;
@@ -597,6 +628,9 @@ partition_bounds_equal(PartitionKey key,
 	if (b1->null_index != b2->null_index)
 		return false;
 
+	if (b1->default_index != b2->default_index)
+		return false;
+
 	for (i = 0; i < b1->ndatums; i++)
 	{
 		int			j;
@@ -875,7 +909,7 @@ get_qual_from_partbound(Relation rel, Relation parent,
 	{
 		case PARTITION_STRATEGY_LIST:
 			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
-			my_qual = get_qual_for_list(key, spec);
+			my_qual = get_qual_for_list(parent, spec);
 			break;
 
 		case PARTITION_STRATEGY_RANGE:
@@ -1274,10 +1308,14 @@ make_partition_op_expr(PartitionKey key, int keynum,
  *
  * Returns an implicit-AND list of expressions to use as a list partition's
  * constraint, given the partition key and bound structures.
+ *
+ * The function returns NIL for a default partition when it's the only
+ * partition since it can not have any partition constraint.
  */
 static List *
-get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
+get_qual_for_list(Relation parent, PartitionBoundSpec *spec)
 {
+	PartitionKey key = RelationGetPartitionKey(parent);
 	List	   *result;
 	Expr	   *keyCol;
 	ArrayExpr  *arr;
@@ -1304,15 +1342,63 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 	else
 		keyCol = (Expr *) copyObject(linitial(key->partexprs));
 
-	/* Create list of Consts for the allowed values, excluding any nulls */
-	foreach(cell, spec->listdatums)
+	/*
+	 * For default list partition, collect datums for all the partitions. The
+	 * default partition constraint should check that the partition key is
+	 * equal to none of those.
+	 */
+	if (spec->is_default)
 	{
-		Const	   *val = castNode(Const, lfirst(cell));
+		int			i;
+		int			ndatums = 0;
+		PartitionDesc pdesc = RelationGetPartitionDesc(parent);
+		PartitionBoundInfo boundinfo = pdesc->boundinfo;
 
-		if (val->constisnull)
-			list_has_null = true;
-		else
-			arrelems = lappend(arrelems, copyObject(val));
+		if (boundinfo)
+		{
+			ndatums = boundinfo->ndatums;
+
+			if (partition_bound_accepts_nulls(boundinfo))
+				list_has_null = true;
+		}
+
+		/*
+		 * If default is the only partition, there need not be any partition
+		 * constraint on it.
+		 */
+		if (ndatums == 0 && !list_has_null)
+			return NIL;
+
+		for (i = 0; i < ndatums; i++)
+		{
+			Const	   *val;
+
+			/* Construct const from datum */
+			val = makeConst(key->parttypid[0],
+							key->parttypmod[0],
+							key->parttypcoll[0],
+							key->parttyplen[0],
+							*boundinfo->datums[i],
+							false,	/* isnull */
+							key->parttypbyval[0]);
+
+			arrelems = lappend(arrelems, val);
+		}
+	}
+	else
+	{
+		/*
+		 * Create list of Consts for the allowed values, excluding any nulls.
+		 */
+		foreach(cell, spec->listdatums)
+		{
+			Const	   *val = castNode(Const, lfirst(cell));
+
+			if (val->constisnull)
+				list_has_null = true;
+			else
+				arrelems = lappend(arrelems, copyObject(val));
+		}
 	}
 
 	if (arrelems)
@@ -1376,6 +1462,25 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 			result = list_make1(nulltest);
 	}
 
+	/*
+	 * In case of the default partition, the constraint is of the form
+	 * "!(result)" i.e. one of the following two forms:
+	 *
+	 * 1. NOT ((keycol IS NULL) OR (keycol = ANY (arr)))
+	 *
+	 * 2. NOT ((keycol IS NOT NULL) AND (keycol = ANY (arr)))
+	 *
+	 * Note that, in general, applying NOT to a constraint expression doesn't
+	 * necessarily invert the set of rows it accepts, because NOT (NULL) is
+	 * NULL.  However, the partition constraints we construct here never
+	 * evaluate to NULL, so applying NOT works as intended.
+	 */
+	if (spec->is_default)
+	{
+		result = list_make1(make_ands_explicit(result));
+		result = list_make1(makeBoolExpr(NOT_EXPR, result, -1));
+	}
+
 	return result;
 }
 
@@ -1991,8 +2096,8 @@ get_partition_for_tuple(PartitionDispatch *pd,
 		}
 
 		/*
-		 * A null partition key is only acceptable if null-accepting list
-		 * partition exists.
+		 * If this is a NULL, route it to the null-accepting partition. 
+		 * Otherwise, route by searching the array of partition bounds.
 		 */
 		cur_index = -1;
 		if (isnull[0] && partition_bound_accepts_nulls(partdesc->boundinfo))
@@ -2030,11 +2135,17 @@ get_partition_for_tuple(PartitionDispatch *pd,
 		}
 
 		/*
-		 * cur_index < 0 means we failed to find a partition of this parent.
-		 * cur_index >= 0 means we either found the leaf partition, or the
-		 * next parent to find a partition of.
+		 * cur_index < 0 means we could not find a non-default partition of
+		 * this parent. cur_index >= 0 means we either found the leaf
+		 * partition, or the next parent to find a partition of.
+		 *
+		 * If we couldn't find a non-default partition check if the default
+		 * partition exists, if it does, get its index.
 		 */
 		if (cur_index < 0)
+			cur_index = partdesc->boundinfo->default_index;
+
+		if (cur_index < 0)
 		{
 			result = -1;
 			*failed_at = parent;
@@ -2322,3 +2433,21 @@ partition_bound_bsearch(PartitionKey key, PartitionBoundInfo boundinfo,
 
 	return lo;
 }
+
+/*
+ * get_default_partition_oid
+ *
+ * If the given relation has a default partition return the OID of the default
+ * partition, otherwise return InvalidOid.
+ */
+Oid
+get_default_partition_oid(Relation parent)
+{
+	PartitionDesc partdesc = RelationGetPartitionDesc(parent);
+
+	if (partdesc && partdesc->boundinfo &&
+		partition_bound_has_default(partdesc->boundinfo))
+		return partdesc->oids[partdesc->boundinfo->default_index];
+
+	return InvalidOid;
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f24602d..bcca46b 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -767,7 +767,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	{
 		PartitionBoundSpec *bound;
 		ParseState *pstate;
-		Oid			parentId = linitial_oid(inheritOids);
+		Oid			parentId = linitial_oid(inheritOids),
+					defaultPartOid;
 		Relation	parent;
 
 		/* Already have strong enough lock on the parent */
@@ -783,6 +784,17 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 					 errmsg("\"%s\" is not partitioned",
 							RelationGetRelationName(parent))));
 
+		/*
+		 * A table cannot be created as a partition of a parent already having
+		 * a default partition.
+		 */
+		defaultPartOid = get_default_partition_oid(parent);
+		if (OidIsValid(defaultPartOid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("cannot add a new partition to table \"%s\" having a default partition",
+							RelationGetRelationName(parent))));
+
 		/* Tranform the bound values */
 		pstate = make_parsestate(NULL);
 		pstate->p_sourcetext = queryString;
@@ -13591,8 +13603,17 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	int			natts;
 	TupleDesc	tupleDesc;
 	ObjectAddress address;
+	Oid			defaultPartOid;
 	const char *trigger_name;
 
+	/* A partition cannot be attached if there exists a default partition */
+	defaultPartOid = get_default_partition_oid(rel);
+	if (OidIsValid(defaultPartOid))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot attach a new partition to table \"%s\" having a default partition",
+						RelationGetRelationName(rel))));
+
 	attachRel = heap_openrv(cmd->name, AccessExclusiveLock);
 
 	/*
@@ -13794,6 +13815,15 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 				new_null[Natts_pg_class],
 				new_repl[Natts_pg_class];
 	ObjectAddress address;
+	Oid			defaultPartOid;
+
+	/*
+	 * We must also lock the default partition, for the same reasons explained
+	 * in heap_drop_with_catalog().
+	 */
+	defaultPartOid = get_default_partition_oid(rel);
+	if (OidIsValid(defaultPartOid))
+		LockRelationOid(defaultPartOid, AccessExclusiveLock);
 
 	partRel = heap_openrv(name, AccessShareLock);
 
@@ -13826,6 +13856,13 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 	heap_close(classRel, RowExclusiveLock);
 
 	/*
+	 * We must invalidate default partition's relcache, for the same reasons
+	 * explained in heap_drop_with_catalog().
+	 */
+	if (OidIsValid(defaultPartOid))
+		CacheInvalidateRelcacheByRelid(defaultPartOid);
+
+	/*
 	 * Invalidate the parent's relcache so that the partition is no longer
 	 * included in its partition descriptor.
 	 */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 45a04b0..52fdea5 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4445,6 +4445,7 @@ _copyPartitionBoundSpec(const PartitionBoundSpec *from)
 	PartitionBoundSpec *newnode = makeNode(PartitionBoundSpec);
 
 	COPY_SCALAR_FIELD(strategy);
+	COPY_SCALAR_FIELD(is_default);
 	COPY_NODE_FIELD(listdatums);
 	COPY_NODE_FIELD(lowerdatums);
 	COPY_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8d92c03..64ecfff 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2838,6 +2838,7 @@ static bool
 _equalPartitionBoundSpec(const PartitionBoundSpec *a, const PartitionBoundSpec *b)
 {
 	COMPARE_SCALAR_FIELD(strategy);
+	COMPARE_SCALAR_FIELD(is_default);
 	COMPARE_NODE_FIELD(listdatums);
 	COMPARE_NODE_FIELD(lowerdatums);
 	COMPARE_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 379d92a..38e3ca7 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3571,6 +3571,7 @@ _outPartitionBoundSpec(StringInfo str, const PartitionBoundSpec *node)
 	WRITE_NODE_TYPE("PARTITIONBOUNDSPEC");
 
 	WRITE_CHAR_FIELD(strategy);
+	WRITE_BOOL_FIELD(is_default);
 	WRITE_NODE_FIELD(listdatums);
 	WRITE_NODE_FIELD(lowerdatums);
 	WRITE_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 86c811d..2840ca7 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2388,6 +2388,7 @@ _readPartitionBoundSpec(void)
 	READ_LOCALS(PartitionBoundSpec);
 
 	READ_CHAR_FIELD(strategy);
+	READ_BOOL_FIELD(is_default);
 	READ_NODE_FIELD(listdatums);
 	READ_NODE_FIELD(lowerdatums);
 	READ_NODE_FIELD(upperdatums);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 4b1ce09..1a3a4b0 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -575,7 +575,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>			part_strategy
 %type <partelem>	part_elem
 %type <list>		part_params
-%type <partboundspec> ForValues
+%type <partboundspec> PartitionBoundSpec
 %type <node>		partbound_datum PartitionRangeDatum
 %type <list>		partbound_datum_list range_datum_list
 
@@ -2011,7 +2011,7 @@ alter_table_cmds:
 
 partition_cmd:
 			/* ALTER TABLE <name> ATTACH PARTITION <table_name> FOR VALUES */
-			ATTACH PARTITION qualified_name ForValues
+			ATTACH PARTITION qualified_name PartitionBoundSpec
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
 					PartitionCmd *cmd = makeNode(PartitionCmd);
@@ -2650,13 +2650,14 @@ alter_identity_column_option:
 				}
 		;
 
-ForValues:
+PartitionBoundSpec:
 			/* a LIST partition */
 			FOR VALUES IN_P '(' partbound_datum_list ')'
 				{
 					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
 
 					n->strategy = PARTITION_STRATEGY_LIST;
+					n->is_default = false;
 					n->listdatums = $5;
 					n->location = @3;
 
@@ -2669,12 +2670,24 @@ ForValues:
 					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
 
 					n->strategy = PARTITION_STRATEGY_RANGE;
+					n->is_default = false;
 					n->lowerdatums = $5;
 					n->upperdatums = $9;
 					n->location = @3;
 
 					$$ = n;
 				}
+
+			/* a DEFAULT partition */
+			| DEFAULT
+				{
+					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+					n->is_default = true;
+					n->location = @1;
+
+					$$ = n;
+				}
 		;
 
 partbound_datum:
@@ -3145,7 +3158,7 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
-			OptTypedTableElementList ForValues OptPartitionSpec OptWith
+			OptTypedTableElementList PartitionBoundSpec OptPartitionSpec OptWith
 			OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
@@ -3164,7 +3177,7 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
-			qualified_name OptTypedTableElementList ForValues OptPartitionSpec
+			qualified_name OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
 			OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
@@ -4879,7 +4892,7 @@ CreateForeignTableStmt:
 					$$ = (Node *) n;
 				}
 		| CREATE FOREIGN TABLE qualified_name
-			PARTITION OF qualified_name OptTypedTableElementList ForValues
+			PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
 			SERVER name create_generic_options
 				{
 					CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
@@ -4900,7 +4913,7 @@ CreateForeignTableStmt:
 					$$ = (Node *) n;
 				}
 		| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
-			PARTITION OF qualified_name OptTypedTableElementList ForValues
+			PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
 			SERVER name create_generic_options
 				{
 					CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 9f37f1b..6830b64 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -60,6 +61,7 @@
 #include "parser/parse_utilcmd.h"
 #include "parser/parser.h"
 #include "rewrite/rewriteManip.h"
+#include "storage/lmgr.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
@@ -3303,6 +3305,23 @@ transformPartitionBound(ParseState *pstate, Relation parent,
 	/* Avoid scribbling on input */
 	result_spec = copyObject(spec);
 
+	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
+		 * partition bound spec.
+		 */
+		result_spec->strategy = strategy;
+
+		return result_spec;
+	}
+
 	if (strategy == PARTITION_STRATEGY_LIST)
 	{
 		ListCell   *cell;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index d83377d..e066159 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8694,10 +8694,18 @@ get_rule_expr(Node *node, deparse_context *context,
 		case T_PartitionBoundSpec:
 			{
 				PartitionBoundSpec *spec = (PartitionBoundSpec *) node;
+				char		strategy = spec->strategy;
 				ListCell   *cell;
 				char	   *sep;
 
-				switch (spec->strategy)
+				if (spec->is_default)
+				{
+					Assert(strategy == PARTITION_STRATEGY_LIST);
+					appendStringInfoString(buf, "DEFAULT");
+					break;
+				}
+
+				switch (strategy)
 				{
 					case PARTITION_STRATEGY_LIST:
 						Assert(spec->listdatums != NIL);
@@ -8767,7 +8775,7 @@ get_rule_expr(Node *node, deparse_context *context,
 
 					default:
 						elog(ERROR, "unrecognized partition strategy: %d",
-							 (int) spec->strategy);
+							 (int) strategy);
 						break;
 				}
 			}
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index e9fdc90..db937a8 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2050,7 +2050,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("ATTACH", "PARTITION", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 	else if (TailMatches2("FOR", "VALUES"))
 		COMPLETE_WITH_LIST2("FROM (", "IN (");
 
@@ -2489,7 +2489,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("PARTITION", "OF", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 
 /* CREATE TABLESPACE */
 	else if (Matches3("CREATE", "TABLESPACE", MatchAny))
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index f10879a..707fca4 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -98,4 +98,6 @@ extern int get_partition_for_tuple(PartitionDispatch *pd,
 						EState *estate,
 						PartitionDispatchData **failed_at,
 						TupleTableSlot **failed_slot);
+extern Oid	get_default_partition_oid(Relation parent);
+
 #endif							/* PARTITION_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 5f2a4a7..6e05b79 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -797,6 +797,7 @@ typedef struct PartitionBoundSpec
 	NodeTag		type;
 
 	char		strategy;		/* see PARTITION_STRATEGY codes above */
+	bool		is_default;		/* is it a default partition bound? */
 
 	/* Partitioning info for LIST strategy: */
 	List	   *listdatums;		/* List of Consts (or A_Consts in raw tree) */
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 13d6a4b..0acf7ef 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3273,6 +3273,11 @@ SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::reg
 CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
 ERROR:  partition "fail_part" would overlap partition "part_1"
+-- attaching default partition overlaps if the default partition already exists
+CREATE TABLE def_part PARTITION OF list_parted DEFAULT;
+CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT;
+ERROR:  cannot attach a new partition to table "list_parted" having a default partition
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
 	a int,
@@ -3286,6 +3291,15 @@ ERROR:  partition constraint is violated by some row
 -- should be ok after deleting the bad row
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+-- check partition cannot be attached if default exists
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
+INSERT INTO list_parted2_def VALUES (11, 'z');
+CREATE TABLE part_3 (LIKE list_parted2);
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+ERROR:  cannot attach a new partition to table "list_parted2" having a default partition
+-- should be ok after detaching a default partition
+ALTER TABLE list_parted2 DETACH PARTITION list_parted2_def;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
 CREATE TABLE part_3_4 (
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index aa44c11..7901442 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -467,6 +467,13 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
 ERROR:  invalid bound specification for a list partition
 LINE 1: ...BLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) T...
                                                              ^
+-- check default partition cannot be created more than once
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
+ERROR:  cannot add a new partition to table "list_parted" having a default partition
+-- check that no partition can be created after default partition
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (3);
+ERROR:  cannot add a new partition to table "list_parted" having a default partition
 -- specified literal can't be cast to the partition column data type
 CREATE TABLE bools (
 	a bool
@@ -514,6 +521,9 @@ 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 (
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 0dcc86f..548295f 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -219,17 +219,56 @@ insert into part_null values (null, 0);
 create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b);
 create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
 create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
+-- test default partition
+create table part_default partition of list_parted default;
+-- fail
+insert into part_default values ('aa', 2);
+ERROR:  new row for relation "part_default" violates partition constraint
+DETAIL:  Failing row contains (aa, 2).
+insert into part_default values (null, 2);
+ERROR:  new row for relation "part_default" violates partition constraint
+DETAIL:  Failing row contains (null, 2).
+-- ok
+insert into part_default values ('Zz', 2);
+-- check in case of multi-level default partitioned table
+drop table part_default;
+create table part_default partition of list_parted default partition by range(b);
+create table part_default_p1 partition of part_default for values from (20) to (30);
+create table part_default_p2 partition of part_default for values from (30) to (40);
 -- fail
 insert into part_ee_ff1 values ('EE', 11);
 ERROR:  new row for relation "part_ee_ff1" violates partition constraint
 DETAIL:  Failing row contains (EE, 11).
+insert into part_default_p2 values ('gg', 43);
+ERROR:  new row for relation "part_default_p2" violates partition constraint
+DETAIL:  Failing row contains (gg, 43).
 -- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
 insert into part_ee_ff1 values ('cc', 1);
 ERROR:  new row for relation "part_ee_ff1" violates partition constraint
 DETAIL:  Failing row contains (cc, 1).
+insert into part_default values ('gg', 43);
+ERROR:  no partition of relation "part_default" found for row
+DETAIL:  Partition key of the failing row contains (b) = (43).
 -- ok
 insert into part_ee_ff1 values ('ff', 1);
 insert into part_ee_ff2 values ('ff', 11);
+insert into part_default_p1 values ('cd', 25);
+insert into part_default_p2 values ('de', 35);
+insert into list_parted values ('ab', 21);
+-- drop default, as we need to add some more partitions to test tuple routing
+select tableoid::regclass, * from list_parted;
+    tableoid     | a  | b  
+-----------------+----+----
+ part_cc_dd      | cC |  1
+ part_null       |    |  0
+ part_ee_ff1     | ff |  1
+ part_ee_ff2     | ff | 11
+ part_default_p1 | cd | 25
+ part_default_p1 | ab | 21
+ part_default_p2 | de | 35
+(7 rows)
+
+drop table part_default;
 -- Check tuple routing for partitioned tables
 -- fail
 insert into range_parted values ('a', 0);
@@ -316,6 +355,23 @@ select tableoid::regclass::text, a, min(b) as min_b, max(b) as max_b from list_p
 
 -- cleanup
 drop table range_parted, list_parted;
+-- test adding default partition as first partition accepts any value including
+-- null
+create table list_parted (a int) partition by list (a);
+create table part_default partition of list_parted default;
+insert into part_default values (null);
+insert into part_default values (1);
+insert into part_default values (-1);
+select tableoid::regclass, a from list_parted;
+   tableoid   | a  
+--------------+----
+ part_default |   
+ part_default |  1
+ part_default | -1
+(3 rows)
+
+-- cleanup
+drop table list_parted;
 -- more tests for certain multi-level partitioning scenarios
 create table mlparted (a int, b int) partition by range (a, b);
 create table mlparted1 (b int not null, a int not null) partition by range ((b+0));
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
index 3f3db33..53fe3d4 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -252,3 +252,25 @@ NOTICE:  3
  
 (1 row)
 
+-- Check that addition or removal of any partition is correctly dealt with by
+-- default partition table when it is being used in cached plan.
+create table list_parted (a int) partition by list(a);
+create table list_part_null partition of list_parted for values in (null);
+create table list_part_1 partition of list_parted for values in (1);
+create table list_part_def partition of list_parted default;
+prepare pstmt_def_insert (int) as insert into list_part_def values($1);
+-- should fail
+execute pstmt_def_insert(null);
+ERROR:  new row for relation "list_part_def" violates partition constraint
+DETAIL:  Failing row contains (null).
+execute pstmt_def_insert(1);
+ERROR:  new row for relation "list_part_def" violates partition constraint
+DETAIL:  Failing row contains (1).
+alter table list_parted detach partition list_part_null;
+-- should be ok
+execute pstmt_def_insert(null);
+drop table list_part_1;
+-- should be ok
+execute pstmt_def_insert(1);
+drop table list_parted;
+deallocate pstmt_def_insert;
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index 9366f04..9912ef2 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -218,5 +218,20 @@ 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;
+create table list_parted (
+	a text,
+	b int
+) partition by list (a);
+create table list_part1  partition of list_parted for values in ('a', 'b');
+create table list_default partition of list_parted default;
+insert into list_part1 values ('a', 1);
+insert into list_default values ('d', 10);
+-- fail
+update list_default set a = 'a' where a = 'd';
+ERROR:  new row for relation "list_default" violates partition constraint
+DETAIL:  Failing row contains (a, 10).
+-- ok
+update list_default set a = 'x' where a = 'd';
 -- cleanup
 drop table range_parted;
+drop table list_parted;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 5dd1402..2346c1f 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2095,6 +2095,10 @@ SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::reg
 -- check that the new partition won't overlap with an existing partition
 CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+-- attaching default partition overlaps if the default partition already exists
+CREATE TABLE def_part PARTITION OF list_parted DEFAULT;
+CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT;
 
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
@@ -2111,6 +2115,15 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 
+-- check partition cannot be attached if default exists
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
+INSERT INTO list_parted2_def VALUES (11, 'z');
+CREATE TABLE part_3 (LIKE list_parted2);
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+-- should be ok after detaching a default partition
+ALTER TABLE list_parted2 DETACH PARTITION list_parted2_def;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
 CREATE TABLE part_3_4 (
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 1c0ce92..71dbd6e 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -447,6 +447,12 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
 -- trying to specify range for list partitioned table
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
 
+-- check default partition cannot be created more than once
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
+-- check that no partition can be created after default partition
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (3);
+
 -- specified literal can't be cast to the partition column data type
 CREATE TABLE bools (
 	a bool
@@ -484,6 +490,8 @@ 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
 
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 6adf25d..ad6388a 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -132,13 +132,34 @@ create table part_ee_ff partition of list_parted for values in ('ee', 'ff') part
 create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
 create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
 
+-- test default partition
+create table part_default partition of list_parted default;
+-- fail
+insert into part_default values ('aa', 2);
+insert into part_default values (null, 2);
+-- ok
+insert into part_default values ('Zz', 2);
+-- check in case of multi-level default partitioned table
+drop table part_default;
+create table part_default partition of list_parted default partition by range(b);
+create table part_default_p1 partition of part_default for values from (20) to (30);
+create table part_default_p2 partition of part_default for values from (30) to (40);
+
 -- fail
 insert into part_ee_ff1 values ('EE', 11);
+insert into part_default_p2 values ('gg', 43);
 -- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
 insert into part_ee_ff1 values ('cc', 1);
+insert into part_default values ('gg', 43);
 -- ok
 insert into part_ee_ff1 values ('ff', 1);
 insert into part_ee_ff2 values ('ff', 11);
+insert into part_default_p1 values ('cd', 25);
+insert into part_default_p2 values ('de', 35);
+insert into list_parted values ('ab', 21);
+-- drop default, as we need to add some more partitions to test tuple routing
+select tableoid::regclass, * from list_parted;
+drop table part_default;
 
 -- Check tuple routing for partitioned tables
 
@@ -188,6 +209,17 @@ select tableoid::regclass::text, a, min(b) as min_b, max(b) as max_b from list_p
 -- cleanup
 drop table range_parted, list_parted;
 
+-- test adding default partition as first partition accepts any value including
+-- null
+create table list_parted (a int) partition by list (a);
+create table part_default partition of list_parted default;
+insert into part_default values (null);
+insert into part_default values (1);
+insert into part_default values (-1);
+select tableoid::regclass, a from list_parted;
+-- cleanup
+drop table list_parted;
+
 -- more tests for certain multi-level partitioning scenarios
 create table mlparted (a int, b int) partition by range (a, b);
 create table mlparted1 (b int not null, a int not null) partition by range ((b+0));
diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql
index bc20861..89ddf3f 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -156,3 +156,22 @@ end$$ language plpgsql;
 
 select cachebug();
 select cachebug();
+
+-- Check that addition or removal of any partition is correctly dealt with by
+-- default partition table when it is being used in cached plan.
+create table list_parted (a int) partition by list(a);
+create table list_part_null partition of list_parted for values in (null);
+create table list_part_1 partition of list_parted for values in (1);
+create table list_part_def partition of list_parted default;
+prepare pstmt_def_insert (int) as insert into list_part_def values($1);
+-- should fail
+execute pstmt_def_insert(null);
+execute pstmt_def_insert(1);
+alter table list_parted detach partition list_part_null;
+-- should be ok
+execute pstmt_def_insert(null);
+drop table list_part_1;
+-- should be ok
+execute pstmt_def_insert(1);
+drop table list_parted;
+deallocate pstmt_def_insert;
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 6637119..44fb0dc 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -125,5 +125,20 @@ update range_parted set b = b - 1 where b = 10;
 -- ok
 update range_parted set b = b + 1 where b = 10;
 
+create table list_parted (
+	a text,
+	b int
+) partition by list (a);
+create table list_part1  partition of list_parted for values in ('a', 'b');
+create table list_default partition of list_parted default;
+insert into list_part1 values ('a', 1);
+insert into list_default values ('d', 10);
+
+-- fail
+update list_default set a = 'a' where a = 'd';
+-- ok
+update list_default set a = 'x' where a = 'd';
+
 -- cleanup
 drop table range_parted;
+drop table list_parted;
-- 
2.7.4

0004-Store-the-default-partition-OID-in-catalog.patch0000664000175000017500000002465513136103112021717 0ustar  jeevanjeevanFrom 3885709a81fb193bc13ad96d672c117cac000139 Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Wed, 26 Jul 2017 04:58:32 -0700
Subject: [PATCH 4/8] Store the default partition OID in catalog
 pg_partitioned_table

This patch introduces a new field partdefid to pg_partitioned_table.
This can be used to quickly look up for default partition instead
of looking for every child in pg_inherits, then looking its entry
in pg_class to verify if the partition is default of or not.

Jeevan Ladhe.
---
 src/backend/catalog/heap.c                 | 16 +++++++---
 src/backend/catalog/partition.c            | 50 ++++++++++++++++++++++++++----
 src/backend/commands/tablecmds.c           | 32 ++++++++++++++-----
 src/include/catalog/partition.h            |  3 +-
 src/include/catalog/pg_partitioned_table.h | 13 +++++---
 5 files changed, 90 insertions(+), 24 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 068c3bd..0f6b41c 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1755,8 +1755,7 @@ RemoveAttrDefaultById(Oid attrdefId)
 void
 heap_drop_with_catalog(Oid relid)
 {
-	Relation	rel,
-				parentRel;
+	Relation	rel;
 	HeapTuple	tuple;
 	Oid			parentOid = InvalidOid,
 				defaultPartOid = InvalidOid;
@@ -1774,7 +1773,7 @@ heap_drop_with_catalog(Oid relid)
 	if (((Form_pg_class) GETSTRUCT(tuple))->relispartition)
 	{
 		parentOid = get_partition_parent(relid);
-		parentRel = heap_open(parentOid, AccessExclusiveLock);
+		LockRelationOid(parentOid, AccessExclusiveLock);
 
 		/*
 		 * The partition constraint of the default partition depends on the
@@ -1787,7 +1786,7 @@ heap_drop_with_catalog(Oid relid)
 		 * we commit and send out a shared-cache-inval notice that will make
 		 * them update their index lists.
 		 */
-		defaultPartOid = get_default_partition_oid(parentRel);
+		defaultPartOid = get_default_partition_oid(parentOid);
 		if (OidIsValid(defaultPartOid))
 			LockRelationOid(defaultPartOid, AccessExclusiveLock);
 	}
@@ -1841,6 +1840,13 @@ heap_drop_with_catalog(Oid relid)
 		RemovePartitionKeyByRelId(relid);
 
 	/*
+	 * If the relation being dropped is the default partition itself,
+	 * invalidate its entry in pg_partitioned_table.
+	 */
+	if (relid == defaultPartOid)
+		update_default_partition_oid(parentOid, InvalidOid);
+
+	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -1914,7 +1920,6 @@ heap_drop_with_catalog(Oid relid)
 		 */
 		CacheInvalidateRelcacheByRelid(parentOid);
 		/* keep the lock */
-		heap_close(parentRel, NoLock);
 	}
 }
 
@@ -3163,6 +3168,7 @@ StorePartitionKey(Relation rel,
 	values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
 	values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
 	values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
+	values[Anum_pg_partitioned_table_partdefid - 1] = ObjectIdGetDatum(InvalidOid);
 	values[Anum_pg_partitioned_table_partattrs - 1] = PointerGetDatum(partattrs_vec);
 	values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
 	values[Anum_pg_partitioned_table_partcollation - 1] = PointerGetDatum(partcollation_vec);
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 361d3a4..2ba8c85 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -27,6 +27,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_inherits_fn.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_type.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
@@ -2441,13 +2442,50 @@ partition_bound_bsearch(PartitionKey key, PartitionBoundInfo boundinfo,
  * partition, otherwise return InvalidOid.
  */
 Oid
-get_default_partition_oid(Relation parent)
+get_default_partition_oid(Oid parentId)
 {
-	PartitionDesc partdesc = RelationGetPartitionDesc(parent);
+	HeapTuple	tuple;
+	Oid			defaultPartId = InvalidOid;
+
+	tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(parentId));
+
+	if (HeapTupleIsValid(tuple))
+	{
+		Form_pg_partitioned_table part_table_form;
+
+		part_table_form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+		defaultPartId = part_table_form->partdefid;
+	}
+
+	ReleaseSysCache(tuple);
+	return defaultPartId;
+}
+
+/*
+ * update_default_partition_oid
+ *
+ * Updates the pg_partition_table catalog partdefid field for the given parent
+ * with the given default partition oid.
+ */
+void
+update_default_partition_oid(Oid parentId, Oid defaultPartId)
+{
+	HeapTuple	tuple;
+	Relation	pg_partitioned_table;
+	Form_pg_partitioned_table part_table_form;
+
+	pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCacheCopy1(PARTRELID, ObjectIdGetDatum(parentId));
+
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of relation %u",
+			 parentId);
 
-	if (partdesc && partdesc->boundinfo &&
-		partition_bound_has_default(partdesc->boundinfo))
-		return partdesc->oids[partdesc->boundinfo->default_index];
+	part_table_form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+	part_table_form->partdefid = defaultPartId;
+	CatalogTupleUpdate(pg_partitioned_table, &tuple->t_self, tuple);
 
-	return InvalidOid;
+	heap_freetuple(tuple);
+	heap_close(pg_partitioned_table, RowExclusiveLock);
 }
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index bcca46b..fc8a6f6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -788,7 +788,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		 * A table cannot be created as a partition of a parent already having
 		 * a default partition.
 		 */
-		defaultPartOid = get_default_partition_oid(parent);
+		defaultPartOid = get_default_partition_oid(RelationGetRelid(parent));
 		if (OidIsValid(defaultPartOid))
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
@@ -811,6 +811,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		/* Update the pg_class entry. */
 		StorePartitionBound(rel, parent, bound);
 
+		/* Update the default partition oid */
+		if (bound->is_default)
+			update_default_partition_oid(RelationGetRelid(parent), relationId);
+
 		heap_close(parent, NoLock);
 
 		/*
@@ -13607,7 +13611,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	const char *trigger_name;
 
 	/* A partition cannot be attached if there exists a default partition */
-	defaultPartOid = get_default_partition_oid(rel);
+	defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
 	if (OidIsValid(defaultPartOid))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
@@ -13769,6 +13773,11 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	/* Update the pg_class entry. */
 	StorePartitionBound(attachRel, rel, cmd->bound);
 
+	/* Update the default partition oid */
+	if (cmd->bound->is_default)
+		update_default_partition_oid(RelationGetRelid(rel),
+									 RelationGetRelid(attachRel));
+
 	/*
 	 * Generate partition constraint from the partition bound specification.
 	 * If the parent itself is a partition, make sure to include its
@@ -13821,7 +13830,7 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 	 * We must also lock the default partition, for the same reasons explained
 	 * in heap_drop_with_catalog().
 	 */
-	defaultPartOid = get_default_partition_oid(rel);
+	defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
 	if (OidIsValid(defaultPartOid))
 		LockRelationOid(defaultPartOid, AccessExclusiveLock);
 
@@ -13855,12 +13864,21 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 	heap_freetuple(newtuple);
 	heap_close(classRel, RowExclusiveLock);
 
-	/*
-	 * We must invalidate default partition's relcache, for the same reasons
-	 * explained in heap_drop_with_catalog().
-	 */
 	if (OidIsValid(defaultPartOid))
+	{
+		/*
+		 * If the detach relation is the default partition itself, invalidate
+		 * its entry in pg_partitioned_table.
+		 */
+		if (RelationGetRelid(partRel) == defaultPartOid)
+			update_default_partition_oid(RelationGetRelid(rel), InvalidOid);
+
+		/*
+		 * We must invalidate default partition's relcache, for the same
+		 * reasons explained in heap_drop_with_catalog().
+		 */
 		CacheInvalidateRelcacheByRelid(defaultPartOid);
+	}
 
 	/*
 	 * Invalidate the parent's relcache so that the partition is no longer
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 707fca4..7800c96 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -98,6 +98,7 @@ extern int get_partition_for_tuple(PartitionDispatch *pd,
 						EState *estate,
 						PartitionDispatchData **failed_at,
 						TupleTableSlot **failed_slot);
-extern Oid	get_default_partition_oid(Relation parent);
+extern Oid	get_default_partition_oid(Oid parentId);
+extern void update_default_partition_oid(Oid parentId, Oid defaultPartId);
 
 #endif							/* PARTITION_H */
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
index 38d64d6..78c7ee9 100644
--- a/src/include/catalog/pg_partitioned_table.h
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -32,6 +32,8 @@ CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
 	Oid			partrelid;		/* partitioned table oid */
 	char		partstrat;		/* partitioning strategy */
 	int16		partnatts;		/* number of partition key columns */
+	Oid			partdefid;		/* default partition oid; InvalidOid if there
+								 * isn't one */
 
 	/*
 	 * variable-length fields start here, but we allow direct access to
@@ -62,13 +64,14 @@ typedef FormData_pg_partitioned_table *Form_pg_partitioned_table;
  *		compiler constants for pg_partitioned_table
  * ----------------
  */
-#define Natts_pg_partitioned_table				7
+#define Natts_pg_partitioned_table				8
 #define Anum_pg_partitioned_table_partrelid		1
 #define Anum_pg_partitioned_table_partstrat		2
 #define Anum_pg_partitioned_table_partnatts		3
-#define Anum_pg_partitioned_table_partattrs		4
-#define Anum_pg_partitioned_table_partclass		5
-#define Anum_pg_partitioned_table_partcollation 6
-#define Anum_pg_partitioned_table_partexprs		7
+#define Anum_pg_partitioned_table_partdefid		4
+#define Anum_pg_partitioned_table_partattrs		5
+#define Anum_pg_partitioned_table_partclass		6
+#define Anum_pg_partitioned_table_partcollation	7
+#define Anum_pg_partitioned_table_partexprs		8
 
 #endif							/* PG_PARTITIONED_TABLE_H */
-- 
2.7.4

0005-Default-partition-extended-for-create-and-attach.patch0000664000175000017500000010377013136103112023204 0ustar  jeevanjeevanFrom 3277dd396426e91343777e4a084ab38e913ae70f Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Wed, 26 Jul 2017 04:59:37 -0700
Subject: [PATCH 5/8] Default partition extended for create and attach

This patch extends the previous patch in this series to allow a
new partition to be created even when a default partition on a list
partition exists. Also, extends the regression tests to test this
functionality.

Jeevan Ladhe.
---
 src/backend/catalog/heap.c                 |  33 +++---
 src/backend/catalog/partition.c            | 162 ++++++++++++++++++++++++++++-
 src/backend/commands/tablecmds.c           |  41 +++++---
 src/test/regress/expected/alter_table.out  |  22 +++-
 src/test/regress/expected/create_table.out |  15 +--
 src/test/regress/expected/insert.out       |  56 ++++------
 src/test/regress/expected/plancache.out    |  20 ++--
 src/test/regress/sql/alter_table.sql       |  18 +++-
 src/test/regress/sql/create_table.sql      |  12 +--
 src/test/regress/sql/insert.sql            |  15 +--
 src/test/regress/sql/plancache.sql         |  16 ++-
 11 files changed, 300 insertions(+), 110 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 0f6b41c..c1cb5c6 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1776,15 +1776,8 @@ heap_drop_with_catalog(Oid relid)
 		LockRelationOid(parentOid, AccessExclusiveLock);
 
 		/*
-		 * The partition constraint of the default partition depends on the
-		 * partition bounds of every other partition. It is possible that
-		 * other backend might be about to execute a query on the default
-		 * partition table and the query relies on previously cached default
-		 * partition constraints, which won't be correct after removal of a
-		 * partition. We must therefore take a table lock strong enough to
-		 * prevent all queries on the default partition from proceeding until
-		 * we commit and send out a shared-cache-inval notice that will make
-		 * them update their index lists.
+		 * We must also lock the default partition, for the same reasons
+		 * explained in DefineRelation().
 		 */
 		defaultPartOid = get_default_partition_oid(parentOid);
 		if (OidIsValid(defaultPartOid))
@@ -1906,10 +1899,8 @@ heap_drop_with_catalog(Oid relid)
 	if (OidIsValid(parentOid))
 	{
 		/*
-		 * The partition constraint for the default partition depends on the
-		 * partition bounds of every other partition, so we must invalidate
-		 * the relcache entry for that partition every time a partition is
-		 * added or removed.
+		 * We must invalidate default partition's relcache, for the same
+		 * reasons explained in StorePartitionBound().
 		 */
 		if (OidIsValid(defaultPartOid))
 			CacheInvalidateRelcacheByRelid(defaultPartOid);
@@ -3253,8 +3244,9 @@ RemovePartitionKeyByRelId(Oid relid)
  *		Update pg_class tuple of rel to store the partition bound and set
  *		relispartition to true
  *
- * Also, invalidate the parent's relcache, so that the next rebuild will load
- * the new partition's info into its partition descriptor.
+ * Also, invalidate the parent's relcache entry, so that the next rebuild will
+ * load he new partition's info into its partition descriptor.  If there is a
+ * default partition, we must invalidate its relcache entry as well.
  */
 void
 StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
@@ -3265,6 +3257,7 @@ StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
 	Datum		new_val[Natts_pg_class];
 	bool		new_null[Natts_pg_class],
 				new_repl[Natts_pg_class];
+	Oid			defaultPartOid;
 
 	/* Update pg_class tuple */
 	classRel = heap_open(RelationRelationId, RowExclusiveLock);
@@ -3302,5 +3295,15 @@ StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
 	heap_freetuple(newtuple);
 	heap_close(classRel, RowExclusiveLock);
 
+	/*
+	 * The partition constraint for the default partition depends on the
+	 * partition bounds of every other partition, so we must invalidate the
+	 * relcache entry for that partition every time a partition is added or
+	 * removed.
+	 */
+	defaultPartOid = get_default_partition_oid(RelationGetRelid(parent));
+	if (OidIsValid(defaultPartOid))
+		CacheInvalidateRelcacheByRelid(defaultPartOid);
+
 	CacheInvalidateRelcache(parent);
 }
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 2ba8c85..c7e7144 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -36,6 +36,7 @@
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planmain.h"
+#include "optimizer/prep.h"
 #include "optimizer/var.h"
 #include "rewrite/rewriteManip.h"
 #include "storage/lmgr.h"
@@ -143,6 +144,8 @@ static int32 partition_bound_cmp(PartitionKey key,
 static int partition_bound_bsearch(PartitionKey key,
 						PartitionBoundInfo boundinfo,
 						void *probe, bool probe_is_bound, bool *is_equal);
+static void check_default_allows_bound(Relation parent,
+						   PartitionBoundSpec *new_spec);
 
 /*
  * RelationBuildPartitionDesc
@@ -691,10 +694,27 @@ check_new_partition_bound(char *relname, Relation parent,
 {
 	PartitionKey key = RelationGetPartitionKey(parent);
 	PartitionDesc partdesc = RelationGetPartitionDesc(parent);
+	PartitionBoundInfo boundinfo = partdesc->boundinfo;
 	ParseState *pstate = make_parsestate(NULL);
 	int			with = -1;
 	bool		overlap = false;
 
+	if (spec->is_default)
+	{
+		/* Default partition cannot be added if there already exists one. */
+		if (partdesc->nparts > 0 && partition_bound_has_default(boundinfo))
+		{
+			with = boundinfo->default_index;
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("partition \"%s\" conflicts with existing default partition \"%s\"",
+							relname, get_rel_name(partdesc->oids[with])),
+					 parser_errposition(pstate, spec->location)));
+		}
+
+		return;
+	}
+
 	switch (key->strategy)
 	{
 		case PARTITION_STRATEGY_LIST:
@@ -703,13 +723,13 @@ check_new_partition_bound(char *relname, Relation parent,
 
 				if (partdesc->nparts > 0)
 				{
-					PartitionBoundInfo boundinfo = partdesc->boundinfo;
 					ListCell   *cell;
 
 					Assert(boundinfo &&
 						   boundinfo->strategy == PARTITION_STRATEGY_LIST &&
 						   (boundinfo->ndatums > 0 ||
-							partition_bound_accepts_nulls(boundinfo)));
+							partition_bound_accepts_nulls(boundinfo) ||
+							partition_bound_has_default(boundinfo)));
 
 					foreach(cell, spec->listdatums)
 					{
@@ -844,6 +864,144 @@ check_new_partition_bound(char *relname, Relation parent,
 						relname, get_rel_name(partdesc->oids[with])),
 				 parser_errposition(pstate, spec->location)));
 	}
+
+	/*
+	 * If the default partition exists, its partition constraints will change
+	 * after the addition of this new partition such that it won't allow any
+	 * row that qualifies for this new partition. So, check if the existing
+	 * data in the default partition satisfies this *would be* default
+	 * partition constraint. In case the new partition bound being checked
+	 * itself is a DEFAULT bound, this check shouldn't be triggered as there
+	 * won't already exists the default partition in such a case.
+	 */
+	if (boundinfo && partition_bound_has_default(boundinfo))
+		check_default_allows_bound(parent, spec);
+}
+
+/*
+ * check_default_allows_bound
+ *
+ * This function checks if there exists a row in the default partition that
+ * fits in the new partition and throws an error if it finds one.
+ */
+static void
+check_default_allows_bound(Relation parent, PartitionBoundSpec *new_spec)
+{
+	Relation	default_rel;
+	List	   *new_part_constraints = NIL;
+	List	   *all_parts;
+	ListCell   *lc;
+	PartitionDescData *part_desc = RelationGetPartitionDesc(parent);
+
+	/* 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 = (List *) eval_const_expressions(NULL,
+														   (Node *) new_part_constraints);
+	new_part_constraints =
+		(List *) canonicalize_qual((Expr *) new_part_constraints);
+	new_part_constraints = list_make1(make_ands_explicit(new_part_constraints));
+
+	/*
+	 * Generate the constraint and default execution states.
+	 *
+	 * The default partition must be already having an AccessExclusiveLock.
+	 */
+	default_rel = heap_open(get_default_partition_oid(RelationGetRelid(parent)),
+							NoLock);
+
+	if (default_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		all_parts = find_all_inheritors(RelationGetRelid(default_rel),
+										AccessExclusiveLock, NULL);
+	else
+		all_parts = list_make1_oid(RelationGetRelid(default_rel));
+
+	foreach(lc, all_parts)
+	{
+		Oid			part_relid = lfirst_oid(lc);
+		Relation	part_rel;
+		Expr	   *constr;
+		Expr	   *partition_constraint;
+		EState	   *estate;
+		HeapTuple	tuple;
+		ExprState  *partqualstate = NULL;
+		Snapshot	snapshot;
+		TupleDesc	tupdesc;
+		ExprContext *econtext;
+		HeapScanDesc scan;
+		MemoryContext oldCxt;
+		TupleTableSlot *tupslot;
+
+		/* Lock already taken above. */
+		if (part_relid != RelationGetRelid(default_rel))
+			part_rel = heap_open(part_relid, NoLock);
+		else
+			part_rel = default_rel;
+
+		/*
+		 * Only RELKIND_RELATION relations (i.e. leaf partitions) need to be
+		 * scanned.
+		 */
+		if (part_rel->rd_rel->relkind != RELKIND_RELATION)
+		{
+			if (part_rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+				ereport(WARNING,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("skipped scanning foreign table \"%s\" which is a partition of default partition \"%s\"",
+								RelationGetRelationName(part_rel),
+								RelationGetRelationName(default_rel))));
+
+			heap_close(part_rel, NoLock);
+			continue;
+		}
+
+		tupdesc = CreateTupleDescCopy(RelationGetDescr(part_rel));
+		constr = linitial(new_part_constraints);
+		partition_constraint = (Expr *) map_partition_varattnos((List *) constr,
+																1, part_rel, parent);
+		estate = CreateExecutorState();
+
+		/* Build expression execution states for partition check quals */
+		partqualstate = ExecPrepareExpr(partition_constraint, estate);
+
+		econtext = GetPerTupleExprContext(estate);
+		snapshot = RegisterSnapshot(GetLatestSnapshot());
+		scan = heap_beginscan(part_rel, snapshot, 0, NULL);
+		tupslot = MakeSingleTupleTableSlot(tupdesc);
+
+		/*
+		 * Switch to per-tuple memory context and reset it for each tuple
+		 * produced, so we don't leak memory.
+		 */
+		oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		{
+			ExecStoreTuple(tuple, tupslot, InvalidBuffer, false);
+			econtext->ecxt_scantuple = tupslot;
+
+			if (ExecCheck(partqualstate, econtext))
+				ereport(ERROR,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("updated partition constraint for default partition \"%s\" would be violated by some row",
+								RelationGetRelationName(default_rel))));
+
+			ResetExprContext(econtext);
+			CHECK_FOR_INTERRUPTS();
+		}
+
+		MemoryContextSwitchTo(oldCxt);
+		heap_endscan(scan);
+		UnregisterSnapshot(snapshot);
+		ExecDropSingleTupleTableSlot(tupslot);
+		FreeExecutorState(estate);
+		/* keep our lock until commit */
+		heap_close(part_rel, NoLock);
+	}
 }
 
 /*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index fc8a6f6..b875dbe 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -785,15 +785,28 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 							RelationGetRelationName(parent))));
 
 		/*
-		 * A table cannot be created as a partition of a parent already having
-		 * a default partition.
+		 * The partition constraint of the default partition depends on the
+		 * partition bounds of every other partition. It is possible that
+		 * other backend might be about to execute a query on the default
+		 * partition table and the query relies on previously cached default
+		 * partition constraints, which won't be correct after removal of a
+		 * partition. We must therefore take a table lock strong enough to
+		 * prevent all queries on the default partition from proceeding until
+		 * we commit and send out a shared-cache-inval notice that will make
+		 * them update their index lists.
+		 *
+		 * Order of locking: The relation being added won't be visible to
+		 * other backends until it is committed, hence here in
+		 * DefineRelation() the order of locking the default partition and the
+		 * relation being added does not matter. But at all other places we
+		 * need to lock the default relation before we lock the relation being
+		 * added or removed i.e. we should take the lock in same order at all
+		 * the places such that lock parent, lock default partition and then
+		 * lock the partition so as to avoid a deadlock.
 		 */
 		defaultPartOid = get_default_partition_oid(RelationGetRelid(parent));
 		if (OidIsValid(defaultPartOid))
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-					 errmsg("cannot add a new partition to table \"%s\" having a default partition",
-							RelationGetRelationName(parent))));
+			LockRelationOid(defaultPartOid, AccessExclusiveLock);
 
 		/* Tranform the bound values */
 		pstate = make_parsestate(NULL);
@@ -13610,13 +13623,13 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	Oid			defaultPartOid;
 	const char *trigger_name;
 
-	/* A partition cannot be attached if there exists a default partition */
+	/*
+	 * We must lock the default partition, for the same reasons explained in
+	 * DefineRelation().
+	 */
 	defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
 	if (OidIsValid(defaultPartOid))
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-				 errmsg("cannot attach a new partition to table \"%s\" having a default partition",
-						RelationGetRelationName(rel))));
+		LockRelationOid(defaultPartOid, AccessExclusiveLock);
 
 	attachRel = heap_openrv(cmd->name, AccessExclusiveLock);
 
@@ -13827,8 +13840,8 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 	Oid			defaultPartOid;
 
 	/*
-	 * We must also lock the default partition, for the same reasons explained
-	 * in heap_drop_with_catalog().
+	 * We must lock the default partition, for the same reasons explained in
+	 * DefineRelation().
 	 */
 	defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
 	if (OidIsValid(defaultPartOid))
@@ -13875,7 +13888,7 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 
 		/*
 		 * We must invalidate default partition's relcache, for the same
-		 * reasons explained in heap_drop_with_catalog().
+		 * reasons explained in StorePartitionBound().
 		 */
 		CacheInvalidateRelcacheByRelid(defaultPartOid);
 	}
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 0acf7ef..938fe28 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3277,7 +3277,7 @@ ERROR:  partition "fail_part" would overlap partition "part_1"
 CREATE TABLE def_part PARTITION OF list_parted DEFAULT;
 CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT;
-ERROR:  cannot attach a new partition to table "list_parted" having a default partition
+ERROR:  partition "fail_def_part" conflicts with existing default partition "def_part"
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
 	a int,
@@ -3291,14 +3291,14 @@ ERROR:  partition constraint is violated by some row
 -- should be ok after deleting the bad row
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
--- check partition cannot be attached if default exists
+-- check partition cannot be attached if default has some row for its values
 CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 INSERT INTO list_parted2_def VALUES (11, 'z');
 CREATE TABLE part_3 (LIKE list_parted2);
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
-ERROR:  cannot attach a new partition to table "list_parted2" having a default partition
--- should be ok after detaching a default partition
-ALTER TABLE list_parted2 DETACH PARTITION list_parted2_def;
+ERROR:  updated partition constraint for default partition "list_parted2_def" would be violated by some row
+-- should be ok after deleting the bad row
+DELETE FROM list_parted2_def WHERE a = 11;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
@@ -3361,6 +3361,18 @@ ALTER TABLE part_5 DROP CONSTRAINT check_a;
 ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT NULL;
 ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
 INFO:  partition constraint for table "part_5" is implied by existing constraints
+-- check that leaf partitions of default partition are scanned when
+-- attaching a partitioned table.
+ALTER TABLE part_5 DROP CONSTRAINT check_a;
+CREATE TABLE part5_def PARTITION OF part_5 DEFAULT PARTITION BY LIST(a);
+CREATE TABLE part5_def_p1 PARTITION OF part5_def FOR VALUES IN (5);
+INSERT INTO part5_def_p1 VALUES (5, 'y');
+CREATE TABLE part5_p1 (LIKE part_5);
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+ERROR:  updated partition constraint for default partition "part5_def" would be violated by some row
+-- should be ok after deleting the bad row
+DELETE FROM part5_def_p1 WHERE b = 'y';
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
 -- check that the table being attached is not already a partition
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 ERROR:  "part_2" is already a partition
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 7901442..9615c07 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -449,6 +449,7 @@ CREATE TABLE list_parted (
 CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
 CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
 CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
 ERROR:  syntax error at or near "int"
 LINE 1: ... fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
@@ -457,6 +458,8 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
 ERROR:  syntax error at or near "::"
 LINE 1: ...fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
                                                                 ^
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
+ERROR:  partition "fail_default_part" conflicts with existing default partition "part_default"
 -- syntax does not allow empty list of values for list partitions
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
 ERROR:  syntax error at or near ")"
@@ -467,13 +470,6 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
 ERROR:  invalid bound specification for a list partition
 LINE 1: ...BLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) T...
                                                              ^
--- check default partition cannot be created more than once
-CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
-CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
-ERROR:  cannot add a new partition to table "list_parted" having a default partition
--- check that no partition can be created after default partition
-CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (3);
-ERROR:  cannot add a new partition to table "list_parted" having a default partition
 -- specified literal can't be cast to the partition column data type
 CREATE TABLE bools (
 	a bool
@@ -568,10 +564,15 @@ CREATE TABLE list_parted2 (
 ) PARTITION BY LIST (a);
 CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
 CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
 ERROR:  partition "fail_part" would overlap partition "part_null_z"
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
 ERROR:  partition "fail_part" would overlap partition "part_ab"
+-- check default partition overlap
+INSERT INTO list_parted2 VALUES('X');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y');
+ERROR:  updated partition constraint for default partition "list_parted2_def" would be violated by some row
 CREATE TABLE range_parted2 (
 	a int
 ) PARTITION BY RANGE (a);
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 548295f..3319f6d 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -202,6 +202,7 @@ create table list_parted (
 create table part_aa_bb partition of list_parted FOR VALUES IN ('aa', 'bb');
 create table part_cc_dd partition of list_parted FOR VALUES IN ('cc', 'dd');
 create table part_null partition of list_parted FOR VALUES IN (null);
+create table part_default partition of list_parted default;
 -- fail
 insert into part_aa_bb values ('cc', 1);
 ERROR:  new row for relation "part_aa_bb" violates partition constraint
@@ -212,16 +213,6 @@ DETAIL:  Failing row contains (AAa, 1).
 insert into part_aa_bb values (null);
 ERROR:  new row for relation "part_aa_bb" violates partition constraint
 DETAIL:  Failing row contains (null, null).
--- ok
-insert into part_cc_dd values ('cC', 1);
-insert into part_null values (null, 0);
--- check in case of multi-level partitioned table
-create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b);
-create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
-create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
--- test default partition
-create table part_default partition of list_parted default;
--- fail
 insert into part_default values ('aa', 2);
 ERROR:  new row for relation "part_default" violates partition constraint
 DETAIL:  Failing row contains (aa, 2).
@@ -229,7 +220,13 @@ insert into part_default values (null, 2);
 ERROR:  new row for relation "part_default" violates partition constraint
 DETAIL:  Failing row contains (null, 2).
 -- ok
+insert into part_cc_dd values ('cC', 1);
+insert into part_null values (null, 0);
 insert into part_default values ('Zz', 2);
+-- check in case of multi-level partitioned table
+create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b);
+create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
+create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
 -- check in case of multi-level default partitioned table
 drop table part_default;
 create table part_default partition of list_parted default partition by range(b);
@@ -255,20 +252,6 @@ insert into part_ee_ff2 values ('ff', 11);
 insert into part_default_p1 values ('cd', 25);
 insert into part_default_p2 values ('de', 35);
 insert into list_parted values ('ab', 21);
--- drop default, as we need to add some more partitions to test tuple routing
-select tableoid::regclass, * from list_parted;
-    tableoid     | a  | b  
------------------+----+----
- part_cc_dd      | cC |  1
- part_null       |    |  0
- part_ee_ff1     | ff |  1
- part_ee_ff2     | ff | 11
- part_default_p1 | cd | 25
- part_default_p1 | ab | 21
- part_default_p2 | de | 35
-(7 rows)
-
-drop table part_default;
 -- Check tuple routing for partitioned tables
 -- fail
 insert into range_parted values ('a', 0);
@@ -313,17 +296,20 @@ DETAIL:  Partition key of the failing row contains (b) = (0).
 insert into list_parted values ('EE', 1);
 insert into part_ee_ff values ('EE', 10);
 select tableoid::regclass, * from list_parted;
-  tableoid   | a  | b  
--------------+----+----
- part_aa_bb  | aA |   
- part_cc_dd  | cC |  1
- part_null   |    |  0
- part_null   |    |  1
- part_ee_ff1 | ff |  1
- part_ee_ff1 | EE |  1
- part_ee_ff2 | ff | 11
- part_ee_ff2 | EE | 10
-(8 rows)
+    tableoid     | a  | b  
+-----------------+----+----
+ part_aa_bb      | aA |   
+ part_cc_dd      | cC |  1
+ part_null       |    |  0
+ part_null       |    |  1
+ part_ee_ff1     | ff |  1
+ part_ee_ff1     | EE |  1
+ part_ee_ff2     | ff | 11
+ part_ee_ff2     | EE | 10
+ part_default_p1 | cd | 25
+ part_default_p1 | ab | 21
+ part_default_p2 | de | 35
+(11 rows)
 
 -- some more tests to exercise tuple-routing with multi-level partitioning
 create table part_gg partition of list_parted for values in ('gg') partition by range (b);
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
index 53fe3d4..f0e811f 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -255,22 +255,28 @@ NOTICE:  3
 -- Check that addition or removal of any partition is correctly dealt with by
 -- default partition table when it is being used in cached plan.
 create table list_parted (a int) partition by list(a);
-create table list_part_null partition of list_parted for values in (null);
-create table list_part_1 partition of list_parted for values in (1);
 create table list_part_def partition of list_parted default;
 prepare pstmt_def_insert (int) as insert into list_part_def values($1);
--- should fail
 execute pstmt_def_insert(null);
-ERROR:  new row for relation "list_part_def" violates partition constraint
-DETAIL:  Failing row contains (null).
+execute pstmt_def_insert(1);
+delete from list_parted where a=1;
+create table list_part_1 partition of list_parted for values in (1, 2);
+-- should fail
 execute pstmt_def_insert(1);
 ERROR:  new row for relation "list_part_def" violates partition constraint
 DETAIL:  Failing row contains (1).
-alter table list_parted detach partition list_part_null;
--- should be ok
+delete from list_parted where a is null;
+create table list_part_null (like list_parted);
+alter table list_parted attach partition list_part_null for values in (null);
+-- should fail
 execute pstmt_def_insert(null);
+ERROR:  new row for relation "list_part_def" violates partition constraint
+DETAIL:  Failing row contains (null).
 drop table list_part_1;
 -- should be ok
 execute pstmt_def_insert(1);
+alter table list_parted detach partition list_part_null;
+-- should be ok
+execute pstmt_def_insert(null);
 drop table list_parted;
 deallocate pstmt_def_insert;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 2346c1f..493acaf 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2115,13 +2115,13 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 
--- check partition cannot be attached if default exists
+-- check partition cannot be attached if default has some row for its values
 CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 INSERT INTO list_parted2_def VALUES (11, 'z');
 CREATE TABLE part_3 (LIKE list_parted2);
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
--- should be ok after detaching a default partition
-ALTER TABLE list_parted2 DETACH PARTITION list_parted2_def;
+-- should be ok after deleting the bad row
+DELETE FROM list_parted2_def WHERE a = 11;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
 
 -- adding constraints that describe the desired partition constraint
@@ -2191,6 +2191,18 @@ ALTER TABLE part_5 DROP CONSTRAINT check_a;
 ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT NULL;
 ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
 
+-- check that leaf partitions of default partition are scanned when
+-- attaching a partitioned table.
+ALTER TABLE part_5 DROP CONSTRAINT check_a;
+CREATE TABLE part5_def PARTITION OF part_5 DEFAULT PARTITION BY LIST(a);
+CREATE TABLE part5_def_p1 PARTITION OF part5_def FOR VALUES IN (5);
+INSERT INTO part5_def_p1 VALUES (5, 'y');
+CREATE TABLE part5_p1 (LIKE part_5);
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+-- should be ok after deleting the bad row
+DELETE FROM part5_def_p1 WHERE b = 'y';
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+
 -- check that the table being attached is not already a partition
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 71dbd6e..1eefa0e 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -439,20 +439,16 @@ CREATE TABLE list_parted (
 CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
 CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
 CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
 
 -- syntax does not allow empty list of values for list partitions
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
 -- trying to specify range for list partitioned table
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
 
--- check default partition cannot be created more than once
-CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
-CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
--- check that no partition can be created after default partition
-CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (3);
-
 -- specified literal can't be cast to the partition column data type
 CREATE TABLE bools (
 	a bool
@@ -532,9 +528,13 @@ CREATE TABLE list_parted2 (
 ) PARTITION BY LIST (a);
 CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
 CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+-- check default partition overlap
+INSERT INTO list_parted2 VALUES('X');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y');
 
 CREATE TABLE range_parted2 (
 	a int
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index ad6388a..7bf1778 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -118,27 +118,23 @@ create table list_parted (
 create table part_aa_bb partition of list_parted FOR VALUES IN ('aa', 'bb');
 create table part_cc_dd partition of list_parted FOR VALUES IN ('cc', 'dd');
 create table part_null partition of list_parted FOR VALUES IN (null);
+create table part_default partition of list_parted default;
 
 -- fail
 insert into part_aa_bb values ('cc', 1);
 insert into part_aa_bb values ('AAa', 1);
 insert into part_aa_bb values (null);
+insert into part_default values ('aa', 2);
+insert into part_default values (null, 2);
 -- ok
 insert into part_cc_dd values ('cC', 1);
 insert into part_null values (null, 0);
+insert into part_default values ('Zz', 2);
 
 -- check in case of multi-level partitioned table
 create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b);
 create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
 create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
-
--- test default partition
-create table part_default partition of list_parted default;
--- fail
-insert into part_default values ('aa', 2);
-insert into part_default values (null, 2);
--- ok
-insert into part_default values ('Zz', 2);
 -- check in case of multi-level default partitioned table
 drop table part_default;
 create table part_default partition of list_parted default partition by range(b);
@@ -157,9 +153,6 @@ insert into part_ee_ff2 values ('ff', 11);
 insert into part_default_p1 values ('cd', 25);
 insert into part_default_p2 values ('de', 35);
 insert into list_parted values ('ab', 21);
--- drop default, as we need to add some more partitions to test tuple routing
-select tableoid::regclass, * from list_parted;
-drop table part_default;
 
 -- Check tuple routing for partitioned tables
 
diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql
index 89ddf3f..e8814e1 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -160,18 +160,24 @@ select cachebug();
 -- Check that addition or removal of any partition is correctly dealt with by
 -- default partition table when it is being used in cached plan.
 create table list_parted (a int) partition by list(a);
-create table list_part_null partition of list_parted for values in (null);
-create table list_part_1 partition of list_parted for values in (1);
 create table list_part_def partition of list_parted default;
 prepare pstmt_def_insert (int) as insert into list_part_def values($1);
--- should fail
 execute pstmt_def_insert(null);
 execute pstmt_def_insert(1);
-alter table list_parted detach partition list_part_null;
--- should be ok
+delete from list_parted where a=1;
+create table list_part_1 partition of list_parted for values in (1, 2);
+-- should fail
+execute pstmt_def_insert(1);
+delete from list_parted where a is null;
+create table list_part_null (like list_parted);
+alter table list_parted attach partition list_part_null for values in (null);
+-- should fail
 execute pstmt_def_insert(null);
 drop table list_part_1;
 -- should be ok
 execute pstmt_def_insert(1);
+alter table list_parted detach partition list_part_null;
+-- should be ok
+execute pstmt_def_insert(null);
 drop table list_parted;
 deallocate pstmt_def_insert;
-- 
2.7.4

0006-Refactor-default-partitioning-to-re-use-code.patch0000664000175000017500000004151713136103112022407 0ustar  jeevanjeevanFrom 9eef741fbcb0f019a7294bbe857bf2c4930e7ef9 Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Wed, 26 Jul 2017 05:00:49 -0700
Subject: [PATCH 6/8] Refactor default partitioning to re-use code

This patch uses the new functions created in the first patch in this
series.

Ashutosh Bapat, revised by me.
---
 src/backend/catalog/partition.c           | 89 ++++++++++++++++++-------------
 src/backend/commands/tablecmds.c          | 69 ++++++++++++++++++++----
 src/include/catalog/partition.h           |  3 ++
 src/include/commands/tablecmds.h          |  4 ++
 src/test/regress/expected/alter_table.out |  8 ++-
 src/test/regress/sql/alter_table.sql      |  3 ++
 6 files changed, 126 insertions(+), 50 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index c7e7144..28dfa68 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -29,6 +29,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_type.h"
+#include "commands/tablecmds.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -144,8 +145,6 @@ static int32 partition_bound_cmp(PartitionKey key,
 static int partition_bound_bsearch(PartitionKey key,
 						PartitionBoundInfo boundinfo,
 						void *probe, bool probe_is_bound, bool *is_equal);
-static void check_default_allows_bound(Relation parent,
-						   PartitionBoundSpec *new_spec);
 
 /*
  * RelationBuildPartitionDesc
@@ -864,56 +863,45 @@ check_new_partition_bound(char *relname, Relation parent,
 						relname, get_rel_name(partdesc->oids[with])),
 				 parser_errposition(pstate, spec->location)));
 	}
-
-	/*
-	 * If the default partition exists, its partition constraints will change
-	 * after the addition of this new partition such that it won't allow any
-	 * row that qualifies for this new partition. So, check if the existing
-	 * data in the default partition satisfies this *would be* default
-	 * partition constraint. In case the new partition bound being checked
-	 * itself is a DEFAULT bound, this check shouldn't be triggered as there
-	 * won't already exists the default partition in such a case.
-	 */
-	if (boundinfo && partition_bound_has_default(boundinfo))
-		check_default_allows_bound(parent, spec);
 }
 
 /*
  * check_default_allows_bound
  *
  * This function checks if there exists a row in the default partition that
- * fits in the new partition and throws an error if it finds one.
+ * fits in the new partition being added and throws an error if it finds one.
  */
-static void
-check_default_allows_bound(Relation parent, PartitionBoundSpec *new_spec)
+void
+check_default_allows_bound(Relation parent, Relation default_rel,
+						   PartitionBoundSpec *new_spec)
 {
-	Relation	default_rel;
-	List	   *new_part_constraints = NIL;
+	List	   *new_part_constraints;
+	List	   *def_part_constraints;
 	List	   *all_parts;
 	ListCell   *lc;
-	PartitionDescData *part_desc = RelationGetPartitionDesc(parent);
 
 	/* 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 = (List *) eval_const_expressions(NULL,
-														   (Node *) new_part_constraints);
-	new_part_constraints =
-		(List *) canonicalize_qual((Expr *) new_part_constraints);
-	new_part_constraints = list_make1(make_ands_explicit(new_part_constraints));
+	def_part_constraints =
+		get_default_part_validation_constraint(new_part_constraints);
 
 	/*
-	 * Generate the constraint and default execution states.
-	 *
-	 * The default partition must be already having an AccessExclusiveLock.
+	 * If the existing constraints on the default partition imply that it will
+	 * not contain any row that would belong to the new partition, we can
+	 * avoid scanning the default partition.
 	 */
-	default_rel = heap_open(get_default_partition_oid(RelationGetRelid(parent)),
-							NoLock);
+	if (canSkipPartConstraintValidation(default_rel, def_part_constraints,
+										RelationGetPartitionKey(parent)))
+	{
+		ereport(INFO,
+				(errmsg("partition constraint for table \"%s\" is implied by existing constraints",
+						RelationGetRelationName(default_rel))));
+		return;
+	}
 
+	/* Bad luck, scan the default partition and its subpartitions, if any. */
 	if (default_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		all_parts = find_all_inheritors(RelationGetRelid(default_rel),
 										AccessExclusiveLock, NULL);
@@ -955,12 +943,14 @@ check_default_allows_bound(Relation parent, PartitionBoundSpec *new_spec)
 								RelationGetRelationName(part_rel),
 								RelationGetRelationName(default_rel))));
 
-			heap_close(part_rel, NoLock);
+			if (RelationGetRelid(default_rel) != RelationGetRelid(part_rel))
+				heap_close(part_rel, NoLock);
+
 			continue;
 		}
 
 		tupdesc = CreateTupleDescCopy(RelationGetDescr(part_rel));
-		constr = linitial(new_part_constraints);
+		constr = linitial(def_part_constraints);
 		partition_constraint = (Expr *) map_partition_varattnos((List *) constr,
 																1, part_rel, parent);
 		estate = CreateExecutorState();
@@ -984,7 +974,7 @@ check_default_allows_bound(Relation parent, PartitionBoundSpec *new_spec)
 			ExecStoreTuple(tuple, tupslot, InvalidBuffer, false);
 			econtext->ecxt_scantuple = tupslot;
 
-			if (ExecCheck(partqualstate, econtext))
+			if (!ExecCheck(partqualstate, econtext))
 				ereport(ERROR,
 						(errcode(ERRCODE_CHECK_VIOLATION),
 						 errmsg("updated partition constraint for default partition \"%s\" would be violated by some row",
@@ -999,8 +989,9 @@ check_default_allows_bound(Relation parent, PartitionBoundSpec *new_spec)
 		UnregisterSnapshot(snapshot);
 		ExecDropSingleTupleTableSlot(tupslot);
 		FreeExecutorState(estate);
-		/* keep our lock until commit */
-		heap_close(part_rel, NoLock);
+
+		if (RelationGetRelid(default_rel) != RelationGetRelid(part_rel))
+			heap_close(part_rel, NoLock);	/* keep the lock until commit */
 	}
 }
 
@@ -2647,3 +2638,25 @@ update_default_partition_oid(Oid parentId, Oid defaultPartId)
 	heap_freetuple(tuple);
 	heap_close(pg_partitioned_table, RowExclusiveLock);
 }
+
+/*
+ * get_default_part_validation_constraint
+ *
+ * Given partition constraints, this function returns *would be* default
+ * partition constraint.
+ */
+List *
+get_default_part_validation_constraint(List *new_part_constraints)
+{
+	Expr	   *defPartConstraint;
+
+	defPartConstraint = make_ands_explicit(new_part_constraints);
+	defPartConstraint = makeBoolExpr(NOT_EXPR,
+									 list_make1(defPartConstraint),
+									 -1);
+	defPartConstraint = (Expr *) eval_const_expressions(NULL,
+														(Node *) defPartConstraint);
+	defPartConstraint = canonicalize_qual(defPartConstraint);
+
+	return list_make1(defPartConstraint);
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index b875dbe..5449790 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -168,6 +168,8 @@ typedef struct AlteredTableInfo
 	bool		chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
 	char		newrelpersistence;	/* if above is true */
 	Expr	   *partition_constraint;	/* for attach partition validation */
+	/* true, if is a default partition or a child of default partition */
+	bool		is_default_partition;
 	/* Objects to rebuild after completing ALTER TYPE operations */
 	List	   *changedConstraintOids;	/* OIDs of constraints to rebuild */
 	List	   *changedConstraintDefs;	/* string definitions of same */
@@ -769,7 +771,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		ParseState *pstate;
 		Oid			parentId = linitial_oid(inheritOids),
 					defaultPartOid;
-		Relation	parent;
+		Relation	parent,
+					defaultRel;
 
 		/* Already have strong enough lock on the parent */
 		parent = heap_open(parentId, NoLock);
@@ -806,7 +809,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		 */
 		defaultPartOid = get_default_partition_oid(RelationGetRelid(parent));
 		if (OidIsValid(defaultPartOid))
-			LockRelationOid(defaultPartOid, AccessExclusiveLock);
+			defaultRel = heap_open(defaultPartOid, AccessExclusiveLock);
 
 		/* Tranform the bound values */
 		pstate = make_parsestate(NULL);
@@ -821,6 +824,20 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		 */
 		check_new_partition_bound(relname, parent, bound);
 
+		/*
+		 * If the default partition exists, its partition constraints will
+		 * change after the addition of this new partition such that it won't
+		 * allow any row that qualifies for this new partition. So, check that
+		 * the existing data in the default partition satisfies the constraint
+		 * as it will exist after adding this partition.
+		 */
+		if (OidIsValid(defaultPartOid))
+		{
+			check_default_allows_bound(parent, defaultRel, bound);
+			/* Keep the lock until commit. */
+			heap_close(defaultRel, NoLock);
+		}
+
 		/* Update the pg_class entry. */
 		StorePartitionBound(rel, parent, bound);
 
@@ -4611,9 +4628,16 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 			}
 
 			if (partqualstate && !ExecCheck(partqualstate, econtext))
-				ereport(ERROR,
-						(errcode(ERRCODE_CHECK_VIOLATION),
-						 errmsg("partition constraint is violated by some row")));
+			{
+				if (tab->is_default_partition)
+					ereport(ERROR,
+							(errcode(ERRCODE_CHECK_VIOLATION),
+							 errmsg("updated partition constraint for default partition would be violated by some row")));
+				else
+					ereport(ERROR,
+							(errcode(ERRCODE_CHECK_VIOLATION),
+							 errmsg("partition constraint is violated by some row")));
+			}
 
 			/* Write the tuple out to the new relation */
 			if (newrel)
@@ -13450,7 +13474,7 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
  *
  * The function returns true if it's safe to skip the scan, false otherwise.
  */
-static bool
+bool
 canSkipPartConstraintValidation(Relation scanRel, List *partConstraint,
 								PartitionKey key)
 {
@@ -13537,7 +13561,8 @@ canSkipPartConstraintValidation(Relation scanRel, List *partConstraint,
  */
 static void
 validatePartitionConstraints(List **wqueue, Relation scanRel,
-							 List *partConstraint, Relation rel)
+							 List *partConstraint, Relation rel,
+							 bool scanrel_is_default)
 {
 	PartitionKey key = RelationGetPartitionKey(rel);
 	List	   *all_parts;
@@ -13596,6 +13621,7 @@ validatePartitionConstraints(List **wqueue, Relation scanRel,
 		tab->partition_constraint = (Expr *)
 			map_partition_varattnos((List *) constr, 1,
 									part_rel, rel);
+		tab->is_default_partition = scanrel_is_default;
 		/* keep our lock until commit */
 		if (part_rel != scanRel)
 			heap_close(part_rel, NoLock);
@@ -13621,6 +13647,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	TupleDesc	tupleDesc;
 	ObjectAddress address;
 	Oid			defaultPartOid;
+	List	   *partBoundConstraint;
 	const char *trigger_name;
 
 	/*
@@ -13796,8 +13823,8 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	 * If the parent itself is a partition, make sure to include its
 	 * constraint as well.
 	 */
-	partConstraint = list_concat(get_qual_from_partbound(attachRel, rel,
-														 cmd->bound),
+	partBoundConstraint = get_qual_from_partbound(attachRel, rel, cmd->bound);
+	partConstraint = list_concat(partBoundConstraint,
 								 RelationGetPartitionQual(rel));
 
 	/* Skip validation if there are no constraints to validate. */
@@ -13809,7 +13836,29 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 		partConstraint = list_make1(make_ands_explicit(partConstraint));
 
 		/* Validate partition constraints against the table being attached. */
-		validatePartitionConstraints(wqueue, attachRel, partConstraint, rel);
+		validatePartitionConstraints(wqueue, attachRel, partConstraint, rel, false);
+
+		/*
+		 * Check whether default partition has a row that would fit the
+		 * partition being attached by negating the partition constraint
+		 * derived from the bounds(the partition constraint never evaluates to
+		 * NULL, so negating it like this is safe).
+		 */
+		defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
+		if (OidIsValid(defaultPartOid))
+		{
+			Relation	default_rel;
+			List	   *defPartConstraint;
+
+			/* We already have taken a lock on default partition. */
+			default_rel = heap_open(defaultPartOid, NoLock);
+			defPartConstraint = get_default_part_validation_constraint(partBoundConstraint);
+			validatePartitionConstraints(wqueue, default_rel, defPartConstraint,
+										 rel, true);
+
+			/* keep our lock until commit. */
+			heap_close(default_rel, NoLock);
+		}
 	}
 
 	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 7800c96..c3153d7 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -100,5 +100,8 @@ extern int get_partition_for_tuple(PartitionDispatch *pd,
 						TupleTableSlot **failed_slot);
 extern Oid	get_default_partition_oid(Oid parentId);
 extern void update_default_partition_oid(Oid parentId, Oid defaultPartId);
+extern void check_default_allows_bound(Relation parent, Relation defaultRel,
+						   PartitionBoundSpec *new_spec);
+extern List *get_default_part_validation_constraint(List *new_part_constaints);
 
 #endif							/* PARTITION_H */
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index abd31b6..bb40d88 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -18,6 +18,7 @@
 #include "catalog/dependency.h"
 #include "catalog/objectaddress.h"
 #include "nodes/parsenodes.h"
+#include "catalog/partition.h"
 #include "storage/lock.h"
 #include "utils/relcache.h"
 
@@ -87,4 +88,7 @@ extern void RangeVarCallbackOwnsTable(const RangeVar *relation,
 
 extern void RangeVarCallbackOwnsRelation(const RangeVar *relation,
 							 Oid relId, Oid oldRelId, void *noCatalogs);
+extern bool canSkipPartConstraintValidation(Relation scanRel, List *partConstraint,
+								PartitionKey key);
+
 #endif							/* TABLECMDS_H */
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 938fe28..88e4a89 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3296,7 +3296,7 @@ CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 INSERT INTO list_parted2_def VALUES (11, 'z');
 CREATE TABLE part_3 (LIKE list_parted2);
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
-ERROR:  updated partition constraint for default partition "list_parted2_def" would be violated by some row
+ERROR:  updated partition constraint for default partition would be violated by some row
 -- should be ok after deleting the bad row
 DELETE FROM list_parted2_def WHERE a = 11;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
@@ -3315,6 +3315,10 @@ ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
 ALTER TABLE part_3_4 ALTER a SET NOT NULL;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
 INFO:  partition constraint for table "part_3_4" is implied by existing constraints
+-- check if default partition scan skipped
+ALTER TABLE list_parted2_def ADD CONSTRAINT check_a CHECK (a IN (5, 6));
+CREATE TABLE part_7_8 PARTITION OF list_parted2 FOR VALUES IN (7, 8);
+INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
 -- check validation when attaching range partitions
 CREATE TABLE range_parted (
 	a int,
@@ -3369,7 +3373,7 @@ CREATE TABLE part5_def_p1 PARTITION OF part5_def FOR VALUES IN (5);
 INSERT INTO part5_def_p1 VALUES (5, 'y');
 CREATE TABLE part5_p1 (LIKE part_5);
 ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
-ERROR:  updated partition constraint for default partition "part5_def" would be violated by some row
+ERROR:  updated partition constraint for default partition would be violated by some row
 -- should be ok after deleting the bad row
 DELETE FROM part5_def_p1 WHERE b = 'y';
 ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 493acaf..38655ee 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2141,6 +2141,9 @@ ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
 ALTER TABLE part_3_4 ALTER a SET NOT NULL;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
 
+-- check if default partition scan skipped
+ALTER TABLE list_parted2_def ADD CONSTRAINT check_a CHECK (a IN (5, 6));
+CREATE TABLE part_7_8 PARTITION OF list_parted2 FOR VALUES IN (7, 8);
 
 -- check validation when attaching range partitions
 CREATE TABLE range_parted (
-- 
2.7.4

0007-Check-default-partitition-child-relations-predicate.patch0000664000175000017500000001106013136103112023774 0ustar  jeevanjeevanFrom 480ee0430b38ad1c3b8df0b31181b0c0ccf2e56f Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Wed, 26 Jul 2017 05:01:43 -0700
Subject: [PATCH 7/8] Check default partitition child relations predicate
 implication.

Add code to check_default_allows_bound() such that the default
partition children constraints are checked against new partition
constraints for implication and avoid scan of the child of which
existing constraints are implied by new default partition
constraints. Also, added testcase for the same.

Jeevan Ladhe.
---
 src/backend/catalog/partition.c           | 19 +++++++++++++++++++
 src/test/regress/expected/alter_table.out | 12 ++++++++++++
 src/test/regress/sql/alter_table.sql      |  9 +++++++++
 3 files changed, 40 insertions(+)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 28dfa68..45de96b 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -926,7 +926,26 @@ check_default_allows_bound(Relation parent, Relation default_rel,
 
 		/* Lock already taken above. */
 		if (part_relid != RelationGetRelid(default_rel))
+		{
 			part_rel = heap_open(part_relid, NoLock);
+
+			/*
+			 * If the partition constraints on default partition child imply
+			 * that it will not contain any row that would belong to the new
+			 * partition, we can avoid scanning the child table.
+			 */
+			if (canSkipPartConstraintValidation(part_rel,
+												def_part_constraints,
+												RelationGetPartitionKey(parent)))
+			{
+				ereport(INFO,
+						(errmsg("partition constraint for table \"%s\" is implied by existing constraints",
+								RelationGetRelationName(part_rel))));
+
+				heap_close(part_rel, NoLock);
+				continue;
+			}
+		}
 		else
 			part_rel = default_rel;
 
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 88e4a89..9a5f6bf 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3319,6 +3319,18 @@ INFO:  partition constraint for table "part_3_4" is implied by existing constrai
 ALTER TABLE list_parted2_def ADD CONSTRAINT check_a CHECK (a IN (5, 6));
 CREATE TABLE part_7_8 PARTITION OF list_parted2 FOR VALUES IN (7, 8);
 INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
+-- check if default partition child relation scan skipped
+DROP TABLE list_parted2_def;
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT PARTITION BY RANGE (a);
+CREATE TABLE list_parted2_def_p1 PARTITION OF list_parted2_def FOR VALUES FROM (21) TO (30);
+CREATE TABLE list_parted2_def_p2 PARTITION OF list_parted2_def FOR VALUES FROM (31) TO (40);
+ALTER TABLE list_parted2_def_p1 ADD CONSTRAINT check_a CHECK (a IN (21, 22));
+ALTER TABLE list_parted2_def_p2 ADD CONSTRAINT check_a CHECK (a IN (31, 32));
+CREATE TABLE part_9 PARTITION OF list_parted2 FOR VALUES IN (9);
+INFO:  partition constraint for table "list_parted2_def_p1" is implied by existing constraints
+INFO:  partition constraint for table "list_parted2_def_p2" is implied by existing constraints
+CREATE TABLE part_31 PARTITION OF list_parted2 FOR VALUES IN (31);
+INFO:  partition constraint for table "list_parted2_def_p1" is implied by existing constraints
 -- check validation when attaching range partitions
 CREATE TABLE range_parted (
 	a int,
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 38655ee..935b24b 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2144,6 +2144,15 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
 -- check if default partition scan skipped
 ALTER TABLE list_parted2_def ADD CONSTRAINT check_a CHECK (a IN (5, 6));
 CREATE TABLE part_7_8 PARTITION OF list_parted2 FOR VALUES IN (7, 8);
+-- check if default partition child relation scan skipped
+DROP TABLE list_parted2_def;
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT PARTITION BY RANGE (a);
+CREATE TABLE list_parted2_def_p1 PARTITION OF list_parted2_def FOR VALUES FROM (21) TO (30);
+CREATE TABLE list_parted2_def_p2 PARTITION OF list_parted2_def FOR VALUES FROM (31) TO (40);
+ALTER TABLE list_parted2_def_p1 ADD CONSTRAINT check_a CHECK (a IN (21, 22));
+ALTER TABLE list_parted2_def_p2 ADD CONSTRAINT check_a CHECK (a IN (31, 32));
+CREATE TABLE part_9 PARTITION OF list_parted2 FOR VALUES IN (9);
+CREATE TABLE part_31 PARTITION OF list_parted2 FOR VALUES IN (31);
 
 -- check validation when attaching range partitions
 CREATE TABLE range_parted (
-- 
2.7.4

0008-Documentation-for-default-partition.patch0000664000175000017500000002123013136103112021003 0ustar  jeevanjeevanFrom 83d761598ed49ee892050fea5ac90253b004b2c7 Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Wed, 26 Jul 2017 05:03:03 -0700
Subject: [PATCH 8/8] Documentation for default partition.

Jeevan Ladhe.
---
 doc/src/sgml/ref/alter_table.sgml  | 46 +++++++++++++++++++++++++++-----------
 doc/src/sgml/ref/create_table.sgml | 30 ++++++++++++++++++++++---
 2 files changed, 60 insertions(+), 16 deletions(-)

diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 6960032..abea3a2 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -34,7 +34,7 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
 ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable> [ OWNED BY <replaceable class="PARAMETER">role_name</replaceable> [, ... ] ]
     SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable> [ NOWAIT ]
 ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
-    ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+    ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> { DEFAULT | FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> }
 ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
     DETACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable>
 
@@ -765,24 +765,33 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
    </varlistentry>
 
    <varlistentry>
-    <term><literal>ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable></literal></term>
+    <term><literal>ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> { DEFAULT | FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> }</literal></term>
     <listitem>
      <para>
       This form attaches an existing table (which might itself be partitioned)
-      as a partition of the target table using the same syntax for
+      as a partition of the target table. The table can be attached as a
+      default partition by using <literal>DEFAULT</literal> or a partition for
+      specific values using <literal>FOR VALUES</literal>.
+     </para>
+
+     <para>
+      A partition using <literal>FOR VALUES</literal> uses same syntax for
       <replaceable class="PARAMETER">partition_bound_spec</replaceable> as
       <xref linkend="sql-createtable">.  The partition bound specification
       must correspond to the partitioning strategy and partition key of the
-      target table.  The table to be attached must have all the same columns
-      as the target table and no more; moreover, the column types must also
-      match.  Also, it must have all the <literal>NOT NULL</literal> and
-      <literal>CHECK</literal> constraints of the target table.  Currently
-      <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and
-      <literal>FOREIGN KEY</literal> constraints are not considered.
-      If any of the <literal>CHECK</literal> constraints of the table being
-      attached is marked <literal>NO INHERIT</literal>, the command will fail;
-      such a constraint must be recreated without the <literal>NO INHERIT</literal>
-      clause.
+      target table.
+     </para>
+
+     <para>
+      The table to be attached must have all the same columns as the target
+      table and no more; moreover, the column types must also match.  Also, it
+      must have all the <literal>NOT NULL</literal> and <literal>CHECK</literal>
+      constraints of the target table.  Currently <literal>UNIQUE</literal>,
+      <literal>PRIMARY KEY</literal>, and <literal>FOREIGN KEY</literal>
+      constraints are not considered. If any of the <literal>CHECK</literal>
+      constraints of the table being attached is marked <literal>NO INHERIT
+      </literal>, the command will fail; such a constraint must be recreated
+      without the <literal>NO INHERIT</literal> `clause.
      </para>
 
      <para>
@@ -798,6 +807,10 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       a list partition that will not accept <literal>NULL</literal> values,
       also add <literal>NOT NULL</literal> constraint to the partition key
       column, unless it's an expression.
+      Also, if there exists a default partition table for the parent table,
+      then the default partition(if it is a regular table) is scanned to check
+      that no existing row in default partition would fit in the partition that
+      is being attached.
      </para>
 
      <para>
@@ -1396,6 +1409,13 @@ ALTER TABLE cities
 </programlisting></para>
 
   <para>
+   Attach a default partition to a partitioned table:
+<programlisting>
+ALTER TABLE cities
+    ATTACH PARTITION cities_partdef DEFAULT;
+</programlisting></para>
+
+  <para>
    Detach a partition from partitioned table:
 <programlisting>
 ALTER TABLE measurement
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index e9c2c49..d0044dc 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -49,7 +49,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
   { <replaceable class="PARAMETER">column_name</replaceable> [ WITH OPTIONS ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
     | <replaceable>table_constraint</replaceable> }
     [, ... ]
-) ] FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+) ] { DEFAULT | FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> }
 [ PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
 [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
 [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
@@ -250,11 +250,13 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
    </varlistentry>
 
    <varlistentry id="SQL-CREATETABLE-PARTITION">
-    <term><literal>PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable> FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable></literal></term>
+    <term><literal>PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable> { DEFAULT | FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> }</literal></term>
     <listitem>
      <para>
       Creates the table as a <firstterm>partition</firstterm> of the specified
-      parent table.
+      parent table. The table can be created either as a default partition
+      using <literal>DEFAULT</literal> or a partition for specific values using
+      <literal>FOR VALUES</literal>.
      </para>
 
      <para>
@@ -343,6 +345,21 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
      </para>
 
      <para>
+     If <literal>DEFAULT</literal> is specified the table will be created as a
+     default partition of the parent table. The parent can either be a list or
+     range partitioned table. A partition key value not fitting into any other
+     partition of the given parent will be routed to the default partition.
+     There can be only one default partition for a given parent table.
+     </para>
+
+     <para>
+     If the given parent is already having a default partition then adding a
+     new partition would result in an error if the default partition contains a
+     record that would fit in the new partition being added. This check is not
+     performed if the default partition is a foreign table.
+     </para>
+
+     <para>
       A partition must have the same column names and types as the partitioned
       table to which it belongs.  If the parent is specified <literal>WITH
       OIDS</literal> then all partitions must have OIDs; the parent's OID
@@ -1679,6 +1696,13 @@ CREATE TABLE cities_ab
 CREATE TABLE cities_ab_10000_to_100000
     PARTITION OF cities_ab FOR VALUES FROM (10000) TO (100000);
 </programlisting></para>
+
+  <para>
+   Create a default partition of partitioned table:
+<programlisting>
+CREATE TABLE cities_partdef
+    PARTITION OF cities DEFAULT;
+</programlisting></para>
  </refsect1>
 
  <refsect1 id="SQL-CREATETABLE-compatibility">
-- 
2.7.4

#153Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Jeevan Ladhe (#152)
Re: Adding support for Default partition in partitioning

On Wed, Jul 26, 2017 at 5:44 PM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

Hi,

I have rebased the patches on the latest commit.

Thanks for rebasing the patches. The patches apply and compile
cleanly. make check passes.

Here are some review comments
0001 patch
Most of this patch is same as 0002 patch posted in thread [1]/messages/by-id/cee32590-68a7-8b56-5213-e07d9b8ab89e@lab.ntt.co.jp. I have
extensively reviewed that patch for Amit Langote. Can you please compare these
two patches and try to address those comments OR just use patch from that
thread? For example, canSkipPartConstraintValidation() is named as
PartConstraintImpliedByRelConstraint() in that patch. OR
+ if (scanRel_constr == NULL)
+ return false;
+
is not there in that patch since returning false is wrong when partConstraint
is NULL. I think this patch needs those fixes. Also, this patch set would need
a rebase when 0001 from that thread gets committed.

0002 patch
+ if (!and_args)
+ result = NULL;
Add "NULL, if there are not partition constraints e.g. in case of default
partition as the only partition.". This patch avoids calling
validatePartitionConstraints() and hence canSkipPartConstraintValidation() when
partConstraint is NULL, but patches in [1]/messages/by-id/cee32590-68a7-8b56-5213-e07d9b8ab89e@lab.ntt.co.jp introduce more callers of
canSkipPartConstraintValidation() which may pass NULL. So, it's better that we
handle that case.

0003 patch
+        parentRel = heap_open(parentOid, AccessExclusiveLock);
In [2], Amit Langote has given a reason as to why heap_drop_with_catalog()
should not heap_open() the parent relation. But this patch still calls
heap_open() without giving any counter argument. Also I don't see
get_default_partition_oid() using Relation anywhere. If you remove that
heap_open() please remove following heap_close().
+        heap_close(parentRel, NoLock);
+                        /*
+                         * The default partition accepts any non-specified
+                         * value, hence it should not get a mapped index while
+                         * assigning those for non-null datums.
+                         */
Instead of "any non-specified value", you may want to use "any value not
specified in the lists of other partitions" or something like that.
+         * If this is a NULL, route it to the null-accepting partition.
+         * Otherwise, route by searching the array of partition bounds.
You may want to write it as "If this is a null partition key, ..." to clarify
what's NULL.
+         * cur_index < 0 means we could not find a non-default partition of
+         * this parent. cur_index >= 0 means we either found the leaf
+         * partition, or the next parent to find a partition of.
+         *
+         * If we couldn't find a non-default partition check if the default
+         * partition exists, if it does, get its index.
In order to avoid repeating "we couldn't find a ..."; you may want to add ",
try default partition if one exists." in the first sentence itself.

get_default_partition_oid() is defined in this patch and then redefined in
0004. Let's define it only once, mostly in or before 0003 patch.

+ * partition strategy. Assign the parent strategy to the default
s/parent/parent's/

+-- attaching default partition overlaps if the default partition already exists
+CREATE TABLE def_part PARTITION OF list_parted DEFAULT;
+CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT;
+ERROR:  cannot attach a new partition to table "list_parted" having a
default partition
For 0003 patch this testcase is same as the testcase in the next hunk; no new
partition can be added after default partition. Please add this testcase in
next set of patches.
+-- fail
+insert into part_default values ('aa', 2);
May be explain why the insert should fail. "A row, which would fit
other partition, does not fit default partition, even when inserted directly"
or something like that. I see that many of the tests in that file do not
explain why something should "fail" or be "ok", but may be it's better to
document the reason for better readability and future reference.

+-- check in case of multi-level default partitioned table
s/in/the/ ?. Or you may want to reword it as "default partitioned partition in
multi-level partitioned table" as there is nothing like "default partitioned
table". May be we need a testcase where every level of a multi-level
partitioned table has a default partition.

+-- drop default, as we need to add some more partitions to test tuple routing
Should be clubbed with the actual DROP statement?

+-- Check that addition or removal of any partition is correctly dealt with by
+-- default partition table when it is being used in cached plan.
Plan of a prepared statement gets cached only after it's executed 5 times.
Before that the statement gets invalidated but there's not cached plan that
gets invalidated. The test is fine here, but in order to test the cached plan
as mentioned in the comment, you will need to execute the statement 5 times
before executing drop statement. That's probably unnecessary, so just modify
the comment to say "prepared statements instead of cached plan".

0004 patch
The patch adds another column partdefid to catalog pg_partitioned_table. The
column gives OID of the default partition for a given partitioned table. This
means that the default partition's OID is stored at two places 1. in the
default partition table's pg_class entry and in pg_partitioned_table. There is
no way to detect when these two go out of sync. Keeping those two in sync is
also a maintenance burdern. Given that default partition's OID is required only
while adding/dropping a partition, which is a less frequent operation, it won't
hurt to join a few catalogs (pg_inherits and pg_class in this case) to find out
the default partition's OID. That will be occasional performance hit
worth the otherwise maintenance burden.

I haven't reviewed next two patches, but those patches depend upon
some of the comments above. So, it's better to consider these comments
before looking at those patches.

[1]: /messages/by-id/cee32590-68a7-8b56-5213-e07d9b8ab89e@lab.ntt.co.jp
[2]: /messages/by-id/35d68d49-555f-421a-99f8-185a44d085a4@lab.ntt.co.jp

--
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

#154Robert Haas
robertmhaas@gmail.com
In reply to: Ashutosh Bapat (#153)
Re: Adding support for Default partition in partitioning

On Fri, Jul 28, 2017 at 9:30 AM, Ashutosh Bapat
<ashutosh.bapat@enterprisedb.com> wrote:

0004 patch
The patch adds another column partdefid to catalog pg_partitioned_table. The
column gives OID of the default partition for a given partitioned table. This
means that the default partition's OID is stored at two places 1. in the
default partition table's pg_class entry and in pg_partitioned_table. There is
no way to detect when these two go out of sync. Keeping those two in sync is
also a maintenance burdern. Given that default partition's OID is required only
while adding/dropping a partition, which is a less frequent operation, it won't
hurt to join a few catalogs (pg_inherits and pg_class in this case) to find out
the default partition's OID. That will be occasional performance hit
worth the otherwise maintenance burden.

Performance isn't the only consideration here. We also need to think
about locking and concurrency. I think that most operations that
involve locking the parent will also involve locking the default
partition. However, we can't safely build a relcache entry for a
relation before we've got some kind of lock on it. We can't assume
that there is no concurrent DDL going on before we take some lock. We
can't assume invalidation messages are processed before we have taken
some lock. If we read multiple catalog tuples, they may be from
different points in time. If we can figure out everything we need to
know from one or two syscache lookups, it may be easier to verify that
the code is bug-free vs. having to do something more complicated.

Now that having been said, I'm not taking the position that Jeevan's
patch (based on Amit Langote's idea) has definitely got the right
idea, just that you should think twice before shooting down the
approach.

--
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

#155Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Ashutosh Bapat (#153)
Re: Adding support for Default partition in partitioning

Hi Ashutosh,

0003 patch

+ parentRel = heap_open(parentOid, AccessExclusiveLock);
In [2], Amit Langote has given a reason as to why heap_drop_with_catalog()
should not heap_open() the parent relation. But this patch still calls
heap_open() without giving any counter argument. Also I don't see
get_default_partition_oid() using Relation anywhere. If you remove that
heap_open() please remove following heap_close().

I think the patch 0004 exactly does what you have said here, i.e. it gets
rid of the heap_open() and heap_close().
The question might be why I kept the patch 0004 a separate one, and the
answer is I wanted to make it easier for review, and also keeping it that
way would make it bit easy to work on a different approach if needed.

About this: *"Also I don't see get_default_partition_oid() using Relation
anywhere."*
The get_default_partition_oid() uses parent relation to
retrieve PartitionDesc
from parent.

Kindly let me know if you think I am still missing anything.

Regards,
Jeevan Ladhe

#156Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Robert Haas (#154)
Re: Adding support for Default partition in partitioning

On Sat, Jul 29, 2017 at 2:55 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Fri, Jul 28, 2017 at 9:30 AM, Ashutosh Bapat
<ashutosh.bapat@enterprisedb.com> wrote:

0004 patch
The patch adds another column partdefid to catalog pg_partitioned_table. The
column gives OID of the default partition for a given partitioned table. This
means that the default partition's OID is stored at two places 1. in the
default partition table's pg_class entry and in pg_partitioned_table. There is
no way to detect when these two go out of sync. Keeping those two in sync is
also a maintenance burdern. Given that default partition's OID is required only
while adding/dropping a partition, which is a less frequent operation, it won't
hurt to join a few catalogs (pg_inherits and pg_class in this case) to find out
the default partition's OID. That will be occasional performance hit
worth the otherwise maintenance burden.

Performance isn't the only consideration here. We also need to think
about locking and concurrency. I think that most operations that
involve locking the parent will also involve locking the default
partition. However, we can't safely build a relcache entry for a
relation before we've got some kind of lock on it. We can't assume
that there is no concurrent DDL going on before we take some lock. We
can't assume invalidation messages are processed before we have taken
some lock. If we read multiple catalog tuples, they may be from
different points in time. If we can figure out everything we need to
know from one or two syscache lookups, it may be easier to verify that
the code is bug-free vs. having to do something more complicated.

The code takes a lock on the parent relation. While that function
holds that lock nobody else would change partitions of that relation
and hence nobody changes the default partition.
heap_drop_with_catalog() has code to lock the parent. Looking up
pg_inherits catalog for its partitions followed by identifying the
partition which has default partition bounds specification while
holding the lock on the parent should be safe. Any changes to
partition bounds, or partitions would require lock on the parent. In
order to prevent any buggy code changing the default partition without
sufficient locks, we should lock the default partition after it's
found and check the default partition bound specification again. Will
that work?

Now that having been said, I'm not taking the position that Jeevan's
patch (based on Amit Langote's idea) has definitely got the right
idea, just that you should think twice before shooting down the
approach.

If we can avoid the problems specified by Amit Langote, I am fine with
the approach of reading the default partition OID from the Relcache as
well. But I am not able to device a solution to those problems.

--
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

#157Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Jeevan Ladhe (#155)
Re: Adding support for Default partition in partitioning

On Sun, Jul 30, 2017 at 8:07 AM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

Hi Ashutosh,

0003 patch

+ parentRel = heap_open(parentOid, AccessExclusiveLock);
In [2], Amit Langote has given a reason as to why heap_drop_with_catalog()
should not heap_open() the parent relation. But this patch still calls
heap_open() without giving any counter argument. Also I don't see
get_default_partition_oid() using Relation anywhere. If you remove that
heap_open() please remove following heap_close().

I think the patch 0004 exactly does what you have said here, i.e. it gets
rid of the heap_open() and heap_close().
The question might be why I kept the patch 0004 a separate one, and the
answer is I wanted to make it easier for review, and also keeping it that
way would make it bit easy to work on a different approach if needed.

The reviewer has to review two different set of changes to the same
portion of the code. That just doubles the work. I didn't find that
simplifying review. As I have suggested earlier, let's define
get_default_partition_oid() only once, mostly in or before 0003 patch.
Having it in a separate patch would allow you to change its
implementation if needed.

--
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

#158Robert Haas
robertmhaas@gmail.com
In reply to: Jeevan Ladhe (#146)
Re: Adding support for Default partition in partitioning

On Wed, Jul 12, 2017 at 3:31 PM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

0001:
Refactoring existing ATExecAttachPartition code so that it can be used for
default partitioning as well

Boring refactoring. Seems fine.

0002:
This patch teaches the partitioning code to handle the NIL returned by
get_qual_for_list().
This is needed because a default partition will not have any constraints in
case
it is the only partition of its parent.

Perhaps it would be better to make validatePartConstraint() a no-op
when the constraint is empty rather than putting the logic in the
caller. Otherwise, every place that calls validatePartConstraint()
has to think about whether or not the constraint-is-NULL case needs to
be handled.

0003:
Support for default partition with the restriction of preventing addition of
any
new partition after default partition.

This looks generally reasonable, but can't really be committed without
the later patches, because it might break pg_dump, which won't know
that the DEFAULT partition must be dumped last and might therefore get
the dump ordering wrong, and of course also because it reverts commit
c1e0e7e1d790bf18c913e6a452dea811e858b554.

0004:
Store the default partition OID in pg_partition_table, this will help us to
retrieve the OID of default relation when we don't have the relation cache
available. This was also suggested by Amit Langote here[1].

I looked this over and I think this is the right approach. An
alternative way to avoid needing a relcache entry in
heap_drop_with_catalog() would be for get_default_partition_oid() to
call find_inheritance_children() here and then use a syscache lookup
to get the partition bound for each one, but that's still going to
cause some syscache bloat.

0005:
Extend default partitioning support to allow addition of new partitions.

+       if (spec->is_default)
+       {
+               /* Default partition cannot be added if there already
exists one. */
+               if (partdesc->nparts > 0 &&
partition_bound_has_default(boundinfo))
+               {
+                       with = boundinfo->default_index;
+                       ereport(ERROR,
+
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                                        errmsg("partition \"%s\"
conflicts with existing default partition \"%s\"",
+                                                       relname,
get_rel_name(partdesc->oids[with])),
+                                        parser_errposition(pstate,
spec->location)));
+               }
+
+               return;
+       }

I generally think it's good to structure the code so as to minimize
the indentation level. In this case, if you did if (partdesc->nparts
== 0 || !partition_bound_has_default(boundinfo)) return; first, then
the rest of it could be one level less indented. Also, perhaps it
would be clearer to test boundinfo == NULL rather than
partdesc->nparts == 0, assuming they are equivalent.

-        * We must also lock the default partition, for the same
reasons explained
-        * in heap_drop_with_catalog().
+        * We must lock the default partition, for the same reasons explained in
+        * DefineRelation().

I don't really see the point of this change. Whichever earlier patch
adds this code could include or omit the word "also" as appropriate,
and then this patch wouldn't need to change it.

0006:
Extend default partitioning validation code to reuse the refactored code in
patch 0001.

I'm having a very hard time understanding what's going on with this
patch. It certainly doesn't seem to be just refactoring things to use
the code from 0001. For example:

-                       if (ExecCheck(partqualstate, econtext))
+                       if (!ExecCheck(partqualstate, econtext))

It seems hard to believe that refactoring things to use the code from
0001 would involve inverting the value of this test.

+                * derived from the bounds(the partition constraint
never evaluates to
+                * NULL, so negating it like this is safe).

I don't see it being negated.

I think this patch needs a better explanation of what it's trying to
do, and better comments. I gather that at least part of the point
here is to skip validation scans on default partitions if the default
partition has been constrained not to contain any values that would
fall in the new partition, but neither the commit message for 0006 nor
your description here make that very clear.

0007:
This patch introduces code to check if the scanning of default partition
child
can be skipped if it's constraints are proven.

If I understand correctly, this is actually a completely separate
feature not intrinsically related to default partitioning.

[0008 documentation]

-      attached is marked <literal>NO INHERIT</literal>, the command will fail;
-      such a constraint must be recreated without the <literal>NO
INHERIT</literal>
-      clause.
+      target table.
+     </para>

I don't favor inserting a paragraph break here.

+ then the default partition(if it is a regular table) is scanned to check

The sort-of-trivial problem with this is that an open parenthesis
should be proceeded by a space. But I think this won't be clear. I
think you should move this below the following paragraph, which
describes what happens for foreign tables, and then add a new
paragraph like this:

When a table has a default partition, defining a new partition changes
the partition constraint for the default partition. The default
partition can't contain any rows that would need to be moved to the
new partition, and will be scanned to verify that none are present.
This scan, like the scan of the new partition, can be avoided if an
appropriate <literal>CHECK</literal> constraint is present. Also like
the scan of the new partition, it is always skipped when the default
partition is a foreign table.

-) ] FOR VALUES <replaceable
class="PARAMETER">partition_bound_spec</replaceable>
+) ] { DEFAULT | FOR VALUES <replaceable
class="PARAMETER">partition_bound_spec</replaceable> }

I recommend writing FOR VALUES | DEFAULT both here and in the ATTACH
PARTITION syntax summary.

+     If <literal>DEFAULT</literal> is specified the table will be created as a
+     default partition of the parent table. The parent can either be a list or
+     range partitioned table. A partition key value not fitting into any other
+     partition of the given parent will be routed to the default partition.
+     There can be only one default partition for a given parent table.
+     </para>
+
+     <para>
+     If the given parent is already having a default partition then adding a
+     new partition would result in an error if the default partition contains a
+     record that would fit in the new partition being added. This check is not
+     performed if the default partition is a foreign table.
+     </para>

The indentation isn't correct here - it doesn't match the surrounding
paragraphs. The bit about list or range partitioning doesn't match
the actual behavior of the other patches, but maybe you intended this
to document both this feature and what Beena's doing.

--
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

#159Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Robert Haas (#158)
Re: Adding support for Default partition in partitioning

Hi Robert,

0005:

Extend default partitioning support to allow addition of new partitions.

+       if (spec->is_default)
+       {
+               /* Default partition cannot be added if there already
exists one. */
+               if (partdesc->nparts > 0 &&
partition_bound_has_default(boundinfo))
+               {
+                       with = boundinfo->default_index;
+                       ereport(ERROR,
+
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                                        errmsg("partition \"%s\"
conflicts with existing default partition \"%s\"",
+                                                       relname,
get_rel_name(partdesc->oids[with])),
+                                        parser_errposition(pstate,
spec->location)));
+               }
+
+               return;
+       }

I generally think it's good to structure the code so as to minimize
the indentation level. In this case, if you did if (partdesc->nparts
== 0 || !partition_bound_has_default(boundinfo)) return; first, then
the rest of it could be one level less indented. Also, perhaps it
would be clearer to test boundinfo == NULL rather than
partdesc->nparts == 0, assuming they are equivalent.

I think even with this change there will be one level of indentation
needed for throwing the error, as the error is to be thrown only if
there exists a default partition.

- * We must also lock the default partition, for the same

reasons explained
-        * in heap_drop_with_catalog().
+        * We must lock the default partition, for the same reasons
explained in
+        * DefineRelation().

I don't really see the point of this change. Whichever earlier patch
adds this code could include or omit the word "also" as appropriate,
and then this patch wouldn't need to change it.

Actually the change is made because if the difference in the function name.
I will remove ‘also’ from the first patch itself.

0007:
This patch introduces code to check if the scanning of default partition
child
can be skipped if it's constraints are proven.

If I understand correctly, this is actually a completely separate
feature not intrinsically related to default partitioning.

I don't see this as a new feature, since scanning the default partition
will be introduced by this series of patches only, and rather than a
feature this can be classified as a completeness of default skip
validation logic. Your thoughts?

Regards,
Jeevan Ladhe

#160Robert Haas
robertmhaas@gmail.com
In reply to: Jeevan Ladhe (#159)
Re: Adding support for Default partition in partitioning

On Mon, Aug 14, 2017 at 7:51 AM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

I think even with this change there will be one level of indentation
needed for throwing the error, as the error is to be thrown only if
there exists a default partition.

That's true, but we don't need two levels.

0007:
This patch introduces code to check if the scanning of default partition
child
can be skipped if it's constraints are proven.

If I understand correctly, this is actually a completely separate
feature not intrinsically related to default partitioning.

I don't see this as a new feature, since scanning the default partition
will be introduced by this series of patches only, and rather than a
feature this can be classified as a completeness of default skip
validation logic. Your thoughts?

Currently, when a partitioned table is attached, we check whether all
the scans can be checked but not whether scans on some partitions can
be attached. So there are two separate things:

1. When we introduce default partitioning, we need scan the default
partition either when (a) any partition is attached or (b) any
partition is created.

2. In any situation where scans are needed (scanning the partition
when it's attached, scanning the default partition when some other
partition is attached, scanning the default when a new partition is
created), we can run predicate_implied_by for each partition to see
whether the scan of that partition can be skipped.

Those two changes are independent. We could do (1) without doing (2)
or (2) without doing (1) or we could do both. So they are separate
features.

--
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

#161Robert Haas
robertmhaas@gmail.com
In reply to: Jeevan Ladhe (#152)
Re: Adding support for Default partition in partitioning

On Wed, Jul 26, 2017 at 8:14 AM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

I have rebased the patches on the latest commit.

This needs another rebase.

--
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

#162Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Robert Haas (#161)
1 attachment(s)
Re: Adding support for Default partition in partitioning

Hi,

On Tue, Aug 15, 2017 at 7:20 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Wed, Jul 26, 2017 at 8:14 AM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

I have rebased the patches on the latest commit.

This needs another rebase.

I have rebased the patch and addressed your and Ashutosh comments on last
set of patches.

The current set of patches contains 6 patches as below:

0001:
Refactoring existing ATExecAttachPartition code so that it can be used for
default partitioning as well

0002:
This patch teaches the partitioning code to handle the NIL returned by
get_qual_for_list().
This is needed because a default partition will not have any constraints in
case
it is the only partition of its parent.

0003:
Support for default partition with the restriction of preventing addition
of any
new partition after default partition. This is a merge of 0003 and 0004 in
V24 series.

0004:
Extend default partitioning support to allow addition of new partitions
after
default partition is created/attached. This patch is a merge of patches
0005 and 0006 in V24 series to simplify the review process. The
commit message has more details regarding what all is included.

0005:
This patch introduces code to check if the scanning of default partition
child
can be skipped if it's constraints are proven.

0006:
Documentation.

PFA, and let me know in case of any comments.

Regards,
Jeevan Ladhe

Attachments:

default_partition_V25.tarapplication/x-tar; name=default_partition_V25.tarDownload
0001-Refactor-ATExecAttachPartition.patch0000664000175000017500000002620213145251221017652 0ustar  jeevanjeevanFrom 51779f18156464bc5e74420faf75f0e9e7fb25f3 Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Thu, 17 Aug 2017 13:09:33 +0530
Subject: [PATCH 1/6] Refactor ATExecAttachPartition

Move code to validate the table being attached against the partition
constraints into set of functions. This will be used for validating
the changed default partition constraints when a new partition is
added.

Ashutosh Bapat, revised by me.
---
 src/backend/commands/tablecmds.c | 318 +++++++++++++++++++++------------------
 1 file changed, 172 insertions(+), 146 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 513a9ec..83cb460 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -473,6 +473,11 @@ static void CreateInheritance(Relation child_rel, Relation parent_rel);
 static void RemoveInheritance(Relation child_rel, Relation parent_rel);
 static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
 					  PartitionCmd *cmd);
+static bool PartConstraintImpliedByRelConstraint(Relation scanrel,
+									 List *partConstraint);
+static void ValidatePartitionConstraints(List **wqueue, Relation scanrel,
+							 List *scanrel_children,
+							 List *partConstraint);
 static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
 
 
@@ -13425,6 +13430,169 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
 }
 
 /*
+ * PartConstraintImpliedByRelConstraint
+ *		Does scanrel's existing constraints imply the partition constraint?
+ *
+ * Existing constraints includes its check constraints and column-level
+ * NOT NULL constraints and partConstraint describes the partition constraint.
+ */
+static bool
+PartConstraintImpliedByRelConstraint(Relation scanrel,
+									 List *partConstraint)
+{
+	List	   *existConstraint = NIL;
+	TupleConstr *constr = RelationGetDescr(scanrel)->constr;
+	int			num_check,
+				i;
+
+	if (constr && constr->has_not_null)
+	{
+		int			natts = scanrel->rd_att->natts;
+
+		for (i = 1; i <= natts; i++)
+		{
+			Form_pg_attribute att = scanrel->rd_att->attrs[i - 1];
+
+			if (att->attnotnull && !att->attisdropped)
+			{
+				NullTest   *ntest = makeNode(NullTest);
+
+				ntest->arg = (Expr *) makeVar(1,
+											  i,
+											  att->atttypid,
+											  att->atttypmod,
+											  att->attcollation,
+											  0);
+				ntest->nulltesttype = IS_NOT_NULL;
+
+				/*
+				 * argisrow=false is correct even for a composite column,
+				 * because attnotnull does not represent a SQL-spec IS NOT
+				 * NULL test in such a case, just IS DISTINCT FROM NULL.
+				 */
+				ntest->argisrow = false;
+				ntest->location = -1;
+				existConstraint = lappend(existConstraint, ntest);
+			}
+		}
+	}
+
+	num_check = (constr != NULL) ? constr->num_check : 0;
+	for (i = 0; i < num_check; i++)
+	{
+		Node	   *cexpr;
+
+		/*
+		 * If this constraint hasn't been fully validated yet, we must ignore
+		 * it here.
+		 */
+		if (!constr->check[i].ccvalid)
+			continue;
+
+		cexpr = stringToNode(constr->check[i].ccbin);
+
+		/*
+		 * Run each expression through const-simplification and
+		 * canonicalization.  It is necessary, because we will be comparing it
+		 * to similarly-processed partition constraint expressions, and may
+		 * fail to detect valid matches without this.
+		 */
+		cexpr = eval_const_expressions(NULL, cexpr);
+		cexpr = (Node *) canonicalize_qual((Expr *) cexpr);
+
+		existConstraint = list_concat(existConstraint,
+									  make_ands_implicit((Expr *) cexpr));
+	}
+
+	if (existConstraint != NIL)
+		existConstraint = list_make1(make_ands_explicit(existConstraint));
+
+	/* And away we go ... */
+	return predicate_implied_by(partConstraint, existConstraint, true);
+}
+
+/*
+ * ValidatePartitionConstraints
+ *
+ * Check whether all rows in the given table obey the given partition
+ * constraint; if so, it can be attached as a partition.  We do this by
+ * scanning the table (or all of its leaf partitions) row by row, except when
+ * the existing constraints are sufficient to prove that the new partitioning
+ * constraint must already hold.
+ */
+static void
+ValidatePartitionConstraints(List **wqueue, Relation scanrel,
+							 List *scanrel_children,
+							 List *partConstraint)
+{
+	bool		found_whole_row;
+	ListCell   *lc;
+
+	if (partConstraint == NIL)
+		return;
+
+	/*
+	 * Based on the table's existing constraints, determine if we can skip
+	 * scanning the table to validate the partition constraint.
+	 */
+	if (PartConstraintImpliedByRelConstraint(scanrel, partConstraint))
+	{
+		ereport(INFO,
+				(errmsg("partition constraint for table \"%s\" is implied by existing constraints",
+						RelationGetRelationName(scanrel))));
+		return;
+	}
+
+	/* Constraints proved insufficient, so we need to scan the table. */
+	foreach(lc, scanrel_children)
+	{
+		AlteredTableInfo *tab;
+		Oid			part_relid = lfirst_oid(lc);
+		Relation	part_rel;
+		List	   *my_partconstr = partConstraint;
+
+		/* Lock already taken */
+		if (part_relid != RelationGetRelid(scanrel))
+			part_rel = heap_open(part_relid, NoLock);
+		else
+			part_rel = scanrel;
+
+		/*
+		 * Skip if the partition is itself a partitioned table.  We can only
+		 * ever scan RELKIND_RELATION relations.
+		 */
+		if (part_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
+			if (part_rel != scanrel)
+				heap_close(part_rel, NoLock);
+			continue;
+		}
+
+		if (part_rel != scanrel)
+		{
+			/*
+			 * Adjust the constraint for scanrel so that it matches this
+			 * partition's attribute numbers.
+			 */
+			my_partconstr = map_partition_varattnos(my_partconstr, 1,
+													part_rel, scanrel,
+													&found_whole_row);
+			/* There can never be a whole-row reference here */
+			if (found_whole_row)
+				elog(ERROR, "unexpected whole-row reference found in partition key");
+		}
+
+		/* Grab a work queue entry. */
+		tab = ATGetQueueEntry(wqueue, part_rel);
+		tab->partition_constraint = (Expr *) linitial(my_partconstr);
+
+		/* keep our lock until commit */
+		if (part_rel != scanrel)
+			heap_close(part_rel, NoLock);
+	}
+}
+
+/*
  * ALTER TABLE <name> ATTACH PARTITION <partition-name> FOR VALUES
  *
  * Return the address of the newly attached partition.
@@ -13435,15 +13603,12 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	Relation	attachrel,
 				catalog;
 	List	   *attachrel_children;
-	TupleConstr *attachrel_constr;
-	List	   *partConstraint,
-			   *existConstraint;
+	List	   *partConstraint;
 	SysScanDesc scan;
 	ScanKeyData skey;
 	AttrNumber	attno;
 	int			natts;
 	TupleDesc	tupleDesc;
-	bool		skip_validate = false;
 	ObjectAddress address;
 	const char *trigger_name;
 	bool		found_whole_row;
@@ -13637,148 +13802,9 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	if (found_whole_row)
 		elog(ERROR, "unexpected whole-row reference found in partition key");
 
-	/*
-	 * Check if we can do away with having to scan the table being attached to
-	 * validate the partition constraint, by *proving* that the existing
-	 * constraints of the table *imply* the partition predicate.  We include
-	 * the table's check constraints and NOT NULL constraints in the list of
-	 * clauses passed to predicate_implied_by().
-	 *
-	 * There is a case in which we cannot rely on just the result of the
-	 * proof.
-	 */
-	attachrel_constr = tupleDesc->constr;
-	existConstraint = NIL;
-	if (attachrel_constr != NULL)
-	{
-		int			num_check = attachrel_constr->num_check;
-		int			i;
-
-		if (attachrel_constr->has_not_null)
-		{
-			int			natts = attachrel->rd_att->natts;
-
-			for (i = 1; i <= natts; i++)
-			{
-				Form_pg_attribute att = attachrel->rd_att->attrs[i - 1];
-
-				if (att->attnotnull && !att->attisdropped)
-				{
-					NullTest   *ntest = makeNode(NullTest);
-
-					ntest->arg = (Expr *) makeVar(1,
-												  i,
-												  att->atttypid,
-												  att->atttypmod,
-												  att->attcollation,
-												  0);
-					ntest->nulltesttype = IS_NOT_NULL;
-
-					/*
-					 * argisrow=false is correct even for a composite column,
-					 * because attnotnull does not represent a SQL-spec IS NOT
-					 * NULL test in such a case, just IS DISTINCT FROM NULL.
-					 */
-					ntest->argisrow = false;
-					ntest->location = -1;
-					existConstraint = lappend(existConstraint, ntest);
-				}
-			}
-		}
-
-		for (i = 0; i < num_check; i++)
-		{
-			Node	   *cexpr;
-
-			/*
-			 * If this constraint hasn't been fully validated yet, we must
-			 * ignore it here.
-			 */
-			if (!attachrel_constr->check[i].ccvalid)
-				continue;
-
-			cexpr = stringToNode(attachrel_constr->check[i].ccbin);
-
-			/*
-			 * Run each expression through const-simplification and
-			 * canonicalization.  It is necessary, because we will be
-			 * comparing it to similarly-processed qual clauses, and may fail
-			 * to detect valid matches without this.
-			 */
-			cexpr = eval_const_expressions(NULL, cexpr);
-			cexpr = (Node *) canonicalize_qual((Expr *) cexpr);
-
-			existConstraint = list_concat(existConstraint,
-										  make_ands_implicit((Expr *) cexpr));
-		}
-
-		existConstraint = list_make1(make_ands_explicit(existConstraint));
-
-		/* And away we go ... */
-		if (predicate_implied_by(partConstraint, existConstraint, true))
-			skip_validate = true;
-	}
-
-	if (skip_validate)
-	{
-		/* No need to scan the table after all. */
-		ereport(INFO,
-				(errmsg("partition constraint for table \"%s\" is implied by existing constraints",
-						RelationGetRelationName(attachrel))));
-	}
-	else
-	{
-		/* Constraints proved insufficient, so we need to scan the table. */
-		ListCell   *lc;
-
-		foreach(lc, attachrel_children)
-		{
-			AlteredTableInfo *tab;
-			Oid			part_relid = lfirst_oid(lc);
-			Relation	part_rel;
-			List	   *my_partconstr = partConstraint;
-
-			/* Lock already taken */
-			if (part_relid != RelationGetRelid(attachrel))
-				part_rel = heap_open(part_relid, NoLock);
-			else
-				part_rel = attachrel;
-
-			/*
-			 * Skip if the partition is itself a partitioned table.  We can
-			 * only ever scan RELKIND_RELATION relations.
-			 */
-			if (part_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
-			{
-				if (part_rel != attachrel)
-					heap_close(part_rel, NoLock);
-				continue;
-			}
-
-			if (part_rel != attachrel)
-			{
-				/*
-				 * Adjust the constraint that we constructed above for
-				 * attachRel so that it matches this partition's attribute
-				 * numbers.
-				 */
-				my_partconstr = map_partition_varattnos(my_partconstr, 1,
-														part_rel, attachrel,
-														&found_whole_row);
-				/* There can never be a whole-row reference here */
-				if (found_whole_row)
-					elog(ERROR, "unexpected whole-row reference found in partition key");
-			}
-
-			/* Grab a work queue entry. */
-			tab = ATGetQueueEntry(wqueue, part_rel);
-			tab->partition_constraint = (Expr *) linitial(my_partconstr);
-
-			/* keep our lock until commit */
-			if (part_rel != attachrel)
-				heap_close(part_rel, NoLock);
-		}
-	}
+	/* Validate partition constraints against the table being attached. */
+	ValidatePartitionConstraints(wqueue, attachrel, attachrel_children,
+								 partConstraint);
 
 	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachrel));
 
-- 
2.7.4

0002-Fix-assumptions-that-get_qual_from_partbound-cannot.patch0000664000175000017500000000734313145251221024214 0ustar  jeevanjeevanFrom c565aab3f04f8a49120ab71225873044204ebf59 Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Thu, 17 Aug 2017 13:47:37 +0530
Subject: [PATCH 2/6] Fix assumptions that get_qual_from_partbound() cannot
 return NIL list.

Current partitioning code assumes that there cannot be any partition
without partition constraints, but in future this assumption might
not hold true.
e.g. if we introduce support for default partition, then default
partition will not have any constraints in case it is the only
partition of its parent.

Jeevan Ladhe.
---
 src/backend/catalog/partition.c  |  8 ++++++--
 src/backend/commands/tablecmds.c | 37 +++++++++++++++++++++----------------
 2 files changed, 27 insertions(+), 18 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index c1a307c..293fc83 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -955,7 +955,8 @@ RelationGetPartitionQual(Relation rel)
  * get_partition_qual_relid
  *
  * Returns an expression tree describing the passed-in relation's partition
- * constraint.
+ * constraint. If there are no partition constraints returns NULL e.g. in case
+ * default partition is the only partition.
  */
 Expr *
 get_partition_qual_relid(Oid relid)
@@ -968,7 +969,10 @@ get_partition_qual_relid(Oid relid)
 	if (rel->rd_rel->relispartition)
 	{
 		and_args = generate_partition_qual(rel);
-		if (list_length(and_args) > 1)
+
+		if (!and_args)
+			result = NULL;
+		else if (list_length(and_args) > 1)
 			result = makeBoolExpr(AND_EXPR, and_args, -1);
 		else
 			result = linitial(and_args);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 83cb460..1976964 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13787,24 +13787,29 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	partConstraint = list_concat(get_qual_from_partbound(attachrel, rel,
 														 cmd->bound),
 								 RelationGetPartitionQual(rel));
-	partConstraint = (List *) eval_const_expressions(NULL,
-													 (Node *) partConstraint);
-	partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
-	partConstraint = list_make1(make_ands_explicit(partConstraint));
 
-	/*
-	 * Adjust the generated constraint to match this partition's attribute
-	 * numbers.
-	 */
-	partConstraint = map_partition_varattnos(partConstraint, 1, attachrel,
-											 rel, &found_whole_row);
-	/* There can never be a whole-row reference here */
-	if (found_whole_row)
-		elog(ERROR, "unexpected whole-row reference found in partition key");
+	/* Skip validation if there are no constraints to validate. */
+	if (partConstraint)
+	{
+		partConstraint = (List *) eval_const_expressions(NULL,
+														 (Node *) partConstraint);
+		partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
+		partConstraint = list_make1(make_ands_explicit(partConstraint));
 
-	/* Validate partition constraints against the table being attached. */
-	ValidatePartitionConstraints(wqueue, attachrel, attachrel_children,
-								 partConstraint);
+		/*
+		 * Adjust the generated constraint to match this partition's attribute
+		 * numbers.
+		 */
+		partConstraint = map_partition_varattnos(partConstraint, 1, attachrel,
+												 rel, &found_whole_row);
+		/* There can never be a whole-row reference here */
+		if (found_whole_row)
+			elog(ERROR, "unexpected whole-row reference found in partition key");
+
+		/* Validate partition constraints against the table being attached. */
+		ValidatePartitionConstraints(wqueue, attachrel, attachrel_children,
+									 partConstraint);
+	}
 
 	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachrel));
 
-- 
2.7.4

0003-Implement-default-partition-support.patch0000664000175000017500000014256113145251221021065 0ustar  jeevanjeevanFrom 6c032c0b55ace3a309d2962a67a4f0147b7a6d37 Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Thu, 17 Aug 2017 13:49:12 +0530
Subject: [PATCH 3/6] Implement default partition support

This patch introduces default partition for a list partitioned
table. However, in this version of patch no other partition can
be attached to a list partitioned table if it has a default
partition. To ease the retrieval of default partition OID, this
patch adds a new column 'partdefid' to pg_partitioned_table.

Jeevan Ladhe.
---
 src/backend/catalog/heap.c                 |  35 ++++-
 src/backend/catalog/partition.c            | 212 +++++++++++++++++++++++++++--
 src/backend/commands/tablecmds.c           |  57 +++++++-
 src/backend/nodes/copyfuncs.c              |   1 +
 src/backend/nodes/equalfuncs.c             |   1 +
 src/backend/nodes/outfuncs.c               |   1 +
 src/backend/nodes/readfuncs.c              |   1 +
 src/backend/parser/gram.y                  |  27 +++-
 src/backend/parser/parse_utilcmd.c         |  19 +++
 src/backend/utils/adt/ruleutils.c          |  12 +-
 src/bin/psql/describe.c                    |   8 +-
 src/bin/psql/tab-complete.c                |   4 +-
 src/include/catalog/partition.h            |   3 +
 src/include/catalog/pg_partitioned_table.h |  13 +-
 src/include/nodes/parsenodes.h             |   1 +
 src/test/regress/expected/alter_table.out  |  18 +++
 src/test/regress/expected/create_table.out |   7 +
 src/test/regress/expected/insert.out       |  90 ++++++++++--
 src/test/regress/expected/plancache.out    |  22 +++
 src/test/regress/expected/update.out       |  15 ++
 src/test/regress/sql/alter_table.sql       |  17 +++
 src/test/regress/sql/create_table.sql      |   6 +
 src/test/regress/sql/insert.sql            |  40 ++++++
 src/test/regress/sql/plancache.sql         |  19 +++
 src/test/regress/sql/update.sql            |  15 ++
 25 files changed, 598 insertions(+), 46 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index a376b99..0f6b41c 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1757,7 +1757,8 @@ heap_drop_with_catalog(Oid relid)
 {
 	Relation	rel;
 	HeapTuple	tuple;
-	Oid			parentOid = InvalidOid;
+	Oid			parentOid = InvalidOid,
+				defaultPartOid = InvalidOid;
 
 	/*
 	 * To drop a partition safely, we must grab exclusive lock on its parent,
@@ -1773,6 +1774,21 @@ heap_drop_with_catalog(Oid relid)
 	{
 		parentOid = get_partition_parent(relid);
 		LockRelationOid(parentOid, AccessExclusiveLock);
+
+		/*
+		 * The partition constraint of the default partition depends on the
+		 * partition bounds of every other partition. It is possible that
+		 * other backend might be about to execute a query on the default
+		 * partition table and the query relies on previously cached default
+		 * partition constraints, which won't be correct after removal of a
+		 * partition. We must therefore take a table lock strong enough to
+		 * prevent all queries on the default partition from proceeding until
+		 * we commit and send out a shared-cache-inval notice that will make
+		 * them update their index lists.
+		 */
+		defaultPartOid = get_default_partition_oid(parentOid);
+		if (OidIsValid(defaultPartOid))
+			LockRelationOid(defaultPartOid, AccessExclusiveLock);
 	}
 
 	ReleaseSysCache(tuple);
@@ -1824,6 +1840,13 @@ heap_drop_with_catalog(Oid relid)
 		RemovePartitionKeyByRelId(relid);
 
 	/*
+	 * If the relation being dropped is the default partition itself,
+	 * invalidate its entry in pg_partitioned_table.
+	 */
+	if (relid == defaultPartOid)
+		update_default_partition_oid(parentOid, InvalidOid);
+
+	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -1883,6 +1906,15 @@ heap_drop_with_catalog(Oid relid)
 	if (OidIsValid(parentOid))
 	{
 		/*
+		 * The partition constraint for the default partition depends on the
+		 * partition bounds of every other partition, so we must invalidate
+		 * the relcache entry for that partition every time a partition is
+		 * added or removed.
+		 */
+		if (OidIsValid(defaultPartOid))
+			CacheInvalidateRelcacheByRelid(defaultPartOid);
+
+		/*
 		 * Invalidate the parent's relcache so that the partition is no longer
 		 * included in its partition descriptor.
 		 */
@@ -3136,6 +3168,7 @@ StorePartitionKey(Relation rel,
 	values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
 	values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
 	values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
+	values[Anum_pg_partitioned_table_partdefid - 1] = ObjectIdGetDatum(InvalidOid);
 	values[Anum_pg_partitioned_table_partattrs - 1] = PointerGetDatum(partattrs_vec);
 	values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
 	values[Anum_pg_partitioned_table_partcollation - 1] = PointerGetDatum(partcollation_vec);
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 293fc83..bcd8e80 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -27,6 +27,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_inherits_fn.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_type.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
@@ -80,9 +81,12 @@ typedef struct PartitionBoundInfoData
 								 * partitioned table) */
 	int			null_index;		/* Index of the null-accepting partition; -1
 								 * if there isn't one */
+	int			default_index;	/* Index of the default partition; -1 if there
+								 * isn't one */
 } 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
@@ -120,7 +124,7 @@ static void get_range_key_properties(PartitionKey key, int keynum,
 						 ListCell **partexprs_item,
 						 Expr **keyCol,
 						 Const **lower_val, Const **upper_val);
-static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_list(Relation parent, PartitionBoundSpec *spec);
 static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
 static List *generate_partition_qual(Relation rel);
 
@@ -166,6 +170,7 @@ RelationBuildPartitionDesc(Relation rel)
 	/* List partitioning specific */
 	PartitionListValue **all_values = NULL;
 	int			null_index = -1;
+	int			default_index = -1;
 
 	/* Range partitioning specific */
 	PartitionRangeBound **rbounds = NULL;
@@ -213,6 +218,19 @@ RelationBuildPartitionDesc(Relation rel)
 								&isnull);
 		Assert(!isnull);
 		boundspec = (Node *) stringToNode(TextDatumGetCString(datum));
+
+		/*
+		 * If this is a default partition, pg_partitioned_table must have it's
+		 * OID as value of 'partdefid' for it's parent (i.e. rel) entry.
+		 */
+		if (castNode(PartitionBoundSpec, boundspec)->is_default)
+		{
+			Oid			partdefid;
+
+			partdefid = get_default_partition_oid(RelationGetRelid(rel));
+			Assert(partdefid == inhrelid);
+		}
+
 		boundspecs = lappend(boundspecs, boundspec);
 		partoids = lappend_oid(partoids, inhrelid);
 		ReleaseSysCache(tuple);
@@ -246,6 +264,18 @@ RelationBuildPartitionDesc(Relation rel)
 				if (spec->strategy != PARTITION_STRATEGY_LIST)
 					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 list of non-null
+				 * datums for this partition.
+				 */
+				if (spec->is_default)
+				{
+					default_index = i;
+					i++;
+					continue;
+				}
+
 				foreach(c, spec->listdatums)
 				{
 					Const	   *val = castNode(Const, lfirst(c));
@@ -441,6 +471,7 @@ RelationBuildPartitionDesc(Relation rel)
 		boundinfo = (PartitionBoundInfoData *)
 			palloc0(sizeof(PartitionBoundInfoData));
 		boundinfo->strategy = key->strategy;
+		boundinfo->default_index = -1;
 		boundinfo->ndatums = ndatums;
 		boundinfo->null_index = -1;
 		boundinfo->datums = (Datum **) palloc0(ndatums * sizeof(Datum *));
@@ -493,6 +524,21 @@ RelationBuildPartitionDesc(Relation rel)
 						boundinfo->null_index = mapping[null_index];
 					}
 
+					/* Assign mapped index for the default partition. */
+					if (default_index != -1)
+					{
+						/*
+						 * The default partition accepts any value not
+						 * specified in the lists of other partitions, hence
+						 * it should not get mapped index while assigning
+						 * those for non-null datums.
+						 */
+						Assert(default_index >= 0 &&
+							   mapping[default_index] == -1);
+						mapping[default_index] = next_index++;
+						boundinfo->default_index = mapping[default_index];
+					}
+
 					/* All partition must now have a valid mapping */
 					Assert(next_index == nparts);
 					break;
@@ -597,6 +643,9 @@ partition_bounds_equal(int partnatts, int16 *parttyplen, bool *parttypbyval,
 	if (b1->null_index != b2->null_index)
 		return false;
 
+	if (b1->default_index != b2->default_index)
+		return false;
+
 	for (i = 0; i < b1->ndatums; i++)
 	{
 		int			j;
@@ -880,7 +929,7 @@ get_qual_from_partbound(Relation rel, Relation parent,
 	{
 		case PARTITION_STRATEGY_LIST:
 			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
-			my_qual = get_qual_for_list(key, spec);
+			my_qual = get_qual_for_list(parent, spec);
 			break;
 
 		case PARTITION_STRATEGY_RANGE:
@@ -1284,10 +1333,14 @@ make_partition_op_expr(PartitionKey key, int keynum,
  *
  * Returns an implicit-AND list of expressions to use as a list partition's
  * constraint, given the partition key and bound structures.
+ *
+ * The function returns NIL for a default partition when it's the only
+ * partition since it can not have any partition constraint.
  */
 static List *
-get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
+get_qual_for_list(Relation parent, PartitionBoundSpec *spec)
 {
+	PartitionKey key = RelationGetPartitionKey(parent);
 	List	   *result;
 	Expr	   *keyCol;
 	ArrayExpr  *arr;
@@ -1314,15 +1367,63 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 	else
 		keyCol = (Expr *) copyObject(linitial(key->partexprs));
 
-	/* Create list of Consts for the allowed values, excluding any nulls */
-	foreach(cell, spec->listdatums)
+	/*
+	 * For default list partition, collect datums for all the partitions. The
+	 * default partition constraint should check that the partition key is
+	 * equal to none of those.
+	 */
+	if (spec->is_default)
 	{
-		Const	   *val = castNode(Const, lfirst(cell));
+		int			i;
+		int			ndatums = 0;
+		PartitionDesc pdesc = RelationGetPartitionDesc(parent);
+		PartitionBoundInfo boundinfo = pdesc->boundinfo;
 
-		if (val->constisnull)
-			list_has_null = true;
-		else
-			arrelems = lappend(arrelems, copyObject(val));
+		if (boundinfo)
+		{
+			ndatums = boundinfo->ndatums;
+
+			if (partition_bound_accepts_nulls(boundinfo))
+				list_has_null = true;
+		}
+
+		/*
+		 * If default is the only partition, there need not be any partition
+		 * constraint on it.
+		 */
+		if (ndatums == 0 && !list_has_null)
+			return NIL;
+
+		for (i = 0; i < ndatums; i++)
+		{
+			Const	   *val;
+
+			/* Construct const from datum */
+			val = makeConst(key->parttypid[0],
+							key->parttypmod[0],
+							key->parttypcoll[0],
+							key->parttyplen[0],
+							*boundinfo->datums[i],
+							false,	/* isnull */
+							key->parttypbyval[0]);
+
+			arrelems = lappend(arrelems, val);
+		}
+	}
+	else
+	{
+		/*
+		 * Create list of Consts for the allowed values, excluding any nulls.
+		 */
+		foreach(cell, spec->listdatums)
+		{
+			Const	   *val = castNode(Const, lfirst(cell));
+
+			if (val->constisnull)
+				list_has_null = true;
+			else
+				arrelems = lappend(arrelems, copyObject(val));
+		}
 	}
 
 	if (arrelems)
@@ -1386,6 +1487,25 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 			result = list_make1(nulltest);
 	}
 
+	/*
+	 * In case of the default partition, the constraint is of the form
+	 * "!(result)" i.e. one of the following two forms:
+	 *
+	 * 1. NOT ((keycol IS NULL) OR (keycol = ANY (arr)))
+	 *
+	 * 2. NOT ((keycol IS NOT NULL) AND (keycol = ANY (arr)))
+	 *
+	 * Note that, in general, applying NOT to a constraint expression doesn't
+	 * necessarily invert the set of rows it accepts, because NOT (NULL) is
+	 * NULL.  However, the partition constraints we construct here never
+	 * evaluate to NULL, so applying NOT works as intended.
+	 */
+	if (spec->is_default)
+	{
+		result = list_make1(make_ands_explicit(result));
+		result = list_make1(makeBoolExpr(NOT_EXPR, result, -1));
+	}
+
 	return result;
 }
 
@@ -2006,8 +2126,9 @@ get_partition_for_tuple(PartitionDispatch *pd,
 		}
 
 		/*
-		 * A null partition key is only acceptable if null-accepting list
-		 * partition exists.
+		 * If this is a null partition key, route it to the null-accepting
+		 * partition. Otherwise, route by searching the array of partition
+		 * bounds.
 		 */
 		cur_index = -1;
 		if (isnull[0] && partition_bound_accepts_nulls(partdesc->boundinfo))
@@ -2045,11 +2166,17 @@ get_partition_for_tuple(PartitionDispatch *pd,
 		}
 
 		/*
-		 * cur_index < 0 means we failed to find a partition of this parent.
-		 * cur_index >= 0 means we either found the leaf partition, or the
-		 * next parent to find a partition of.
+		 * cur_index < 0 means we could not find a non-default partition of
+		 * this parent. cur_index >= 0 means we either found the leaf
+		 * partition, or the next parent to find a partition of.
+		 *
+		 * If we couldn't find a non-default partition check if the default
+		 * partition exists, if it does, get its index.
 		 */
 		if (cur_index < 0)
+			cur_index = partdesc->boundinfo->default_index;
+
+		if (cur_index < 0)
 		{
 			result = -1;
 			*failed_at = parent;
@@ -2337,3 +2464,58 @@ partition_bound_bsearch(PartitionKey key, PartitionBoundInfo boundinfo,
 
 	return lo;
 }
+
+/*
+ * get_default_partition_oid
+ *
+ * If the given relation has a default partition return the OID of the default
+ * partition, otherwise return InvalidOid.
+ */
+Oid
+get_default_partition_oid(Oid parentId)
+{
+	HeapTuple	tuple;
+	Oid			defaultPartId = InvalidOid;
+
+	tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(parentId));
+
+	if (HeapTupleIsValid(tuple))
+	{
+		Form_pg_partitioned_table part_table_form;
+
+		part_table_form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+		defaultPartId = part_table_form->partdefid;
+	}
+
+	ReleaseSysCache(tuple);
+	return defaultPartId;
+}
+
+/*
+ * update_default_partition_oid
+ *
+ * Updates the pg_partition_table catalog partdefid field for the given parent
+ * with the given default partition oid.
+ */
+void
+update_default_partition_oid(Oid parentId, Oid defaultPartId)
+{
+	HeapTuple	tuple;
+	Relation	pg_partitioned_table;
+	Form_pg_partitioned_table part_table_form;
+
+	pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCacheCopy1(PARTRELID, ObjectIdGetDatum(parentId));
+
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of relation %u",
+			 parentId);
+
+	part_table_form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+	part_table_form->partdefid = defaultPartId;
+	CatalogTupleUpdate(pg_partitioned_table, &tuple->t_self, tuple);
+
+	heap_freetuple(tuple);
+	heap_close(pg_partitioned_table, RowExclusiveLock);
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1976964..550f713 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -772,7 +772,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	{
 		PartitionBoundSpec *bound;
 		ParseState *pstate;
-		Oid			parentId = linitial_oid(inheritOids);
+		Oid			parentId = linitial_oid(inheritOids),
+					defaultPartOid;
 		Relation	parent;
 
 		/* Already have strong enough lock on the parent */
@@ -788,6 +789,17 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 					 errmsg("\"%s\" is not partitioned",
 							RelationGetRelationName(parent))));
 
+		/*
+		 * A table cannot be created as a partition of a parent already having
+		 * a default partition.
+		 */
+		defaultPartOid = get_default_partition_oid(RelationGetRelid(parent));
+		if (OidIsValid(defaultPartOid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("cannot add a new partition to table \"%s\" having a default partition",
+							RelationGetRelationName(parent))));
+
 		/* Tranform the bound values */
 		pstate = make_parsestate(NULL);
 		pstate->p_sourcetext = queryString;
@@ -804,6 +816,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		/* Update the pg_class entry. */
 		StorePartitionBound(rel, parent, bound);
 
+		/* Update the default partition oid */
+		if (bound->is_default)
+			update_default_partition_oid(RelationGetRelid(parent), relationId);
+
 		heap_close(parent, NoLock);
 
 		/*
@@ -13612,6 +13628,15 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	ObjectAddress address;
 	const char *trigger_name;
 	bool		found_whole_row;
+	Oid			defaultPartOid;
+
+	/* A partition cannot be attached if there exists a default partition */
+	defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
+	if (OidIsValid(defaultPartOid))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot attach a new partition to table \"%s\" having a default partition",
+						RelationGetRelationName(rel))));
 
 	attachrel = heap_openrv(cmd->name, AccessExclusiveLock);
 
@@ -13768,6 +13793,11 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	/* OK to create inheritance.  Rest of the checks performed there */
 	CreateInheritance(attachrel, rel);
 
+	/* Update the default partition oid */
+	if (cmd->bound->is_default)
+		update_default_partition_oid(RelationGetRelid(rel),
+									 RelationGetRelid(attachrel));
+
 	/*
 	 * Check that the new partition's bound is valid and does not overlap any
 	 * of existing partitions of the parent - note that it does not return on
@@ -13836,6 +13866,15 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 				new_null[Natts_pg_class],
 				new_repl[Natts_pg_class];
 	ObjectAddress address;
+	Oid			defaultPartOid;
+
+	/*
+	 * We must lock the default partition, for the same reasons explained in
+	 * heap_drop_with_catalog().
+	 */
+	defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
+	if (OidIsValid(defaultPartOid))
+		LockRelationOid(defaultPartOid, AccessExclusiveLock);
 
 	partRel = heap_openrv(name, AccessShareLock);
 
@@ -13867,6 +13906,22 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 	heap_freetuple(newtuple);
 	heap_close(classRel, RowExclusiveLock);
 
+	if (OidIsValid(defaultPartOid))
+	{
+		/*
+		 * If the detach relation is the default partition itself, invalidate
+		 * its entry in pg_partitioned_table.
+		 */
+		if (RelationGetRelid(partRel) == defaultPartOid)
+			update_default_partition_oid(RelationGetRelid(rel), InvalidOid);
+
+		/*
+		 * We must invalidate default partition's relcache, for the same
+		 * reasons explained in heap_drop_with_catalog().
+		 */
+		CacheInvalidateRelcacheByRelid(defaultPartOid);
+	}
+
 	/*
 	 * Invalidate the parent's relcache so that the partition is no longer
 	 * included in its partition descriptor.
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 7204169..ad15e46 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4447,6 +4447,7 @@ _copyPartitionBoundSpec(const PartitionBoundSpec *from)
 	PartitionBoundSpec *newnode = makeNode(PartitionBoundSpec);
 
 	COPY_SCALAR_FIELD(strategy);
+	COPY_SCALAR_FIELD(is_default);
 	COPY_NODE_FIELD(listdatums);
 	COPY_NODE_FIELD(lowerdatums);
 	COPY_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8d92c03..64ecfff 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2838,6 +2838,7 @@ static bool
 _equalPartitionBoundSpec(const PartitionBoundSpec *a, const PartitionBoundSpec *b)
 {
 	COMPARE_SCALAR_FIELD(strategy);
+	COMPARE_SCALAR_FIELD(is_default);
 	COMPARE_NODE_FIELD(listdatums);
 	COMPARE_NODE_FIELD(lowerdatums);
 	COMPARE_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 5ce3c7c..a20cbc5 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3571,6 +3571,7 @@ _outPartitionBoundSpec(StringInfo str, const PartitionBoundSpec *node)
 	WRITE_NODE_TYPE("PARTITIONBOUNDSPEC");
 
 	WRITE_CHAR_FIELD(strategy);
+	WRITE_BOOL_FIELD(is_default);
 	WRITE_NODE_FIELD(listdatums);
 	WRITE_NODE_FIELD(lowerdatums);
 	WRITE_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 86c811d..2840ca7 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2388,6 +2388,7 @@ _readPartitionBoundSpec(void)
 	READ_LOCALS(PartitionBoundSpec);
 
 	READ_CHAR_FIELD(strategy);
+	READ_BOOL_FIELD(is_default);
 	READ_NODE_FIELD(listdatums);
 	READ_NODE_FIELD(lowerdatums);
 	READ_NODE_FIELD(upperdatums);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7d0de99..1db35e2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -575,7 +575,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>			part_strategy
 %type <partelem>	part_elem
 %type <list>		part_params
-%type <partboundspec> ForValues
+%type <partboundspec> PartitionBoundSpec
 %type <node>		partbound_datum PartitionRangeDatum
 %type <list>		partbound_datum_list range_datum_list
 
@@ -1980,7 +1980,7 @@ alter_table_cmds:
 
 partition_cmd:
 			/* ALTER TABLE <name> ATTACH PARTITION <table_name> FOR VALUES */
-			ATTACH PARTITION qualified_name ForValues
+			ATTACH PARTITION qualified_name PartitionBoundSpec
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
 					PartitionCmd *cmd = makeNode(PartitionCmd);
@@ -2619,13 +2619,14 @@ alter_identity_column_option:
 				}
 		;
 
-ForValues:
+PartitionBoundSpec:
 			/* a LIST partition */
 			FOR VALUES IN_P '(' partbound_datum_list ')'
 				{
 					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
 
 					n->strategy = PARTITION_STRATEGY_LIST;
+					n->is_default = false;
 					n->listdatums = $5;
 					n->location = @3;
 
@@ -2638,12 +2639,24 @@ ForValues:
 					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
 
 					n->strategy = PARTITION_STRATEGY_RANGE;
+					n->is_default = false;
 					n->lowerdatums = $5;
 					n->upperdatums = $9;
 					n->location = @3;
 
 					$$ = n;
 				}
+
+			/* a DEFAULT partition */
+			| DEFAULT
+				{
+					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+					n->is_default = true;
+					n->location = @1;
+
+					$$ = n;
+				}
 		;
 
 partbound_datum:
@@ -3114,7 +3127,7 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
-			OptTypedTableElementList ForValues OptPartitionSpec OptWith
+			OptTypedTableElementList PartitionBoundSpec OptPartitionSpec OptWith
 			OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
@@ -3133,7 +3146,7 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
-			qualified_name OptTypedTableElementList ForValues OptPartitionSpec
+			qualified_name OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
 			OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
@@ -4848,7 +4861,7 @@ CreateForeignTableStmt:
 					$$ = (Node *) n;
 				}
 		| CREATE FOREIGN TABLE qualified_name
-			PARTITION OF qualified_name OptTypedTableElementList ForValues
+			PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
 			SERVER name create_generic_options
 				{
 					CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
@@ -4869,7 +4882,7 @@ CreateForeignTableStmt:
 					$$ = (Node *) n;
 				}
 		| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
-			PARTITION OF qualified_name OptTypedTableElementList ForValues
+			PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
 			SERVER name create_generic_options
 				{
 					CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 495ba3d..2816c9f 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -60,6 +61,7 @@
 #include "parser/parse_utilcmd.h"
 #include "parser/parser.h"
 #include "rewrite/rewriteManip.h"
+#include "storage/lmgr.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
@@ -3304,6 +3306,23 @@ transformPartitionBound(ParseState *pstate, Relation parent,
 	/* Avoid scribbling on input */
 	result_spec = copyObject(spec);
 
+	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
+		 * partition bound spec.
+		 */
+		result_spec->strategy = strategy;
+
+		return result_spec;
+	}
+
 	if (strategy == PARTITION_STRATEGY_LIST)
 	{
 		ListCell   *cell;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 7469ec7..fc4854ba 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8694,10 +8694,18 @@ get_rule_expr(Node *node, deparse_context *context,
 		case T_PartitionBoundSpec:
 			{
 				PartitionBoundSpec *spec = (PartitionBoundSpec *) node;
+				char		strategy = spec->strategy;
 				ListCell   *cell;
 				char	   *sep;
 
-				switch (spec->strategy)
+				if (spec->is_default)
+				{
+					Assert(strategy == PARTITION_STRATEGY_LIST);
+					appendStringInfoString(buf, "DEFAULT");
+					break;
+				}
+
+				switch (strategy)
 				{
 					case PARTITION_STRATEGY_LIST:
 						Assert(spec->listdatums != NIL);
@@ -8729,7 +8737,7 @@ get_rule_expr(Node *node, deparse_context *context,
 
 					default:
 						elog(ERROR, "unrecognized partition strategy: %d",
-							 (int) spec->strategy);
+							 (int) strategy);
 						break;
 				}
 			}
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index f6049cc..4d42f8d 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1900,8 +1900,12 @@ describeOneTableDetails(const char *schemaname,
 
 			if (partconstraintdef)
 			{
-				printfPQExpBuffer(&tmpbuf, _("Partition constraint: %s"),
-								  partconstraintdef);
+				/* If there isn't any constraint, show that explicitly */
+				if (partconstraintdef[0] == '\0')
+					printfPQExpBuffer(&tmpbuf, _("No partition constraint"));
+				else
+					printfPQExpBuffer(&tmpbuf, _("Partition constraint: %s"),
+									  partconstraintdef);
 				printTableAddFooter(&cont, tmpbuf.data);
 			}
 
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 1583cfa..fb1091a 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2050,7 +2050,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("ATTACH", "PARTITION", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 	else if (TailMatches2("FOR", "VALUES"))
 		COMPLETE_WITH_LIST2("FROM (", "IN (");
 
@@ -2489,7 +2489,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("PARTITION", "OF", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 
 /* CREATE TABLESPACE */
 	else if (Matches3("CREATE", "TABLESPACE", MatchAny))
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index bef7a0f..3e30863 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -100,4 +100,7 @@ extern int get_partition_for_tuple(PartitionDispatch *pd,
 						EState *estate,
 						PartitionDispatchData **failed_at,
 						TupleTableSlot **failed_slot);
+extern Oid	get_default_partition_oid(Oid parentId);
+extern void update_default_partition_oid(Oid parentId, Oid defaultPartId);
+
 #endif							/* PARTITION_H */
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
index 38d64d6..525e541 100644
--- a/src/include/catalog/pg_partitioned_table.h
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -32,6 +32,8 @@ CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
 	Oid			partrelid;		/* partitioned table oid */
 	char		partstrat;		/* partitioning strategy */
 	int16		partnatts;		/* number of partition key columns */
+	Oid			partdefid;		/* default partition oid; InvalidOid if there
+								 * isn't one */
 
 	/*
 	 * variable-length fields start here, but we allow direct access to
@@ -62,13 +64,14 @@ typedef FormData_pg_partitioned_table *Form_pg_partitioned_table;
  *		compiler constants for pg_partitioned_table
  * ----------------
  */
-#define Natts_pg_partitioned_table				7
+#define Natts_pg_partitioned_table				8
 #define Anum_pg_partitioned_table_partrelid		1
 #define Anum_pg_partitioned_table_partstrat		2
 #define Anum_pg_partitioned_table_partnatts		3
-#define Anum_pg_partitioned_table_partattrs		4
-#define Anum_pg_partitioned_table_partclass		5
-#define Anum_pg_partitioned_table_partcollation 6
-#define Anum_pg_partitioned_table_partexprs		7
+#define Anum_pg_partitioned_table_partdefid		4
+#define Anum_pg_partitioned_table_partattrs		5
+#define Anum_pg_partitioned_table_partclass		6
+#define Anum_pg_partitioned_table_partcollation 7
+#define Anum_pg_partitioned_table_partexprs		8
 
 #endif							/* PG_PARTITIONED_TABLE_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 5f2a4a7..6e05b79 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -797,6 +797,7 @@ typedef struct PartitionBoundSpec
 	NodeTag		type;
 
 	char		strategy;		/* see PARTITION_STRATEGY codes above */
+	bool		is_default;		/* is it a default partition bound? */
 
 	/* Partitioning info for LIST strategy: */
 	List	   *listdatums;		/* List of Consts (or A_Consts in raw tree) */
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 58192d2..c1e090a 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3273,6 +3273,14 @@ SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::reg
 CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
 ERROR:  partition "fail_part" would overlap partition "part_1"
+-- check that an existing table can be attached as a default partition
+CREATE TABLE def_part (LIKE list_parted INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION def_part DEFAULT;
+-- check attaching default partition fails if a default partition already
+-- exists
+CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT;
+ERROR:  cannot attach a new partition to table "list_parted" having a default partition
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
 	a int,
@@ -3286,6 +3294,15 @@ ERROR:  partition constraint is violated by some row
 -- should be ok after deleting the bad row
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+-- check partition cannot be attached if default exists
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
+INSERT INTO list_parted2_def VALUES (11, 'z');
+CREATE TABLE part_3 (LIKE list_parted2);
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+ERROR:  cannot attach a new partition to table "list_parted2" having a default partition
+-- should be ok after detaching a default partition
+ALTER TABLE list_parted2 DETACH PARTITION list_parted2_def;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
 CREATE TABLE part_3_4 (
@@ -3514,6 +3531,7 @@ ALTER TABLE list_parted2 ALTER COLUMN b TYPE text;
 ERROR:  cannot alter type of column named in partition key
 -- cleanup
 DROP TABLE list_parted, list_parted2, range_parted;
+DROP TABLE fail_def_part;
 -- more tests for certain multi-level partitioning scenarios
 create table p (a int, b int) partition by range (a, b);
 create table p1 (b int, a int not null) partition by range (b);
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index babda89..a6a7bf4 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -467,6 +467,13 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
 ERROR:  invalid bound specification for a list partition
 LINE 1: ...BLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) T...
                                                              ^
+-- check default partition cannot be created more than once
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
+ERROR:  cannot add a new partition to table "list_parted" having a default partition
+-- check that no partition can be created after default partition
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (3);
+ERROR:  cannot add a new partition to table "list_parted" having a default partition
 -- specified literal can't be cast to the partition column data type
 CREATE TABLE bools (
 	a bool
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index a2d9469..17f0210 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -219,17 +219,66 @@ insert into part_null values (null, 0);
 create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b);
 create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
 create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
+-- test default partition
+create table part_default partition of list_parted default;
+-- Negative test: a row, which would fit in other partition, does not fit
+-- default partition, even when inserted directly
+insert into part_default values ('aa', 2);
+ERROR:  new row for relation "part_default" violates partition constraint
+DETAIL:  Failing row contains (aa, 2).
+insert into part_default values (null, 2);
+ERROR:  new row for relation "part_default" violates partition constraint
+DETAIL:  Failing row contains (null, 2).
+-- ok
+insert into part_default values ('Zz', 2);
+-- test if default partition works as expected for multi-level partitioned
+-- table as well as when default partition itself is further partitioned
+drop table part_default;
+create table part_xx_yy partition of list_parted for values in ('xx', 'yy') partition by list (a);
+create table part_xx_yy_p1 partition of part_xx_yy for values in ('xx');
+create table part_xx_yy_defpart partition of part_xx_yy default;
+create table part_default partition of list_parted default partition by range(b);
+create table part_default_p1 partition of part_default for values from (20) to (30);
+create table part_default_p2 partition of part_default for values from (30) to (40);
 -- fail
 insert into part_ee_ff1 values ('EE', 11);
 ERROR:  new row for relation "part_ee_ff1" violates partition constraint
 DETAIL:  Failing row contains (EE, 11).
+insert into part_default_p2 values ('gg', 43);
+ERROR:  new row for relation "part_default_p2" violates partition constraint
+DETAIL:  Failing row contains (gg, 43).
 -- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
 insert into part_ee_ff1 values ('cc', 1);
 ERROR:  new row for relation "part_ee_ff1" violates partition constraint
 DETAIL:  Failing row contains (cc, 1).
+insert into part_default values ('gg', 43);
+ERROR:  no partition of relation "part_default" found for row
+DETAIL:  Partition key of the failing row contains (b) = (43).
 -- ok
 insert into part_ee_ff1 values ('ff', 1);
 insert into part_ee_ff2 values ('ff', 11);
+insert into part_default_p1 values ('cd', 25);
+insert into part_default_p2 values ('de', 35);
+insert into list_parted values ('ab', 21);
+insert into list_parted values ('xx', 1);
+insert into list_parted values ('yy', 2);
+select tableoid::regclass, * from list_parted;
+      tableoid      | a  | b  
+--------------------+----+----
+ part_cc_dd         | cC |  1
+ part_null          |    |  0
+ part_ee_ff1        | ff |  1
+ part_ee_ff2        | ff | 11
+ part_xx_yy_p1      | xx |  1
+ part_xx_yy_defpart | yy |  2
+ part_default_p1    | cd | 25
+ part_default_p1    | ab | 21
+ part_default_p2    | de | 35
+(9 rows)
+
+-- drop default partition, as we need to add some more partitions to test tuple
+-- routing
+drop table part_default;
 -- Check tuple routing for partitioned tables
 -- fail
 insert into range_parted values ('a', 0);
@@ -274,17 +323,19 @@ DETAIL:  Partition key of the failing row contains (b) = (0).
 insert into list_parted values ('EE', 1);
 insert into part_ee_ff values ('EE', 10);
 select tableoid::regclass, * from list_parted;
-  tableoid   | a  | b  
--------------+----+----
- part_aa_bb  | aA |   
- part_cc_dd  | cC |  1
- part_null   |    |  0
- part_null   |    |  1
- part_ee_ff1 | ff |  1
- part_ee_ff1 | EE |  1
- part_ee_ff2 | ff | 11
- part_ee_ff2 | EE | 10
-(8 rows)
+      tableoid      | a  | b  
+--------------------+----+----
+ part_aa_bb         | aA |   
+ part_cc_dd         | cC |  1
+ part_null          |    |  0
+ part_null          |    |  1
+ part_ee_ff1        | ff |  1
+ part_ee_ff1        | EE |  1
+ part_ee_ff2        | ff | 11
+ part_ee_ff2        | EE | 10
+ part_xx_yy_p1      | xx |  1
+ part_xx_yy_defpart | yy |  2
+(10 rows)
 
 -- some more tests to exercise tuple-routing with multi-level partitioning
 create table part_gg partition of list_parted for values in ('gg') partition by range (b);
@@ -316,6 +367,23 @@ select tableoid::regclass::text, a, min(b) as min_b, max(b) as max_b from list_p
 
 -- cleanup
 drop table range_parted, list_parted;
+-- test adding default partition as first partition accepts any value including
+-- null
+create table list_parted (a int) partition by list (a);
+create table part_default partition of list_parted default;
+insert into part_default values (null);
+insert into part_default values (1);
+insert into part_default values (-1);
+select tableoid::regclass, a from list_parted;
+   tableoid   | a  
+--------------+----
+ part_default |   
+ part_default |  1
+ part_default | -1
+(3 rows)
+
+-- cleanup
+drop table list_parted;
 -- more tests for certain multi-level partitioning scenarios
 create table mlparted (a int, b int) partition by range (a, b);
 create table mlparted1 (b int not null, a int not null) partition by range ((b+0));
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
index 3f3db33..fb90357 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -252,3 +252,25 @@ NOTICE:  3
  
 (1 row)
 
+-- Check that addition or removal of any partition is correctly dealt with by
+-- default partition table when it is being used in prepared statement.
+create table list_parted (a int) partition by list(a);
+create table list_part_null partition of list_parted for values in (null);
+create table list_part_1 partition of list_parted for values in (1);
+create table list_part_def partition of list_parted default;
+prepare pstmt_def_insert (int) as insert into list_part_def values($1);
+-- should fail
+execute pstmt_def_insert(null);
+ERROR:  new row for relation "list_part_def" violates partition constraint
+DETAIL:  Failing row contains (null).
+execute pstmt_def_insert(1);
+ERROR:  new row for relation "list_part_def" violates partition constraint
+DETAIL:  Failing row contains (1).
+alter table list_parted detach partition list_part_null;
+-- should be ok
+execute pstmt_def_insert(null);
+drop table list_part_1;
+-- should be ok
+execute pstmt_def_insert(1);
+drop table list_parted, list_part_null;
+deallocate pstmt_def_insert;
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index 9366f04..9912ef2 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -218,5 +218,20 @@ 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;
+create table list_parted (
+	a text,
+	b int
+) partition by list (a);
+create table list_part1  partition of list_parted for values in ('a', 'b');
+create table list_default partition of list_parted default;
+insert into list_part1 values ('a', 1);
+insert into list_default values ('d', 10);
+-- fail
+update list_default set a = 'a' where a = 'd';
+ERROR:  new row for relation "list_default" violates partition constraint
+DETAIL:  Failing row contains (a, 10).
+-- ok
+update list_default set a = 'x' where a = 'd';
 -- cleanup
 drop table range_parted;
+drop table list_parted;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 9a20dd1..47641a4 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2095,6 +2095,13 @@ SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::reg
 -- check that the new partition won't overlap with an existing partition
 CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+-- check that an existing table can be attached as a default partition
+CREATE TABLE def_part (LIKE list_parted INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION def_part DEFAULT;
+-- check attaching default partition fails if a default partition already
+-- exists
+CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT;
 
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
@@ -2111,6 +2118,15 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 
+-- check partition cannot be attached if default exists
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
+INSERT INTO list_parted2_def VALUES (11, 'z');
+CREATE TABLE part_3 (LIKE list_parted2);
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+-- should be ok after detaching a default partition
+ALTER TABLE list_parted2 DETACH PARTITION list_parted2_def;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
 CREATE TABLE part_3_4 (
@@ -2311,6 +2327,7 @@ ALTER TABLE list_parted2 ALTER COLUMN b TYPE text;
 
 -- cleanup
 DROP TABLE list_parted, list_parted2, range_parted;
+DROP TABLE fail_def_part;
 
 -- more tests for certain multi-level partitioning scenarios
 create table p (a int, b int) partition by range (a, b);
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 1c0ce92..8d0c252 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -447,6 +447,12 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
 -- trying to specify range for list partitioned table
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
 
+-- check default partition cannot be created more than once
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
+-- check that no partition can be created after default partition
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (3);
+
 -- specified literal can't be cast to the partition column data type
 CREATE TABLE bools (
 	a bool
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 6f17872..a44d454 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -132,13 +132,42 @@ create table part_ee_ff partition of list_parted for values in ('ee', 'ff') part
 create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
 create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
 
+-- test default partition
+create table part_default partition of list_parted default;
+-- Negative test: a row, which would fit in other partition, does not fit
+-- default partition, even when inserted directly
+insert into part_default values ('aa', 2);
+insert into part_default values (null, 2);
+-- ok
+insert into part_default values ('Zz', 2);
+-- test if default partition works as expected for multi-level partitioned
+-- table as well as when default partition itself is further partitioned
+drop table part_default;
+create table part_xx_yy partition of list_parted for values in ('xx', 'yy') partition by list (a);
+create table part_xx_yy_p1 partition of part_xx_yy for values in ('xx');
+create table part_xx_yy_defpart partition of part_xx_yy default;
+create table part_default partition of list_parted default partition by range(b);
+create table part_default_p1 partition of part_default for values from (20) to (30);
+create table part_default_p2 partition of part_default for values from (30) to (40);
+
 -- fail
 insert into part_ee_ff1 values ('EE', 11);
+insert into part_default_p2 values ('gg', 43);
 -- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
 insert into part_ee_ff1 values ('cc', 1);
+insert into part_default values ('gg', 43);
 -- ok
 insert into part_ee_ff1 values ('ff', 1);
 insert into part_ee_ff2 values ('ff', 11);
+insert into part_default_p1 values ('cd', 25);
+insert into part_default_p2 values ('de', 35);
+insert into list_parted values ('ab', 21);
+insert into list_parted values ('xx', 1);
+insert into list_parted values ('yy', 2);
+select tableoid::regclass, * from list_parted;
+-- drop default partition, as we need to add some more partitions to test tuple
+-- routing
+drop table part_default;
 
 -- Check tuple routing for partitioned tables
 
@@ -188,6 +217,17 @@ select tableoid::regclass::text, a, min(b) as min_b, max(b) as max_b from list_p
 -- cleanup
 drop table range_parted, list_parted;
 
+-- test adding default partition as first partition accepts any value including
+-- null
+create table list_parted (a int) partition by list (a);
+create table part_default partition of list_parted default;
+insert into part_default values (null);
+insert into part_default values (1);
+insert into part_default values (-1);
+select tableoid::regclass, a from list_parted;
+-- cleanup
+drop table list_parted;
+
 -- more tests for certain multi-level partitioning scenarios
 create table mlparted (a int, b int) partition by range (a, b);
 create table mlparted1 (b int not null, a int not null) partition by range ((b+0));
diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql
index bc20861..a8e357c 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -156,3 +156,22 @@ end$$ language plpgsql;
 
 select cachebug();
 select cachebug();
+
+-- Check that addition or removal of any partition is correctly dealt with by
+-- default partition table when it is being used in prepared statement.
+create table list_parted (a int) partition by list(a);
+create table list_part_null partition of list_parted for values in (null);
+create table list_part_1 partition of list_parted for values in (1);
+create table list_part_def partition of list_parted default;
+prepare pstmt_def_insert (int) as insert into list_part_def values($1);
+-- should fail
+execute pstmt_def_insert(null);
+execute pstmt_def_insert(1);
+alter table list_parted detach partition list_part_null;
+-- should be ok
+execute pstmt_def_insert(null);
+drop table list_part_1;
+-- should be ok
+execute pstmt_def_insert(1);
+drop table list_parted, list_part_null;
+deallocate pstmt_def_insert;
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 6637119..44fb0dc 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -125,5 +125,20 @@ update range_parted set b = b - 1 where b = 10;
 -- ok
 update range_parted set b = b + 1 where b = 10;
 
+create table list_parted (
+	a text,
+	b int
+) partition by list (a);
+create table list_part1  partition of list_parted for values in ('a', 'b');
+create table list_default partition of list_parted default;
+insert into list_part1 values ('a', 1);
+insert into list_default values ('d', 10);
+
+-- fail
+update list_default set a = 'a' where a = 'd';
+-- ok
+update list_default set a = 'x' where a = 'd';
+
 -- cleanup
 drop table range_parted;
+drop table list_parted;
-- 
2.7.4

0004-Default-partition-extended-for-create-and-attach.patch0000664000175000017500000010755013145251221023210 0ustar  jeevanjeevanFrom 69a96b0df9e15f0e7b537324cfa42e94314ffaf4 Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Thu, 17 Aug 2017 13:50:34 +0530
Subject: [PATCH 4/6] Default partition extended for create and attach

1. This patch extends the previous patch in this series to allow a
new partition to be created or attached even when a default
partition on a list partition exists.
2. Also, extends the regression tests to test this functionality.
3. This patch uses the refactored functions created in patch 0001
in this series.
4. While adding a new partition we need to make sure that default
partition does not contain any rows that would fit in newly added
partition. So, in this patch adds a code to negate the partition
constraints of new partition so as these constraints form the part
of would be default partition constraints. Then the default
partition is scanned to check if there exist a row that does not
hold these negated partition constraints, if there exists such a
row error out.
5. While doing 4, to optimize the validation scan a check is done,
if the existing constraints on the default partition imply that it
will not contain any row that would belong to the new partition,
then the validation scan is skipped.

Jeevan Ladhe, some refactoring and code-reuse of patch 0001 by
Ashutosh bapat.
---
 src/backend/catalog/heap.c                 |  33 +++---
 src/backend/catalog/partition.c            | 183 ++++++++++++++++++++++++++++-
 src/backend/commands/tablecmds.c           | 112 ++++++++++++++----
 src/include/catalog/partition.h            |   3 +
 src/include/commands/tablecmds.h           |   4 +
 src/test/regress/expected/alter_table.out  |  28 ++++-
 src/test/regress/expected/create_table.out |  10 +-
 src/test/regress/expected/insert.out       |   8 +-
 src/test/regress/expected/plancache.out    |   4 +
 src/test/regress/sql/alter_table.sql       |  21 +++-
 src/test/regress/sql/create_table.sql      |   6 +-
 src/test/regress/sql/insert.sql            |   3 -
 src/test/regress/sql/plancache.sql         |   2 +
 13 files changed, 354 insertions(+), 63 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 0f6b41c..c1cb5c6 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1776,15 +1776,8 @@ heap_drop_with_catalog(Oid relid)
 		LockRelationOid(parentOid, AccessExclusiveLock);
 
 		/*
-		 * The partition constraint of the default partition depends on the
-		 * partition bounds of every other partition. It is possible that
-		 * other backend might be about to execute a query on the default
-		 * partition table and the query relies on previously cached default
-		 * partition constraints, which won't be correct after removal of a
-		 * partition. We must therefore take a table lock strong enough to
-		 * prevent all queries on the default partition from proceeding until
-		 * we commit and send out a shared-cache-inval notice that will make
-		 * them update their index lists.
+		 * We must also lock the default partition, for the same reasons
+		 * explained in DefineRelation().
 		 */
 		defaultPartOid = get_default_partition_oid(parentOid);
 		if (OidIsValid(defaultPartOid))
@@ -1906,10 +1899,8 @@ heap_drop_with_catalog(Oid relid)
 	if (OidIsValid(parentOid))
 	{
 		/*
-		 * The partition constraint for the default partition depends on the
-		 * partition bounds of every other partition, so we must invalidate
-		 * the relcache entry for that partition every time a partition is
-		 * added or removed.
+		 * We must invalidate default partition's relcache, for the same
+		 * reasons explained in StorePartitionBound().
 		 */
 		if (OidIsValid(defaultPartOid))
 			CacheInvalidateRelcacheByRelid(defaultPartOid);
@@ -3253,8 +3244,9 @@ RemovePartitionKeyByRelId(Oid relid)
  *		Update pg_class tuple of rel to store the partition bound and set
  *		relispartition to true
  *
- * Also, invalidate the parent's relcache, so that the next rebuild will load
- * the new partition's info into its partition descriptor.
+ * Also, invalidate the parent's relcache entry, so that the next rebuild will
+ * load he new partition's info into its partition descriptor.  If there is a
+ * default partition, we must invalidate its relcache entry as well.
  */
 void
 StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
@@ -3265,6 +3257,7 @@ StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
 	Datum		new_val[Natts_pg_class];
 	bool		new_null[Natts_pg_class],
 				new_repl[Natts_pg_class];
+	Oid			defaultPartOid;
 
 	/* Update pg_class tuple */
 	classRel = heap_open(RelationRelationId, RowExclusiveLock);
@@ -3302,5 +3295,15 @@ StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
 	heap_freetuple(newtuple);
 	heap_close(classRel, RowExclusiveLock);
 
+	/*
+	 * The partition constraint for the default partition depends on the
+	 * partition bounds of every other partition, so we must invalidate the
+	 * relcache entry for that partition every time a partition is added or
+	 * removed.
+	 */
+	defaultPartOid = get_default_partition_oid(RelationGetRelid(parent));
+	if (OidIsValid(defaultPartOid))
+		CacheInvalidateRelcacheByRelid(defaultPartOid);
+
 	CacheInvalidateRelcache(parent);
 }
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index bcd8e80..fbc4bec 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -29,6 +29,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_type.h"
+#include "commands/tablecmds.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -36,6 +37,7 @@
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planmain.h"
+#include "optimizer/prep.h"
 #include "optimizer/var.h"
 #include "rewrite/rewriteManip.h"
 #include "storage/lmgr.h"
@@ -704,10 +706,24 @@ check_new_partition_bound(char *relname, Relation parent,
 {
 	PartitionKey key = RelationGetPartitionKey(parent);
 	PartitionDesc partdesc = RelationGetPartitionDesc(parent);
+	PartitionBoundInfo boundinfo = partdesc->boundinfo;
 	ParseState *pstate = make_parsestate(NULL);
 	int			with = -1;
 	bool		overlap = false;
 
+	if (spec->is_default)
+	{
+		if (boundinfo == NULL || !partition_bound_has_default(boundinfo))
+			return;
+
+		/* Default partition already exists, error out. */
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("partition \"%s\" conflicts with existing default partition \"%s\"",
+						relname, get_rel_name(partdesc->oids[boundinfo->default_index])),
+				 parser_errposition(pstate, spec->location)));
+	}
+
 	switch (key->strategy)
 	{
 		case PARTITION_STRATEGY_LIST:
@@ -716,13 +732,13 @@ check_new_partition_bound(char *relname, Relation parent,
 
 				if (partdesc->nparts > 0)
 				{
-					PartitionBoundInfo boundinfo = partdesc->boundinfo;
 					ListCell   *cell;
 
 					Assert(boundinfo &&
 						   boundinfo->strategy == PARTITION_STRATEGY_LIST &&
 						   (boundinfo->ndatums > 0 ||
-							partition_bound_accepts_nulls(boundinfo)));
+							partition_bound_accepts_nulls(boundinfo) ||
+							partition_bound_has_default(boundinfo)));
 
 					foreach(cell, spec->listdatums)
 					{
@@ -866,6 +882,140 @@ check_new_partition_bound(char *relname, Relation parent,
 }
 
 /*
+ * check_default_allows_bound
+ *
+ * This function checks if there exists a row in the default partition that
+ * fits in the new partition being added and throws an error if it finds one.
+ */
+void
+check_default_allows_bound(Relation parent, Relation default_rel,
+						   PartitionBoundSpec *new_spec)
+{
+	List	   *new_part_constraints;
+	List	   *def_part_constraints;
+	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);
+	def_part_constraints =
+		get_default_part_validation_constraint(new_part_constraints);
+
+	/*
+	 * If the existing constraints on the default partition imply that it will
+	 * not contain any row that would belong to the new partition, we can
+	 * avoid scanning the default partition.
+	 */
+	if (PartConstraintImpliedByRelConstraint(default_rel, def_part_constraints))
+	{
+		ereport(INFO,
+				(errmsg("partition constraint for table \"%s\" is implied by existing constraints",
+						RelationGetRelationName(default_rel))));
+		return;
+	}
+
+	/*
+	 * Bad luck, scan the default partition and its subpartitions, and check
+	 * if any of the row does not satisfy the partition constraints that are
+	 * going to be imposed as additional constraints on default partition by
+	 * addition of the new partition.
+	 */
+	if (default_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		all_parts = find_all_inheritors(RelationGetRelid(default_rel),
+										AccessExclusiveLock, NULL);
+	else
+		all_parts = list_make1_oid(RelationGetRelid(default_rel));
+
+	foreach(lc, all_parts)
+	{
+		Oid			part_relid = lfirst_oid(lc);
+		Relation	part_rel;
+		Expr	   *constr;
+		Expr	   *partition_constraint;
+		EState	   *estate;
+		HeapTuple	tuple;
+		ExprState  *partqualstate = NULL;
+		Snapshot	snapshot;
+		TupleDesc	tupdesc;
+		ExprContext *econtext;
+		HeapScanDesc scan;
+		MemoryContext oldCxt;
+		TupleTableSlot *tupslot;
+
+		/* Lock already taken above. */
+		if (part_relid != RelationGetRelid(default_rel))
+			part_rel = heap_open(part_relid, NoLock);
+		else
+			part_rel = default_rel;
+
+		/*
+		 * Only RELKIND_RELATION relations (i.e. leaf partitions) need to be
+		 * scanned.
+		 */
+		if (part_rel->rd_rel->relkind != RELKIND_RELATION)
+		{
+			if (part_rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+				ereport(WARNING,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("skipped scanning foreign table \"%s\" which is a partition of default partition \"%s\"",
+								RelationGetRelationName(part_rel),
+								RelationGetRelationName(default_rel))));
+
+			if (RelationGetRelid(default_rel) != RelationGetRelid(part_rel))
+				heap_close(part_rel, NoLock);
+
+			continue;
+		}
+
+		tupdesc = CreateTupleDescCopy(RelationGetDescr(part_rel));
+		constr = linitial(def_part_constraints);
+		partition_constraint = (Expr *) map_partition_varattnos((List *) constr,
+																1, part_rel, parent, NULL);
+		estate = CreateExecutorState();
+
+		/* Build expression execution states for partition check quals */
+		partqualstate = ExecPrepareExpr(partition_constraint, estate);
+
+		econtext = GetPerTupleExprContext(estate);
+		snapshot = RegisterSnapshot(GetLatestSnapshot());
+		scan = heap_beginscan(part_rel, snapshot, 0, NULL);
+		tupslot = MakeSingleTupleTableSlot(tupdesc);
+
+		/*
+		 * Switch to per-tuple memory context and reset it for each tuple
+		 * produced, so we don't leak memory.
+		 */
+		oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		{
+			ExecStoreTuple(tuple, tupslot, InvalidBuffer, false);
+			econtext->ecxt_scantuple = tupslot;
+
+			if (!ExecCheck(partqualstate, econtext))
+				ereport(ERROR,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("updated partition constraint for default partition \"%s\" would be violated by some row",
+								RelationGetRelationName(default_rel))));
+
+			ResetExprContext(econtext);
+			CHECK_FOR_INTERRUPTS();
+		}
+
+		MemoryContextSwitchTo(oldCxt);
+		heap_endscan(scan);
+		UnregisterSnapshot(snapshot);
+		ExecDropSingleTupleTableSlot(tupslot);
+		FreeExecutorState(estate);
+
+		if (RelationGetRelid(default_rel) != RelationGetRelid(part_rel))
+			heap_close(part_rel, NoLock);	/* keep the lock until commit */
+	}
+}
+
+/*
  * get_partition_parent
  *
  * Returns inheritance parent of a partition by scanning pg_inherits
@@ -2519,3 +2669,32 @@ update_default_partition_oid(Oid parentId, Oid defaultPartId)
 	heap_freetuple(tuple);
 	heap_close(pg_partitioned_table, RowExclusiveLock);
 }
+
+/*
+ * get_default_part_validation_constraint
+ *
+ * This function returns negated constraint of new_part_constraints which
+ * would be an integral part of the default partition constraints after
+ * addition of the partition to which the new_part_constraints belongs.
+ */
+List *
+get_default_part_validation_constraint(List *new_part_constraints)
+{
+	Expr	   *defPartConstraint;
+
+	defPartConstraint = make_ands_explicit(new_part_constraints);
+
+	/*
+	 * Derieve the partition constraints of default partition by negating the
+	 * given partition constraints. The partition constraint never evaluates
+	 * to NULL, so negating it like this is safe.
+	 */
+	defPartConstraint = makeBoolExpr(NOT_EXPR,
+									 list_make1(defPartConstraint),
+									 -1);
+	defPartConstraint = (Expr *) eval_const_expressions(NULL,
+														(Node *) defPartConstraint);
+	defPartConstraint = canonicalize_qual(defPartConstraint);
+
+	return list_make1(defPartConstraint);
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 550f713..f39cac8 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -168,6 +168,8 @@ typedef struct AlteredTableInfo
 	bool		chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
 	char		newrelpersistence;	/* if above is true */
 	Expr	   *partition_constraint;	/* for attach partition validation */
+	/* true, if is a default partition or a child of default partition */
+	bool		is_default_partition;
 	/* Objects to rebuild after completing ALTER TYPE operations */
 	List	   *changedConstraintOids;	/* OIDs of constraints to rebuild */
 	List	   *changedConstraintDefs;	/* string definitions of same */
@@ -473,11 +475,10 @@ static void CreateInheritance(Relation child_rel, Relation parent_rel);
 static void RemoveInheritance(Relation child_rel, Relation parent_rel);
 static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
 					  PartitionCmd *cmd);
-static bool PartConstraintImpliedByRelConstraint(Relation scanrel,
-									 List *partConstraint);
 static void ValidatePartitionConstraints(List **wqueue, Relation scanrel,
 							 List *scanrel_children,
-							 List *partConstraint);
+							 List *partConstraint,
+							 bool scanrel_is_default);
 static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
 
 
@@ -774,7 +775,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		ParseState *pstate;
 		Oid			parentId = linitial_oid(inheritOids),
 					defaultPartOid;
-		Relation	parent;
+		Relation	parent,
+					defaultRel;
 
 		/* Already have strong enough lock on the parent */
 		parent = heap_open(parentId, NoLock);
@@ -790,15 +792,28 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 							RelationGetRelationName(parent))));
 
 		/*
-		 * A table cannot be created as a partition of a parent already having
-		 * a default partition.
+		 * The partition constraint of the default partition depends on the
+		 * partition bounds of every other partition. It is possible that
+		 * other backend might be about to execute a query on the default
+		 * partition table and the query relies on previously cached default
+		 * partition constraints, which won't be correct after removal of a
+		 * partition. We must therefore take a table lock strong enough to
+		 * prevent all queries on the default partition from proceeding until
+		 * we commit and send out a shared-cache-inval notice that will make
+		 * them update their index lists.
+		 *
+		 * Order of locking: The relation being added won't be visible to
+		 * other backends until it is committed, hence here in
+		 * DefineRelation() the order of locking the default partition and the
+		 * relation being added does not matter. But at all other places we
+		 * need to lock the default relation before we lock the relation being
+		 * added or removed i.e. we should take the lock in same order at all
+		 * the places such that lock parent, lock default partition and then
+		 * lock the partition so as to avoid a deadlock.
 		 */
 		defaultPartOid = get_default_partition_oid(RelationGetRelid(parent));
 		if (OidIsValid(defaultPartOid))
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-					 errmsg("cannot add a new partition to table \"%s\" having a default partition",
-							RelationGetRelationName(parent))));
+			defaultRel = heap_open(defaultPartOid, AccessExclusiveLock);
 
 		/* Tranform the bound values */
 		pstate = make_parsestate(NULL);
@@ -813,6 +828,20 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		 */
 		check_new_partition_bound(relname, parent, bound);
 
+		/*
+		 * If the default partition exists, its partition constraints will
+		 * change after the addition of this new partition such that it won't
+		 * allow any row that qualifies for this new partition. So, check that
+		 * the existing data in the default partition satisfies the constraint
+		 * as it will exist after adding this partition.
+		 */
+		if (OidIsValid(defaultPartOid))
+		{
+			check_default_allows_bound(parent, defaultRel, bound);
+			/* Keep the lock until commit. */
+			heap_close(defaultRel, NoLock);
+		}
+
 		/* Update the pg_class entry. */
 		StorePartitionBound(rel, parent, bound);
 
@@ -4603,9 +4632,16 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 			}
 
 			if (partqualstate && !ExecCheck(partqualstate, econtext))
-				ereport(ERROR,
-						(errcode(ERRCODE_CHECK_VIOLATION),
-						 errmsg("partition constraint is violated by some row")));
+			{
+				if (tab->is_default_partition)
+					ereport(ERROR,
+							(errcode(ERRCODE_CHECK_VIOLATION),
+							 errmsg("updated partition constraint for default partition would be violated by some row")));
+				else
+					ereport(ERROR,
+							(errcode(ERRCODE_CHECK_VIOLATION),
+							 errmsg("partition constraint is violated by some row")));
+			}
 
 			/* Write the tuple out to the new relation */
 			if (newrel)
@@ -13452,7 +13488,7 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
  * Existing constraints includes its check constraints and column-level
  * NOT NULL constraints and partConstraint describes the partition constraint.
  */
-static bool
+bool
 PartConstraintImpliedByRelConstraint(Relation scanrel,
 									 List *partConstraint)
 {
@@ -13539,7 +13575,8 @@ PartConstraintImpliedByRelConstraint(Relation scanrel,
 static void
 ValidatePartitionConstraints(List **wqueue, Relation scanrel,
 							 List *scanrel_children,
-							 List *partConstraint)
+							 List *partConstraint,
+							 bool scanrel_is_default)
 {
 	bool		found_whole_row;
 	ListCell   *lc;
@@ -13601,6 +13638,7 @@ ValidatePartitionConstraints(List **wqueue, Relation scanrel,
 		/* Grab a work queue entry. */
 		tab = ATGetQueueEntry(wqueue, part_rel);
 		tab->partition_constraint = (Expr *) linitial(my_partconstr);
+		tab->is_default_partition = scanrel_is_default;
 
 		/* keep our lock until commit */
 		if (part_rel != scanrel)
@@ -13629,14 +13667,15 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	const char *trigger_name;
 	bool		found_whole_row;
 	Oid			defaultPartOid;
+	List	   *partBoundConstraint;
 
-	/* A partition cannot be attached if there exists a default partition */
+	/*
+	 * We must lock the default partition, for the same reasons explained in
+	 * DefineRelation().
+	 */
 	defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
 	if (OidIsValid(defaultPartOid))
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-				 errmsg("cannot attach a new partition to table \"%s\" having a default partition",
-						RelationGetRelationName(rel))));
+		LockRelationOid(defaultPartOid, AccessExclusiveLock);
 
 	attachrel = heap_openrv(cmd->name, AccessExclusiveLock);
 
@@ -13814,8 +13853,8 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	 * If the parent itself is a partition, make sure to include its
 	 * constraint as well.
 	 */
-	partConstraint = list_concat(get_qual_from_partbound(attachrel, rel,
-														 cmd->bound),
+	partBoundConstraint = get_qual_from_partbound(attachrel, rel, cmd->bound);
+	partConstraint = list_concat(partBoundConstraint,
 								 RelationGetPartitionQual(rel));
 
 	/* Skip validation if there are no constraints to validate. */
@@ -13838,7 +13877,30 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 
 		/* Validate partition constraints against the table being attached. */
 		ValidatePartitionConstraints(wqueue, attachrel, attachrel_children,
-									 partConstraint);
+									 partConstraint, false);
+
+		/*
+		 * Check whether default partition has a row that would fit the
+		 * partition being attached.
+		 */
+		defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
+		if (OidIsValid(defaultPartOid))
+		{
+			Relation	defaultrel;
+			List	   *defaultrel_children;
+			List	   *defPartConstraint;
+
+			/* We already have taken a lock on default partition. */
+			defaultrel = heap_open(defaultPartOid, NoLock);
+			defPartConstraint = get_default_part_validation_constraint(partBoundConstraint);
+			defaultrel_children = find_all_inheritors(defaultPartOid,
+													  AccessExclusiveLock, NULL);
+			ValidatePartitionConstraints(wqueue, defaultrel, defaultrel_children,
+										 defPartConstraint, true);
+
+			/* keep our lock until commit. */
+			heap_close(defaultrel, NoLock);
+		}
 	}
 
 	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachrel));
@@ -13870,7 +13932,7 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 
 	/*
 	 * We must lock the default partition, for the same reasons explained in
-	 * heap_drop_with_catalog().
+	 * DefineRelation().
 	 */
 	defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
 	if (OidIsValid(defaultPartOid))
@@ -13917,7 +13979,7 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 
 		/*
 		 * We must invalidate default partition's relcache, for the same
-		 * reasons explained in heap_drop_with_catalog().
+		 * reasons explained in StorePartitionBound().
 		 */
 		CacheInvalidateRelcacheByRelid(defaultPartOid);
 	}
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 3e30863..863ff7e 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -102,5 +102,8 @@ extern int get_partition_for_tuple(PartitionDispatch *pd,
 						TupleTableSlot **failed_slot);
 extern Oid	get_default_partition_oid(Oid parentId);
 extern void update_default_partition_oid(Oid parentId, Oid defaultPartId);
+extern void check_default_allows_bound(Relation parent, Relation defaultRel,
+						   PartitionBoundSpec *new_spec);
+extern List *get_default_part_validation_constraint(List *new_part_constaints);
 
 #endif							/* PARTITION_H */
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index abd31b6..da3ff5d 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -18,6 +18,7 @@
 #include "catalog/dependency.h"
 #include "catalog/objectaddress.h"
 #include "nodes/parsenodes.h"
+#include "catalog/partition.h"
 #include "storage/lock.h"
 #include "utils/relcache.h"
 
@@ -87,4 +88,7 @@ extern void RangeVarCallbackOwnsTable(const RangeVar *relation,
 
 extern void RangeVarCallbackOwnsRelation(const RangeVar *relation,
 							 Oid relId, Oid oldRelId, void *noCatalogs);
+extern bool PartConstraintImpliedByRelConstraint(Relation scanrel,
+									 List *partConstraint);
+
 #endif							/* TABLECMDS_H */
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index c1e090a..a3e2c82 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3280,7 +3280,7 @@ ALTER TABLE list_parted ATTACH PARTITION def_part DEFAULT;
 -- exists
 CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT;
-ERROR:  cannot attach a new partition to table "list_parted" having a default partition
+ERROR:  partition "fail_def_part" conflicts with existing default partition "def_part"
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
 	a int,
@@ -3294,14 +3294,14 @@ ERROR:  partition constraint is violated by some row
 -- should be ok after deleting the bad row
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
--- check partition cannot be attached if default exists
+-- check partition cannot be attached if default has some row for its values
 CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 INSERT INTO list_parted2_def VALUES (11, 'z');
 CREATE TABLE part_3 (LIKE list_parted2);
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
-ERROR:  cannot attach a new partition to table "list_parted2" having a default partition
--- should be ok after detaching a default partition
-ALTER TABLE list_parted2 DETACH PARTITION list_parted2_def;
+ERROR:  updated partition constraint for default partition would be violated by some row
+-- should be ok after deleting the bad row
+DELETE FROM list_parted2_def WHERE a = 11;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
@@ -3318,6 +3318,10 @@ ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
 ALTER TABLE part_3_4 ALTER a SET NOT NULL;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
 INFO:  partition constraint for table "part_3_4" is implied by existing constraints
+-- check if default partition scan skipped
+ALTER TABLE list_parted2_def ADD CONSTRAINT check_a CHECK (a IN (5, 6));
+CREATE TABLE part_55_66 PARTITION OF list_parted2 FOR VALUES IN (55, 66);
+INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
 -- check validation when attaching range partitions
 CREATE TABLE range_parted (
 	a int,
@@ -3395,6 +3399,7 @@ ALTER TABLE part_7 ATTACH PARTITION part_7_a_null FOR VALUES IN ('a', null);
 INFO:  partition constraint for table "part_7_a_null" is implied by existing constraints
 ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
 INFO:  partition constraint for table "part_7" is implied by existing constraints
+INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
 -- Same example, but check this time that the constraint correctly detects
 -- violating rows
 ALTER TABLE list_parted2 DETACH PARTITION part_7;
@@ -3408,7 +3413,20 @@ SELECT tableoid::regclass, a, b FROM part_7 order by a;
 (2 rows)
 
 ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
+INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
 ERROR:  partition constraint is violated by some row
+-- check that leaf partitions of default partition are scanned when
+-- attaching a partitioned table.
+ALTER TABLE part_5 DROP CONSTRAINT check_a;
+CREATE TABLE part5_def PARTITION OF part_5 DEFAULT PARTITION BY LIST(a);
+CREATE TABLE part5_def_p1 PARTITION OF part5_def FOR VALUES IN (5);
+INSERT INTO part5_def_p1 VALUES (5, 'y');
+CREATE TABLE part5_p1 (LIKE part_5);
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+ERROR:  updated partition constraint for default partition would be violated by some row
+-- should be ok after deleting the bad row
+DELETE FROM part5_def_p1 WHERE b = 'y';
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
 -- check that the table being attached is not already a partition
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 ERROR:  "part_2" is already a partition
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index a6a7bf4..bf0acad 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -470,10 +470,7 @@ LINE 1: ...BLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) T...
 -- check default partition cannot be created more than once
 CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
 CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
-ERROR:  cannot add a new partition to table "list_parted" having a default partition
--- check that no partition can be created after default partition
-CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (3);
-ERROR:  cannot add a new partition to table "list_parted" having a default partition
+ERROR:  partition "fail_default_part" conflicts with existing default partition "part_default"
 -- specified literal can't be cast to the partition column data type
 CREATE TABLE bools (
 	a bool
@@ -565,10 +562,15 @@ CREATE TABLE list_parted2 (
 ) PARTITION BY LIST (a);
 CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
 CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
 ERROR:  partition "fail_part" would overlap partition "part_null_z"
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
 ERROR:  partition "fail_part" would overlap partition "part_ab"
+-- check default partition overlap
+INSERT INTO list_parted2 VALUES('X');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y');
+ERROR:  updated partition constraint for default partition "list_parted2_def" would be violated by some row
 CREATE TABLE range_parted2 (
 	a int
 ) PARTITION BY RANGE (a);
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 17f0210..5ca6e5e 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -276,9 +276,6 @@ select tableoid::regclass, * from list_parted;
  part_default_p2    | de | 35
 (9 rows)
 
--- drop default partition, as we need to add some more partitions to test tuple
--- routing
-drop table part_default;
 -- Check tuple routing for partitioned tables
 -- fail
 insert into range_parted values ('a', 0);
@@ -335,7 +332,10 @@ select tableoid::regclass, * from list_parted;
  part_ee_ff2        | EE | 10
  part_xx_yy_p1      | xx |  1
  part_xx_yy_defpart | yy |  2
-(10 rows)
+ part_default_p1    | cd | 25
+ part_default_p1    | ab | 21
+ part_default_p2    | de | 35
+(13 rows)
 
 -- some more tests to exercise tuple-routing with multi-level partitioning
 create table part_gg partition of list_parted for values in ('gg') partition by range (b);
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
index fb90357..c2eeff1 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -266,6 +266,10 @@ DETAIL:  Failing row contains (null).
 execute pstmt_def_insert(1);
 ERROR:  new row for relation "list_part_def" violates partition constraint
 DETAIL:  Failing row contains (1).
+create table list_part_2 partition of list_parted for values in (2);
+execute pstmt_def_insert(2);
+ERROR:  new row for relation "list_part_def" violates partition constraint
+DETAIL:  Failing row contains (2).
 alter table list_parted detach partition list_part_null;
 -- should be ok
 execute pstmt_def_insert(null);
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 47641a4..c565d7f 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2118,13 +2118,13 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 
--- check partition cannot be attached if default exists
+-- check partition cannot be attached if default has some row for its values
 CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 INSERT INTO list_parted2_def VALUES (11, 'z');
 CREATE TABLE part_3 (LIKE list_parted2);
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
--- should be ok after detaching a default partition
-ALTER TABLE list_parted2 DETACH PARTITION list_parted2_def;
+-- should be ok after deleting the bad row
+DELETE FROM list_parted2_def WHERE a = 11;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
 
 -- adding constraints that describe the desired partition constraint
@@ -2144,6 +2144,9 @@ ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
 ALTER TABLE part_3_4 ALTER a SET NOT NULL;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
 
+-- check if default partition scan skipped
+ALTER TABLE list_parted2_def ADD CONSTRAINT check_a CHECK (a IN (5, 6));
+CREATE TABLE part_55_66 PARTITION OF list_parted2 FOR VALUES IN (55, 66);
 
 -- check validation when attaching range partitions
 CREATE TABLE range_parted (
@@ -2232,6 +2235,18 @@ INSERT INTO part_7 (a, b) VALUES (8, null), (9, 'a');
 SELECT tableoid::regclass, a, b FROM part_7 order by a;
 ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
 
+-- check that leaf partitions of default partition are scanned when
+-- attaching a partitioned table.
+ALTER TABLE part_5 DROP CONSTRAINT check_a;
+CREATE TABLE part5_def PARTITION OF part_5 DEFAULT PARTITION BY LIST(a);
+CREATE TABLE part5_def_p1 PARTITION OF part5_def FOR VALUES IN (5);
+INSERT INTO part5_def_p1 VALUES (5, 'y');
+CREATE TABLE part5_p1 (LIKE part_5);
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+-- should be ok after deleting the bad row
+DELETE FROM part5_def_p1 WHERE b = 'y';
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+
 -- check that the table being attached is not already a partition
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 8d0c252..07d653a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -450,8 +450,6 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
 -- check default partition cannot be created more than once
 CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
 CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
--- check that no partition can be created after default partition
-CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (3);
 
 -- specified literal can't be cast to the partition column data type
 CREATE TABLE bools (
@@ -530,9 +528,13 @@ CREATE TABLE list_parted2 (
 ) PARTITION BY LIST (a);
 CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
 CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+-- check default partition overlap
+INSERT INTO list_parted2 VALUES('X');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y');
 
 CREATE TABLE range_parted2 (
 	a int
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index a44d454..7bd0619 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -165,9 +165,6 @@ insert into list_parted values ('ab', 21);
 insert into list_parted values ('xx', 1);
 insert into list_parted values ('yy', 2);
 select tableoid::regclass, * from list_parted;
--- drop default partition, as we need to add some more partitions to test tuple
--- routing
-drop table part_default;
 
 -- Check tuple routing for partitioned tables
 
diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql
index a8e357c..cb2a551 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -167,6 +167,8 @@ prepare pstmt_def_insert (int) as insert into list_part_def values($1);
 -- should fail
 execute pstmt_def_insert(null);
 execute pstmt_def_insert(1);
+create table list_part_2 partition of list_parted for values in (2);
+execute pstmt_def_insert(2);
 alter table list_parted detach partition list_part_null;
 -- should be ok
 execute pstmt_def_insert(null);
-- 
2.7.4

0005-Check-default-partitition-child-validation-scan-is.patch0000664000175000017500000001277413145251221023543 0ustar  jeevanjeevanFrom a6f42681ba9e9161605ccc2cf6d6112a20d1d00a Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Thu, 17 Aug 2017 13:51:16 +0530
Subject: [PATCH 5/6] Check default partitition child validation scan is
 skippable

Add code to check_default_allows_bound() such that the default
partition children constraints are checked against new partition
constraints for implication and avoid scan of the child of which
existing constraints are implied by new default partition
constraints. Also, added testcase for the same.

Jeevan Ladhe.
---
 src/backend/catalog/partition.c           | 18 ++++++++++++++++++
 src/test/regress/expected/alter_table.out | 14 ++++++++++++--
 src/test/regress/sql/alter_table.sql      |  9 +++++++++
 3 files changed, 39 insertions(+), 2 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index fbc4bec..6e6955a 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -946,7 +946,25 @@ check_default_allows_bound(Relation parent, Relation default_rel,
 
 		/* Lock already taken above. */
 		if (part_relid != RelationGetRelid(default_rel))
+		{
 			part_rel = heap_open(part_relid, NoLock);
+
+			/*
+			 * If the partition constraints on default partition child imply
+			 * that it will not contain any row that would belong to the new
+			 * partition, we can avoid scanning the child table.
+			 */
+			if (PartConstraintImpliedByRelConstraint(part_rel,
+													 def_part_constraints))
+			{
+				ereport(INFO,
+						(errmsg("partition constraint for table \"%s\" is implied by existing constraints",
+								RelationGetRelationName(part_rel))));
+
+				heap_close(part_rel, NoLock);
+				continue;
+			}
+		}
 		else
 			part_rel = default_rel;
 
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index a3e2c82..bef8d55 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3322,6 +3322,18 @@ INFO:  partition constraint for table "part_3_4" is implied by existing constrai
 ALTER TABLE list_parted2_def ADD CONSTRAINT check_a CHECK (a IN (5, 6));
 CREATE TABLE part_55_66 PARTITION OF list_parted2 FOR VALUES IN (55, 66);
 INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
+-- check if default partition child relation scan skipped
+DROP TABLE list_parted2_def;
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT PARTITION BY RANGE (a);
+CREATE TABLE list_parted2_def_p1 PARTITION OF list_parted2_def FOR VALUES FROM (21) TO (30);
+CREATE TABLE list_parted2_def_p2 PARTITION OF list_parted2_def FOR VALUES FROM (31) TO (40);
+ALTER TABLE list_parted2_def_p1 ADD CONSTRAINT check_a CHECK (a IN (21, 22));
+ALTER TABLE list_parted2_def_p2 ADD CONSTRAINT check_a CHECK (a IN (31, 32));
+CREATE TABLE part_9 PARTITION OF list_parted2 FOR VALUES IN (9);
+INFO:  partition constraint for table "list_parted2_def_p1" is implied by existing constraints
+INFO:  partition constraint for table "list_parted2_def_p2" is implied by existing constraints
+CREATE TABLE part_31 PARTITION OF list_parted2 FOR VALUES IN (31);
+INFO:  partition constraint for table "list_parted2_def_p1" is implied by existing constraints
 -- check validation when attaching range partitions
 CREATE TABLE range_parted (
 	a int,
@@ -3399,7 +3411,6 @@ ALTER TABLE part_7 ATTACH PARTITION part_7_a_null FOR VALUES IN ('a', null);
 INFO:  partition constraint for table "part_7_a_null" is implied by existing constraints
 ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
 INFO:  partition constraint for table "part_7" is implied by existing constraints
-INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
 -- Same example, but check this time that the constraint correctly detects
 -- violating rows
 ALTER TABLE list_parted2 DETACH PARTITION part_7;
@@ -3413,7 +3424,6 @@ SELECT tableoid::regclass, a, b FROM part_7 order by a;
 (2 rows)
 
 ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
-INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
 ERROR:  partition constraint is violated by some row
 -- check that leaf partitions of default partition are scanned when
 -- attaching a partitioned table.
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index c565d7f..92a93eb 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2147,6 +2147,15 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
 -- check if default partition scan skipped
 ALTER TABLE list_parted2_def ADD CONSTRAINT check_a CHECK (a IN (5, 6));
 CREATE TABLE part_55_66 PARTITION OF list_parted2 FOR VALUES IN (55, 66);
+-- check if default partition child relation scan skipped
+DROP TABLE list_parted2_def;
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT PARTITION BY RANGE (a);
+CREATE TABLE list_parted2_def_p1 PARTITION OF list_parted2_def FOR VALUES FROM (21) TO (30);
+CREATE TABLE list_parted2_def_p2 PARTITION OF list_parted2_def FOR VALUES FROM (31) TO (40);
+ALTER TABLE list_parted2_def_p1 ADD CONSTRAINT check_a CHECK (a IN (21, 22));
+ALTER TABLE list_parted2_def_p2 ADD CONSTRAINT check_a CHECK (a IN (31, 32));
+CREATE TABLE part_9 PARTITION OF list_parted2 FOR VALUES IN (9);
+CREATE TABLE part_31 PARTITION OF list_parted2 FOR VALUES IN (31);
 
 -- check validation when attaching range partitions
 CREATE TABLE range_parted (
-- 
2.7.4

0006-Documentation-for-default-partition.patch0000664000175000017500000002245413145251221021017 0ustar  jeevanjeevanFrom 3db2c862243b965696699560ecb5d846aa831762 Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Thu, 17 Aug 2017 13:53:29 +0530
Subject: [PATCH 6/6] Documentation for default partition.

This patch adds documentation for default partition for
CREATE TABLE and ALTER TABLE commands with example.
Also, added documentation for pg_partitioned_table new
field partdefid.

Jeevan Ladhe.
---
 doc/src/sgml/catalogs.sgml         | 11 +++++++++++
 doc/src/sgml/ref/alter_table.sgml  | 35 ++++++++++++++++++++++++++++++++---
 doc/src/sgml/ref/create_table.sgml | 32 +++++++++++++++++++++++++++++---
 3 files changed, 72 insertions(+), 6 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index ef7054c..b381e7e 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -4739,6 +4739,17 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</>:<replaceable>&lt;salt&gt;<
      </row>
 
      <row>
+      <entry><structfield>partdefid</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+      <entry>
+       The OID of the <structname>pg_class</> entry for the default partition
+       of this partitioned table, or zero if this partitioned table does not
+       have a default partition.
+     </entry>
+     </row>
+
+     <row>
       <entry><structfield>partattrs</structfield></entry>
       <entry><type>int2vector</type></entry>
       <entry><literal><link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.attnum</literal></entry>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 6960032..8dd0f14 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -34,7 +34,7 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
 ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable> [ OWNED BY <replaceable class="PARAMETER">role_name</replaceable> [, ... ] ]
     SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable> [ NOWAIT ]
 ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
-    ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+    ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> { FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> | DEFAULT }
 ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
     DETACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable>
 
@@ -765,11 +765,18 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
    </varlistentry>
 
    <varlistentry>
-    <term><literal>ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable></literal></term>
+    <term><literal>ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> { FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> | DEFAULT }</literal></term>
     <listitem>
      <para>
       This form attaches an existing table (which might itself be partitioned)
-      as a partition of the target table using the same syntax for
+      as a partition of the target table. The table can be attached
+      as a partition for specific values using <literal>FOR VALUES
+      </literal> or as a default partition by using <literal>DEFAULT
+      </literal>.
+     </para>
+
+     <para>
+      A partition using <literal>FOR VALUES</literal> uses same syntax for
       <replaceable class="PARAMETER">partition_bound_spec</replaceable> as
       <xref linkend="sql-createtable">.  The partition bound specification
       must correspond to the partitioning strategy and partition key of the
@@ -798,6 +805,10 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       a list partition that will not accept <literal>NULL</literal> values,
       also add <literal>NOT NULL</literal> constraint to the partition key
       column, unless it's an expression.
+      Also, if there exists a default partition table for the parent table,
+      then the default partition (if it is a regular table) is scanned to
+      check that no existing row in default partition would fit in the
+      partition that is being attached.
      </para>
 
      <para>
@@ -806,6 +817,17 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       (See the discussion in <xref linkend="SQL-CREATEFOREIGNTABLE"> about
       constraints on the foreign table.)
      </para>
+
+     <para>
+      When a table has a default partition, defining a new partition changes
+      the partition constraint for the default partition. The default
+      partition can't contain any rows that would need to be moved to the new
+      partition, and will be scanned to verify that none are present. This
+      scan, like the scan of the new partition, can be avoided if an
+      appropriate <literal>CHECK</literal> constraint is present. Also like
+      the scan of the new partition, it is always skipped when the default
+      partition is a foreign table.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -1396,6 +1418,13 @@ ALTER TABLE cities
 </programlisting></para>
 
   <para>
+   Attach a default partition to a partitioned table:
+<programlisting>
+ALTER TABLE cities
+    ATTACH PARTITION cities_partdef DEFAULT;
+</programlisting></para>
+
+  <para>
    Detach a partition from partitioned table:
 <programlisting>
 ALTER TABLE measurement
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index e9c2c49..c08abac 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -49,7 +49,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
   { <replaceable class="PARAMETER">column_name</replaceable> [ WITH OPTIONS ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
     | <replaceable>table_constraint</replaceable> }
     [, ... ]
-) ] FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+) ] { FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> | DEFAULT }
 [ PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
 [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
 [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
@@ -250,11 +250,13 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
    </varlistentry>
 
    <varlistentry id="SQL-CREATETABLE-PARTITION">
-    <term><literal>PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable> FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable></literal></term>
+    <term><literal>PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable> { FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> | DEFAULT }</literal></term>
     <listitem>
      <para>
       Creates the table as a <firstterm>partition</firstterm> of the specified
-      parent table.
+      parent table. The table can be created either as a partition for specific
+      values using <literal>FOR VALUES</literal> or as a default partition
+      using <literal>DEFAULT</literal>.
      </para>
 
      <para>
@@ -343,6 +345,23 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
      </para>
 
      <para>
+     If <literal>DEFAULT</literal> is specified, the table will be
+     created as a default partition of the parent table. The parent can
+     either be a list or range partitioned table. A partition key value
+     not fitting into any other partition of the given parent will be
+     routed to the default partition. There can be only one default
+     partition for a given parent table.
+     </para>
+
+     <para>
+     If the given parent is already having a default partition then
+     adding a new partition would result in an error if the default
+     partition contains a record that would fit in the new partition
+     being added. This check is not performed if the default partition
+     is a foreign table.
+     </para>
+
+     <para>
       A partition must have the same column names and types as the partitioned
       table to which it belongs.  If the parent is specified <literal>WITH
       OIDS</literal> then all partitions must have OIDs; the parent's OID
@@ -1679,6 +1698,13 @@ CREATE TABLE cities_ab
 CREATE TABLE cities_ab_10000_to_100000
     PARTITION OF cities_ab FOR VALUES FROM (10000) TO (100000);
 </programlisting></para>
+
+  <para>
+   Create a default partition of partitioned table:
+<programlisting>
+CREATE TABLE cities_partdef
+    PARTITION OF cities DEFAULT;
+</programlisting></para>
  </refsect1>
 
  <refsect1 id="SQL-CREATETABLE-compatibility">
-- 
2.7.4

#163Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Ashutosh Bapat (#153)
Re: Adding support for Default partition in partitioning

Hi Ashutosh,

Please find my feedback inlined.

On Fri, Jul 28, 2017 at 7:00 PM, Ashutosh Bapat <
ashutosh.bapat@enterprisedb.com> wrote:

On Wed, Jul 26, 2017 at 5:44 PM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

Hi,

I have rebased the patches on the latest commit.

Thanks for rebasing the patches. The patches apply and compile
cleanly. make check passes.

Here are some review comments
0001 patch
Most of this patch is same as 0002 patch posted in thread [1]. I have
extensively reviewed that patch for Amit Langote. Can you please compare
these
two patches and try to address those comments OR just use patch from that
thread? For example, canSkipPartConstraintValidation() is named as
PartConstraintImpliedByRelConstraint() in that patch. OR
+ if (scanRel_constr == NULL)
+ return false;
+
is not there in that patch since returning false is wrong when
partConstraint
is NULL. I think this patch needs those fixes. Also, this patch set would
need
a rebase when 0001 from that thread gets committed.

I have renamed the canSkipPartConstraintValidation() to
PartConstraintImpliedByRelConstraint() and made other changes applicable per
Amit’s patch. This patch also refactors the scanning logic in
ATExecAttachPartition()
and adds it into a function ValidatePartitionConstraints(), hence I could
not use
Amit’s patch as it is. Please have a look into the new patch and let me
know if it
looks fine to you.

0002 patch
+ if (!and_args)
+ result = NULL;
Add "NULL, if there are not partition constraints e.g. in case of default
partition as the only partition.".

Added. Please check.

This patch avoids calling
validatePartitionConstraints() and hence canSkipPartConstraintValidation()
when
partConstraint is NULL, but patches in [1] introduce more callers of
canSkipPartConstraintValidation() which may pass NULL. So, it's better
that we
handle that case.

Following code added in patch 0001 now should take care of this.
+ num_check = (constr != NULL) ? constr->num_check : 0;

0003 patch
+        parentRel = heap_open(parentOid, AccessExclusiveLock);
In [2], Amit Langote has given a reason as to why heap_drop_with_catalog()
should not heap_open() the parent relation. But this patch still calls
heap_open() without giving any counter argument. Also I don't see
get_default_partition_oid() using Relation anywhere. If you remove that
heap_open() please remove following heap_close().
+        heap_close(parentRel, NoLock);

As clarified earlier this was addressed in 0004 patch of V24 series. In
current set of patches this is now addressed in patch 0003 itself.

+                        /*
+                         * The default partition accepts any non-specified
+                         * value, hence it should not get a mapped index
while
+                         * assigning those for non-null datums.
+                         */
Instead of "any non-specified value", you may want to use "any value not
specified in the lists of other partitions" or something like that.

Changed the comment.

+         * If this is a NULL, route it to the null-accepting partition.
+         * Otherwise, route by searching the array of partition bounds.
You may want to write it as "If this is a null partition key, ..." to
clarify
what's NULL.

Changed the comment.

+         * cur_index < 0 means we could not find a non-default partition
of
+         * this parent. cur_index >= 0 means we either found the leaf
+         * partition, or the next parent to find a partition of.
+         *
+         * If we couldn't find a non-default partition check if the
default
+         * partition exists, if it does, get its index.
In order to avoid repeating "we couldn't find a ..."; you may want to add
",
try default partition if one exists." in the first sentence itself.

Sorry, but I am not really sure how this change would make the comment
more readable than the current one.

get_default_partition_oid() is defined in this patch and then redefined in
0004. Let's define it only once, mostly in or before 0003 patch.

get_default_partition_oid() is now defined only once in patch 0003.

+ * partition strategy. Assign the parent strategy to the default
s/parent/parent's/

Fixed.

+-- attaching default partition overlaps if the default partition already
exists
+CREATE TABLE def_part PARTITION OF list_parted DEFAULT;
+CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT;
+ERROR:  cannot attach a new partition to table "list_parted" having a
default partition
For 0003 patch this testcase is same as the testcase in the next hunk; no
new
partition can be added after default partition. Please add this testcase in
next set of patches.

Though the error message is same, the purpose of testing is different:
1. There cannot be more than one default partition,
2. and other is to test the fact the a new partition cannot be added if the
default partition exists.
The later test needs to be removed in next patch where we add support for
adding new partition even if a default partition exists.

+-- fail
+insert into part_default values ('aa', 2);
May be explain why the insert should fail. "A row, which would fit
other partition, does not fit default partition, even when inserted
directly"
or something like that. I see that many of the tests in that file do not
explain why something should "fail" or be "ok", but may be it's better to
document the reason for better readability and future reference.

Added a comment.

+-- check in case of multi-level default partitioned table

s/in/the/ ?. Or you may want to reword it as "default partitioned
partition in
multi-level partitioned table" as there is nothing like "default
partitioned
table". May be we need a testcase where every level of a multi-level
partitioned table has a default partition.

I have changed the comment as well as added a test scenario where the

partition further has a default partition.

+-- drop default, as we need to add some more partitions to test tuple
routing
Should be clubbed with the actual DROP statement?

This is needed in patch 0003, as it prevents adding/creating further
partitions
to parent. This is removed in patch 0004.

+-- Check that addition or removal of any partition is correctly dealt
with by
+-- default partition table when it is being used in cached plan.
Plan of a prepared statement gets cached only after it's executed 5 times.
Before that the statement gets invalidated but there's not cached plan that
gets invalidated. The test is fine here, but in order to test the cached
plan
as mentioned in the comment, you will need to execute the statement 5 times
before executing drop statement. That's probably unnecessary, so just
modify
the comment to say "prepared statements instead of cached plan".

Agree. Fixed.

0004 patch
The patch adds another column partdefid to catalog pg_partitioned_table.
The
column gives OID of the default partition for a given partitioned table.
This
means that the default partition's OID is stored at two places 1. in the
default partition table's pg_class entry and in pg_partitioned_table.
There is
no way to detect when these two go out of sync. Keeping those two in sync
is
also a maintenance burdern. Given that default partition's OID is required
only
while adding/dropping a partition, which is a less frequent operation, it
won't
hurt to join a few catalogs (pg_inherits and pg_class in this case) to
find out
the default partition's OID. That will be occasional performance hit
worth the otherwise maintenance burden.

To avoid partdefid of pg_partitioned_table going out of sync during any
future developments I have added an assert in RelationBuildPartitionDesc()
in patch 0003 in V25 series. I believe DBAs are not supposed to alter any
catalog tables, hence instead of adding an error, I added an Assert to
prevent
this breaking during development cycle.
We have similar kind of duplications in other catalogs e.g. pg_opfamily,
pg_operator etc. Also, per Robert [1]/messages/by-id/CA+Tgmobbnamyvii0pRdg9pp_jLHSUvq7u5SiRrVV0tEFFU58Tg@mail.gmail.com, the other route of searching pg_class
and pg_inherits is going to cause some syscache bloat.

[1]: /messages/by-id/CA+Tgmobbnamyvii0pRdg9pp_jLHSUvq7u5SiRrVV0tEFFU58Tg@mail.gmail.com
/messages/by-id/CA+Tgmobbnamyvii0pRdg9pp_jLHSUvq7u5SiRrVV0tEFFU58Tg@mail.gmail.com

Regards,
Jeevan Ladhe

#164Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Jeevan Ladhe (#163)
Re: Adding support for Default partition in partitioning

Hi Ashutosh,

On Thu, Aug 17, 2017 at 3:41 PM, Jeevan Ladhe <jeevan.ladhe@enterprisedb.com

wrote:

Hi Ashutosh,

Please find my feedback inlined.

On Fri, Jul 28, 2017 at 7:00 PM, Ashutosh Bapat <
ashutosh.bapat@enterprisedb.com> wrote:

On Wed, Jul 26, 2017 at 5:44 PM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

Hi,

I have rebased the patches on the latest commit.

Thanks for rebasing the patches. The patches apply and compile
cleanly. make check passes.

Here are some review comments
0001 patch
Most of this patch is same as 0002 patch posted in thread [1]. I have
extensively reviewed that patch for Amit Langote. Can you please compare
these
two patches and try to address those comments OR just use patch from that
thread? For example, canSkipPartConstraintValidation() is named as
PartConstraintImpliedByRelConstraint() in that patch. OR
+ if (scanRel_constr == NULL)
+ return false;
+
is not there in that patch since returning false is wrong when
partConstraint
is NULL. I think this patch needs those fixes. Also, this patch set would
need
a rebase when 0001 from that thread gets committed.

I have renamed the canSkipPartConstraintValidation() to
PartConstraintImpliedByRelConstraint() and made other changes applicable
per
Amit’s patch. This patch also refactors the scanning logic in
ATExecAttachPartition()
and adds it into a function ValidatePartitionConstraints(), hence I could
not use
Amit’s patch as it is. Please have a look into the new patch and let me
know if it
looks fine to you.

0002 patch
+ if (!and_args)
+ result = NULL;
Add "NULL, if there are not partition constraints e.g. in case of default
partition as the only partition.".

Added. Please check.

This patch avoids calling
validatePartitionConstraints() and hence canSkipPartConstraintValidation()
when
partConstraint is NULL, but patches in [1] introduce more callers of
canSkipPartConstraintValidation() which may pass NULL. So, it's better
that we
handle that case.

Following code added in patch 0001 now should take care of this.
+ num_check = (constr != NULL) ? constr->num_check : 0;

0003 patch
+        parentRel = heap_open(parentOid, AccessExclusiveLock);
In [2], Amit Langote has given a reason as to why heap_drop_with_catalog()
should not heap_open() the parent relation. But this patch still calls
heap_open() without giving any counter argument. Also I don't see
get_default_partition_oid() using Relation anywhere. If you remove that
heap_open() please remove following heap_close().
+        heap_close(parentRel, NoLock);

As clarified earlier this was addressed in 0004 patch of V24 series. In
current set of patches this is now addressed in patch 0003 itself.

+                        /*
+                         * The default partition accepts any
non-specified
+                         * value, hence it should not get a mapped index
while
+                         * assigning those for non-null datums.
+                         */
Instead of "any non-specified value", you may want to use "any value not
specified in the lists of other partitions" or something like that.

Changed the comment.

+         * If this is a NULL, route it to the null-accepting partition.
+         * Otherwise, route by searching the array of partition bounds.
You may want to write it as "If this is a null partition key, ..." to
clarify
what's NULL.

Changed the comment.

+         * cur_index < 0 means we could not find a non-default partition
of
+         * this parent. cur_index >= 0 means we either found the leaf
+         * partition, or the next parent to find a partition of.
+         *
+         * If we couldn't find a non-default partition check if the
default
+         * partition exists, if it does, get its index.
In order to avoid repeating "we couldn't find a ..."; you may want to add
",
try default partition if one exists." in the first sentence itself.

Sorry, but I am not really sure how this change would make the comment
more readable than the current one.

get_default_partition_oid() is defined in this patch and then redefined in
0004. Let's define it only once, mostly in or before 0003 patch.

get_default_partition_oid() is now defined only once in patch 0003.

+ * partition strategy. Assign the parent strategy to the default
s/parent/parent's/

Fixed.

+-- attaching default partition overlaps if the default partition already
exists
+CREATE TABLE def_part PARTITION OF list_parted DEFAULT;
+CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT;
+ERROR:  cannot attach a new partition to table "list_parted" having a
default partition
For 0003 patch this testcase is same as the testcase in the next hunk; no
new
partition can be added after default partition. Please add this testcase
in
next set of patches.

Though the error message is same, the purpose of testing is different:
1. There cannot be more than one default partition,
2. and other is to test the fact the a new partition cannot be added if the
default partition exists.
The later test needs to be removed in next patch where we add support for
adding new partition even if a default partition exists.

+-- fail
+insert into part_default values ('aa', 2);
May be explain why the insert should fail. "A row, which would fit
other partition, does not fit default partition, even when inserted
directly"
or something like that. I see that many of the tests in that file do not
explain why something should "fail" or be "ok", but may be it's better to
document the reason for better readability and future reference.

Added a comment.

+-- check in case of multi-level default partitioned table

s/in/the/ ?. Or you may want to reword it as "default partitioned
partition in
multi-level partitioned table" as there is nothing like "default
partitioned
table". May be we need a testcase where every level of a multi-level
partitioned table has a default partition.

I have changed the comment as well as added a test scenario where the

partition further has a default partition.

+-- drop default, as we need to add some more partitions to test tuple
routing
Should be clubbed with the actual DROP statement?

This is needed in patch 0003, as it prevents adding/creating further
partitions
to parent. This is removed in patch 0004.

+-- Check that addition or removal of any partition is correctly dealt
with by
+-- default partition table when it is being used in cached plan.
Plan of a prepared statement gets cached only after it's executed 5 times.
Before that the statement gets invalidated but there's not cached plan
that
gets invalidated. The test is fine here, but in order to test the cached
plan
as mentioned in the comment, you will need to execute the statement 5
times
before executing drop statement. That's probably unnecessary, so just
modify
the comment to say "prepared statements instead of cached plan".

Agree. Fixed.

0004 patch
The patch adds another column partdefid to catalog pg_partitioned_table.
The
column gives OID of the default partition for a given partitioned table.
This
means that the default partition's OID is stored at two places 1. in the
default partition table's pg_class entry and in pg_partitioned_table.
There is
no way to detect when these two go out of sync. Keeping those two in sync
is
also a maintenance burdern. Given that default partition's OID is
required only
while adding/dropping a partition, which is a less frequent operation, it
won't
hurt to join a few catalogs (pg_inherits and pg_class in this case) to
find out
the default partition's OID. That will be occasional performance hit
worth the otherwise maintenance burden.

To avoid partdefid of pg_partitioned_table going out of sync during any
future developments I have added an assert in RelationBuildPartitionDesc()
in patch 0003 in V25 series. I believe DBAs are not supposed to alter any
catalog tables, hence instead of adding an error, I added an Assert to
prevent
this breaking during development cycle.
We have similar kind of duplications in other catalogs e.g. pg_opfamily,
pg_operator etc. Also, per Robert [1], the other route of searching
pg_class
and pg_inherits is going to cause some syscache bloat.

[1] /messages/by-id/CA+Tgmobbnamyvii0pRdg9pp_
jLHSUvq7u5SiRrVV0tEFFU58Tg%40mail.gmail.com

You can see your comments addressed as above in patch series v25 here[1]/messages/by-id/CAOgcT0NwqnavYtu-QM-DAZ6N=wTiqKgy83WwtO2x94LSLZ1-Sw@mail.gmail.com.

[1]: /messages/by-id/CAOgcT0NwqnavYtu-QM-DAZ6N=wTiqKgy83WwtO2x94LSLZ1-Sw@mail.gmail.com
/messages/by-id/CAOgcT0NwqnavYtu-QM-DAZ6N=wTiqKgy83WwtO2x94LSLZ1-Sw@mail.gmail.com

Regards,
Jeevan Ladhe

#165Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Robert Haas (#158)
Re: Adding support for Default partition in partitioning

Hi Robert,

Please find my feedback inlined.
I have addressed following comments in V25 patch[1]/messages/by-id/CAOgcT0NwqnavYtu-QM-DAZ6N=wTiqKgy83WwtO2x94LSLZ1-Sw@mail.gmail.com.

0002:
This patch teaches the partitioning code to handle the NIL returned by
get_qual_for_list().
This is needed because a default partition will not have any constraints

in

case
it is the only partition of its parent.

Perhaps it would be better to make validatePartConstraint() a no-op
when the constraint is empty rather than putting the logic in the
caller. Otherwise, every place that calls validatePartConstraint()
has to think about whether or not the constraint-is-NULL case needs to
be handled.

I have now added a check in ValidatePartConstraint(). This change is made

in 0001 patch.

0003:
Support for default partition with the restriction of preventing

addition of

any
new partition after default partition.

This looks generally reasonable, but can't really be committed without
the later patches, because it might break pg_dump, which won't know
that the DEFAULT partition must be dumped last and might therefore get
the dump ordering wrong, and of course also because it reverts commit
c1e0e7e1d790bf18c913e6a452dea811e858b554.

Thanks Robert for looking into this. The purpose of having different
patches is
just to ease the review process and the basic patch of introducing the
default
partition support and extending support for addition of new partition
should go
together.

0004:
Store the default partition OID in pg_partition_table, this will help us

to

retrieve the OID of default relation when we don't have the relation

cache

available. This was also suggested by Amit Langote here[1].

I looked this over and I think this is the right approach. An
alternative way to avoid needing a relcache entry in
heap_drop_with_catalog() would be for get_default_partition_oid() to
call find_inheritance_children() here and then use a syscache lookup
to get the partition bound for each one, but that's still going to
cause some syscache bloat.

To safeguard future development from missing this and leaving it out of
sync, I
have added an Assert in RelationBuildPartitionDesc() to cross check the
partdefid.

0005:
Extend default partitioning support to allow addition of new partitions.

+       if (spec->is_default)
+       {
+               /* Default partition cannot be added if there already
exists one. */
+               if (partdesc->nparts > 0 &&
partition_bound_has_default(boundinfo))
+               {
+                       with = boundinfo->default_index;
+                       ereport(ERROR,
+
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                                        errmsg("partition \"%s\"
conflicts with existing default partition \"%s\"",
+                                                       relname,
get_rel_name(partdesc->oids[with])),
+                                        parser_errposition(pstate,
spec->location)));
+               }
+
+               return;
+       }

I generally think it's good to structure the code so as to minimize
the indentation level. In this case, if you did if (partdesc->nparts
== 0 || !partition_bound_has_default(boundinfo)) return; first, then
the rest of it could be one level less indented. Also, perhaps it
would be clearer to test boundinfo == NULL rather than
partdesc->nparts == 0, assuming they are equivalent.

Fixed.

0006:

Extend default partitioning validation code to reuse the refactored code

in

patch 0001.

I'm having a very hard time understanding what's going on with this
patch. It certainly doesn't seem to be just refactoring things to use
the code from 0001. For example:

-                       if (ExecCheck(partqualstate, econtext))
+                       if (!ExecCheck(partqualstate, econtext))

It seems hard to believe that refactoring things to use the code from
0001 would involve inverting the value of this test.

+                * derived from the bounds(the partition constraint
never evaluates to
+                * NULL, so negating it like this is safe).

I don't see it being negated.

I think this patch needs a better explanation of what it's trying to
do, and better comments. I gather that at least part of the point
here is to skip validation scans on default partitions if the default
partition has been constrained not to contain any values that would
fall in the new partition, but neither the commit message for 0006 nor
your description here make that very clear.

In V25 series, this is now part of patch 0004, and should avoid any
confusion as above. I have tried to add verbose comment in commit
message as well as I have improved the code comments in this code
area.

[0008 documentation]

-      attached is marked <literal>NO INHERIT</literal>, the command will
fail;
-      such a constraint must be recreated without the <literal>NO
INHERIT</literal>
-      clause.
+      target table.
+     </para>

I don't favor inserting a paragraph break here.

Fixed.

+ then the default partition(if it is a regular table) is scanned to
check

The sort-of-trivial problem with this is that an open parenthesis
should be proceeded by a space. But I think this won't be clear. I
think you should move this below the following paragraph, which
describes what happens for foreign tables, and then add a new
paragraph like this:

When a table has a default partition, defining a new partition changes
the partition constraint for the default partition. The default
partition can't contain any rows that would need to be moved to the
new partition, and will be scanned to verify that none are present.
This scan, like the scan of the new partition, can be avoided if an
appropriate <literal>CHECK</literal> constraint is present. Also like
the scan of the new partition, it is always skipped when the default
partition is a foreign table.

I have made the change as suggested.

-) ] FOR VALUES <replaceable
class="PARAMETER">partition_bound_spec</replaceable>
+) ] { DEFAULT | FOR VALUES <replaceable
class="PARAMETER">partition_bound_spec</replaceable> }

I recommend writing FOR VALUES | DEFAULT both here and in the ATTACH
PARTITION syntax summary.

Changed.

+     If <literal>DEFAULT</literal> is specified the table will be created
as a
+     default partition of the parent table. The parent can either be a
list or
+     range partitioned table. A partition key value not fitting into any
other
+     partition of the given parent will be routed to the default
partition.
+     There can be only one default partition for a given parent table.
+     </para>
+
+     <para>
+     If the given parent is already having a default partition then
adding a
+     new partition would result in an error if the default partition
contains a
+     record that would fit in the new partition being added. This check
is not
+     performed if the default partition is a foreign table.
+     </para>

The indentation isn't correct here - it doesn't match the surrounding
paragraphs. The bit about list or range partitioning doesn't match
the actual behavior of the other patches, but maybe you intended this
to document both this feature and what Beena's doing.

I have tried to fix this now.

[1]: /messages/by-id/CAOgcT0NwqnavYtu-QM-DAZ6N=wTiqKgy83WwtO2x94LSLZ1-Sw@mail.gmail.com
/messages/by-id/CAOgcT0NwqnavYtu-QM-DAZ6N=wTiqKgy83WwtO2x94LSLZ1-Sw@mail.gmail.com

#166Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Robert Haas (#160)
Re: Adding support for Default partition in partitioning

Hi Robert,

0007:

This patch introduces code to check if the scanning of default

partition

child
can be skipped if it's constraints are proven.

If I understand correctly, this is actually a completely separate
feature not intrinsically related to default partitioning.

I don't see this as a new feature, since scanning the default partition
will be introduced by this series of patches only, and rather than a
feature this can be classified as a completeness of default skip
validation logic. Your thoughts?

Currently, when a partitioned table is attached, we check whether all
the scans can be checked but not whether scans on some partitions can
be attached. So there are two separate things:

1. When we introduce default partitioning, we need scan the default
partition either when (a) any partition is attached or (b) any
partition is created.

2. In any situation where scans are needed (scanning the partition
when it's attached, scanning the default partition when some other
partition is attached, scanning the default when a new partition is
created), we can run predicate_implied_by for each partition to see
whether the scan of that partition can be skipped.

Those two changes are independent. We could do (1) without doing (2)
or (2) without doing (1) or we could do both. So they are separate
features.

In my V25 series(patch 0005) I have only addressed half of (2) above
i.e. code to check whether the scan of a partition of default partition
can be skipped when other partition is being added. Amit Langote
has submitted[1]/messages/by-id/4cd13b03-846d-dc65-89de-1fd9743a3869@lab.ntt.co.jp a separate patch(0003) to address skipping the scan
of the children of relation when it's being attached as a partition.

[1]: /messages/by-id/4cd13b03-846d-dc65-89de-1fd9743a3869@lab.ntt.co.jp
/messages/by-id/4cd13b03-846d-dc65-89de-1fd9743a3869@lab.ntt.co.jp

Regards,
Jeevan Ladhe

#167Thom Brown
thom@linux.com
In reply to: Jeevan Ladhe (#162)
Re: Adding support for Default partition in partitioning

On 17 August 2017 at 10:59, Jeevan Ladhe <jeevan.ladhe@enterprisedb.com> wrote:

Hi,

On Tue, Aug 15, 2017 at 7:20 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Wed, Jul 26, 2017 at 8:14 AM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

I have rebased the patches on the latest commit.

This needs another rebase.

I have rebased the patch and addressed your and Ashutosh comments on last
set of patches.

The current set of patches contains 6 patches as below:

0001:
Refactoring existing ATExecAttachPartition code so that it can be used for
default partitioning as well

0002:
This patch teaches the partitioning code to handle the NIL returned by
get_qual_for_list().
This is needed because a default partition will not have any constraints in
case
it is the only partition of its parent.

0003:
Support for default partition with the restriction of preventing addition of
any
new partition after default partition. This is a merge of 0003 and 0004 in
V24 series.

0004:
Extend default partitioning support to allow addition of new partitions
after
default partition is created/attached. This patch is a merge of patches
0005 and 0006 in V24 series to simplify the review process. The
commit message has more details regarding what all is included.

0005:
This patch introduces code to check if the scanning of default partition
child
can be skipped if it's constraints are proven.

0006:
Documentation.

PFA, and let me know in case of any comments.

Thanks. Applies fine, and I've been exercising the patch and it is
doing everything it's supposed to do.

I am, however, curious to know why the planner can't optimise the following:

SELECT * FROM mystuff WHERE mystuff = (1::int,'JP'::text,'blue'::text);

This exhaustively checks all partitions, but if I change it to:

SELECT * FROM mystuff WHERE (id, country, content) =
(1::int,'JP'::text,'blue'::text);

It works fine.

The former filters like so: ((mystuff_default_1.*)::mystuff = ROW(1,
'JP'::text, 'blue'::text))

Shouldn't it instead do:

((mystuff_default_1.id, mystuff_default_1.country,
mystuff_default_1.content)::mystuff = ROW(1, 'JP'::text,
'blue'::text))

So it's not really to do with this patch; it's just something I
noticed while testing.

Thom

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#168Robert Haas
robertmhaas@gmail.com
In reply to: Jeevan Ladhe (#165)
Re: Adding support for Default partition in partitioning

On Thu, Aug 17, 2017 at 6:24 AM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

I have addressed following comments in V25 patch[1].

Committed 0001. Since that code seems to be changing about every 10
minutes, it seems best to get this refactoring out of the way before
it changes again.

--
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

#169Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Robert Haas (#168)
Re: Adding support for Default partition in partitioning

On Fri, Aug 18, 2017 at 12:25 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Thu, Aug 17, 2017 at 6:24 AM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

I have addressed following comments in V25 patch[1].

Committed 0001. Since that code seems to be changing about every 10
minutes, it seems best to get this refactoring out of the way before
it changes again.

Thanks Robert for taking care of this.

Regards,
Jeevan Ladhe

#170Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Jeevan Ladhe (#162)
1 attachment(s)
Re: Adding support for Default partition in partitioning

Hi,

On Thu, Aug 17, 2017 at 3:29 PM, Jeevan Ladhe <jeevan.ladhe@enterprisedb.com

wrote:

Hi,

On Tue, Aug 15, 2017 at 7:20 PM, Robert Haas <robertmhaas@gmail.com>
wrote:

On Wed, Jul 26, 2017 at 8:14 AM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

I have rebased the patches on the latest commit.

This needs another rebase.

I have rebased the patch and addressed your and Ashutosh comments on last
set of patches.

The current set of patches contains 6 patches as below:

0001:
Refactoring existing ATExecAttachPartition code so that it can be used for
default partitioning as well

0002:
This patch teaches the partitioning code to handle the NIL returned by
get_qual_for_list().
This is needed because a default partition will not have any constraints
in case
it is the only partition of its parent.

0003:
Support for default partition with the restriction of preventing addition
of any
new partition after default partition. This is a merge of 0003 and 0004 in
V24 series.

0004:
Extend default partitioning support to allow addition of new partitions
after
default partition is created/attached. This patch is a merge of patches
0005 and 0006 in V24 series to simplify the review process. The
commit message has more details regarding what all is included.

0005:
This patch introduces code to check if the scanning of default partition
child
can be skipped if it's constraints are proven.

0006:
Documentation.

After patch 0001 in above series got committed[1]/messages/by-id/CA+TgmoYp-QePjTGEC6W+Rfuh=TMZ4Hj8t2fX2o8cbhto6zS9DA@mail.gmail.com, I have rebased the
patches.

The attached set of patches now looks like below:

0001:

This patch teaches the partitioning code to handle the NIL returned by

get_qual_for_list().

This is needed because a default partition will not have any constraints in
case

it is the only partition of its parent.

0002:

Support for default partition with the restriction of preventing addition
of any

new partition after default partition. This is a merge of 0003 and 0004 in

V24 series.

0003:

Extend default partitioning support to allow addition of new partitions
after

default partition is created/attached. This patch is a merge of patches

0005 and 0006 in V24 series to simplify the review process. The

commit message has more details regarding what all is included.

0004:

This patch introduces code to check if the scanning of default partition
child

can be skipped if it's constraints are proven.

0005:

Documentation.

[1]: /messages/by-id/CA+TgmoYp-QePjTGEC6W+Rfuh=TMZ4Hj8t2fX2o8cbhto6zS9DA@mail.gmail.com
/messages/by-id/CA+TgmoYp-QePjTGEC6W+Rfuh=TMZ4Hj8t2fX2o8cbhto6zS9DA@mail.gmail.com

Regards,
Jeevan Ladhe

Attachments:

default_partition_V25_rebase.tarapplication/x-tar; name=default_partition_V25_rebase.tarDownload
0001-Fix-assumptions-that-get_qual_from_partbound-cannot.patch0000644000076500000240000000736013146531753024055 0ustar  jeevanstaffFrom acbd76fb7a8c2348aa26744055e777f8fc1ec872 Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Mon, 21 Aug 2017 15:29:32 +0530
Subject: [PATCH 1/5] Fix assumptions that get_qual_from_partbound() cannot
 return NIL list.

Current partitioning code assumes that there cannot be any partition
without partition constraints, but in future this assumption might
not hold true.
e.g. if we introduce support for default partition, then default
partition will not have any constraints in case it is the only
partition of its parent.

Jeevan Ladhe.
---
 src/backend/catalog/partition.c  |  8 ++++++--
 src/backend/commands/tablecmds.c | 37 +++++++++++++++++++++----------------
 2 files changed, 27 insertions(+), 18 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 96a64ce6b2..11189e28ec 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -955,7 +955,8 @@ RelationGetPartitionQual(Relation rel)
  * get_partition_qual_relid
  *
  * Returns an expression tree describing the passed-in relation's partition
- * constraint.
+ * constraint. If there are no partition constraints returns NULL e.g. in case
+ * default partition is the only partition.
  */
 Expr *
 get_partition_qual_relid(Oid relid)
@@ -968,7 +969,10 @@ get_partition_qual_relid(Oid relid)
 	if (rel->rd_rel->relispartition)
 	{
 		and_args = generate_partition_qual(rel);
-		if (list_length(and_args) > 1)
+
+		if (!and_args)
+			result = NULL;
+		else if (list_length(and_args) > 1)
 			result = makeBoolExpr(AND_EXPR, and_args, -1);
 		else
 			result = linitial(and_args);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 0f08245a67..0fb8238795 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13802,24 +13802,29 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	partConstraint = list_concat(get_qual_from_partbound(attachrel, rel,
 														 cmd->bound),
 								 RelationGetPartitionQual(rel));
-	partConstraint = (List *) eval_const_expressions(NULL,
-													 (Node *) partConstraint);
-	partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
-	partConstraint = list_make1(make_ands_explicit(partConstraint));
 
-	/*
-	 * Adjust the generated constraint to match this partition's attribute
-	 * numbers.
-	 */
-	partConstraint = map_partition_varattnos(partConstraint, 1, attachrel,
-											 rel, &found_whole_row);
-	/* There can never be a whole-row reference here */
-	if (found_whole_row)
-		elog(ERROR, "unexpected whole-row reference found in partition key");
+	/* Skip validation if there are no constraints to validate. */
+	if (partConstraint)
+	{
+		partConstraint = (List *) eval_const_expressions(NULL,
+														 (Node *) partConstraint);
+		partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
+		partConstraint = list_make1(make_ands_explicit(partConstraint));
 
-	/* Validate partition constraints against the table being attached. */
-	ValidatePartitionConstraints(wqueue, attachrel, attachrel_children,
-								 partConstraint);
+		/*
+		 * Adjust the generated constraint to match this partition's attribute
+		 * numbers.
+		 */
+		partConstraint = map_partition_varattnos(partConstraint, 1, attachrel,
+												 rel, &found_whole_row);
+		/* There can never be a whole-row reference here */
+		if (found_whole_row)
+			elog(ERROR, "unexpected whole-row reference found in partition key");
+
+		/* Validate partition constraints against the table being attached. */
+		ValidatePartitionConstraints(wqueue, attachrel, attachrel_children,
+									 partConstraint);
+	}
 
 	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachrel));
 
-- 
2.13.3

0002-Implement-default-partition-support.patch0000644000076500000240000014300513146531753020721 0ustar  jeevanstaffFrom 26bc57bb868eaddc3b15bfe8a07e3869f43e8321 Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Mon, 21 Aug 2017 15:31:49 +0530
Subject: [PATCH 2/5] Implement default partition support

This patch introduces default partition for a list partitioned
table. However, in this version of patch no other partition can
be attached to a list partitioned table if it has a default
partition. To ease the retrieval of default partition OID, this
patch adds a new column 'partdefid' to pg_partitioned_table.

Jeevan Ladhe.
---
 src/backend/catalog/heap.c                 |  35 ++++-
 src/backend/catalog/partition.c            | 212 +++++++++++++++++++++++++++--
 src/backend/commands/tablecmds.c           |  57 +++++++-
 src/backend/nodes/copyfuncs.c              |   1 +
 src/backend/nodes/equalfuncs.c             |   1 +
 src/backend/nodes/outfuncs.c               |   1 +
 src/backend/nodes/readfuncs.c              |   1 +
 src/backend/parser/gram.y                  |  27 +++-
 src/backend/parser/parse_utilcmd.c         |  19 +++
 src/backend/utils/adt/ruleutils.c          |  12 +-
 src/bin/psql/describe.c                    |   8 +-
 src/bin/psql/tab-complete.c                |   4 +-
 src/include/catalog/partition.h            |   3 +
 src/include/catalog/pg_partitioned_table.h |  13 +-
 src/include/nodes/parsenodes.h             |   1 +
 src/test/regress/expected/alter_table.out  |  18 +++
 src/test/regress/expected/create_table.out |   7 +
 src/test/regress/expected/insert.out       |  90 ++++++++++--
 src/test/regress/expected/plancache.out    |  22 +++
 src/test/regress/expected/update.out       |  15 ++
 src/test/regress/sql/alter_table.sql       |  17 +++
 src/test/regress/sql/create_table.sql      |   6 +
 src/test/regress/sql/insert.sql            |  40 ++++++
 src/test/regress/sql/plancache.sql         |  19 +++
 src/test/regress/sql/update.sql            |  15 ++
 25 files changed, 598 insertions(+), 46 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 45ee9ac8b9..06c8324e8c 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1759,7 +1759,8 @@ heap_drop_with_catalog(Oid relid)
 {
 	Relation	rel;
 	HeapTuple	tuple;
-	Oid			parentOid = InvalidOid;
+	Oid			parentOid = InvalidOid,
+				defaultPartOid = InvalidOid;
 
 	/*
 	 * To drop a partition safely, we must grab exclusive lock on its parent,
@@ -1775,6 +1776,21 @@ heap_drop_with_catalog(Oid relid)
 	{
 		parentOid = get_partition_parent(relid);
 		LockRelationOid(parentOid, AccessExclusiveLock);
+
+		/*
+		 * The partition constraint of the default partition depends on the
+		 * partition bounds of every other partition. It is possible that
+		 * other backend might be about to execute a query on the default
+		 * partition table and the query relies on previously cached default
+		 * partition constraints, which won't be correct after removal of a
+		 * partition. We must therefore take a table lock strong enough to
+		 * prevent all queries on the default partition from proceeding until
+		 * we commit and send out a shared-cache-inval notice that will make
+		 * them update their index lists.
+		 */
+		defaultPartOid = get_default_partition_oid(parentOid);
+		if (OidIsValid(defaultPartOid))
+			LockRelationOid(defaultPartOid, AccessExclusiveLock);
 	}
 
 	ReleaseSysCache(tuple);
@@ -1826,6 +1842,13 @@ heap_drop_with_catalog(Oid relid)
 		RemovePartitionKeyByRelId(relid);
 
 	/*
+	 * If the relation being dropped is the default partition itself,
+	 * invalidate its entry in pg_partitioned_table.
+	 */
+	if (relid == defaultPartOid)
+		update_default_partition_oid(parentOid, InvalidOid);
+
+	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -1885,6 +1908,15 @@ heap_drop_with_catalog(Oid relid)
 	if (OidIsValid(parentOid))
 	{
 		/*
+		 * The partition constraint for the default partition depends on the
+		 * partition bounds of every other partition, so we must invalidate
+		 * the relcache entry for that partition every time a partition is
+		 * added or removed.
+		 */
+		if (OidIsValid(defaultPartOid))
+			CacheInvalidateRelcacheByRelid(defaultPartOid);
+
+		/*
 		 * Invalidate the parent's relcache so that the partition is no longer
 		 * included in its partition descriptor.
 		 */
@@ -3138,6 +3170,7 @@ StorePartitionKey(Relation rel,
 	values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
 	values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
 	values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
+	values[Anum_pg_partitioned_table_partdefid - 1] = ObjectIdGetDatum(InvalidOid);
 	values[Anum_pg_partitioned_table_partattrs - 1] = PointerGetDatum(partattrs_vec);
 	values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
 	values[Anum_pg_partitioned_table_partcollation - 1] = PointerGetDatum(partcollation_vec);
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 11189e28ec..1eafb891d3 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -27,6 +27,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_inherits_fn.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_type.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
@@ -80,9 +81,12 @@ typedef struct PartitionBoundInfoData
 								 * partitioned table) */
 	int			null_index;		/* Index of the null-accepting partition; -1
 								 * if there isn't one */
+	int			default_index;	/* Index of the default partition; -1 if there
+								 * isn't one */
 } 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
@@ -120,7 +124,7 @@ static void get_range_key_properties(PartitionKey key, int keynum,
 						 ListCell **partexprs_item,
 						 Expr **keyCol,
 						 Const **lower_val, Const **upper_val);
-static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_list(Relation parent, PartitionBoundSpec *spec);
 static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
 static List *generate_partition_qual(Relation rel);
 
@@ -166,6 +170,7 @@ RelationBuildPartitionDesc(Relation rel)
 	/* List partitioning specific */
 	PartitionListValue **all_values = NULL;
 	int			null_index = -1;
+	int			default_index = -1;
 
 	/* Range partitioning specific */
 	PartitionRangeBound **rbounds = NULL;
@@ -213,6 +218,19 @@ RelationBuildPartitionDesc(Relation rel)
 								&isnull);
 		Assert(!isnull);
 		boundspec = (Node *) stringToNode(TextDatumGetCString(datum));
+
+		/*
+		 * If this is a default partition, pg_partitioned_table must have it's
+		 * OID as value of 'partdefid' for it's parent (i.e. rel) entry.
+		 */
+		if (castNode(PartitionBoundSpec, boundspec)->is_default)
+		{
+			Oid			partdefid;
+
+			partdefid = get_default_partition_oid(RelationGetRelid(rel));
+			Assert(partdefid == inhrelid);
+		}
+
 		boundspecs = lappend(boundspecs, boundspec);
 		partoids = lappend_oid(partoids, inhrelid);
 		ReleaseSysCache(tuple);
@@ -246,6 +264,18 @@ RelationBuildPartitionDesc(Relation rel)
 				if (spec->strategy != PARTITION_STRATEGY_LIST)
 					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 list of non-null
+				 * datums for this partition.
+				 */
+				if (spec->is_default)
+				{
+					default_index = i;
+					i++;
+					continue;
+				}
+
 				foreach(c, spec->listdatums)
 				{
 					Const	   *val = castNode(Const, lfirst(c));
@@ -441,6 +471,7 @@ RelationBuildPartitionDesc(Relation rel)
 		boundinfo = (PartitionBoundInfoData *)
 			palloc0(sizeof(PartitionBoundInfoData));
 		boundinfo->strategy = key->strategy;
+		boundinfo->default_index = -1;
 		boundinfo->ndatums = ndatums;
 		boundinfo->null_index = -1;
 		boundinfo->datums = (Datum **) palloc0(ndatums * sizeof(Datum *));
@@ -493,6 +524,21 @@ RelationBuildPartitionDesc(Relation rel)
 						boundinfo->null_index = mapping[null_index];
 					}
 
+					/* Assign mapped index for the default partition. */
+					if (default_index != -1)
+					{
+						/*
+						 * The default partition accepts any value not
+						 * specified in the lists of other partitions, hence
+						 * it should not get mapped index while assigning
+						 * those for non-null datums.
+						 */
+						Assert(default_index >= 0 &&
+							   mapping[default_index] == -1);
+						mapping[default_index] = next_index++;
+						boundinfo->default_index = mapping[default_index];
+					}
+
 					/* All partition must now have a valid mapping */
 					Assert(next_index == nparts);
 					break;
@@ -597,6 +643,9 @@ partition_bounds_equal(int partnatts, int16 *parttyplen, bool *parttypbyval,
 	if (b1->null_index != b2->null_index)
 		return false;
 
+	if (b1->default_index != b2->default_index)
+		return false;
+
 	for (i = 0; i < b1->ndatums; i++)
 	{
 		int			j;
@@ -880,7 +929,7 @@ get_qual_from_partbound(Relation rel, Relation parent,
 	{
 		case PARTITION_STRATEGY_LIST:
 			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
-			my_qual = get_qual_for_list(key, spec);
+			my_qual = get_qual_for_list(parent, spec);
 			break;
 
 		case PARTITION_STRATEGY_RANGE:
@@ -1287,10 +1336,14 @@ make_partition_op_expr(PartitionKey key, int keynum,
  *
  * Returns an implicit-AND list of expressions to use as a list partition's
  * constraint, given the partition key and bound structures.
+ *
+ * The function returns NIL for a default partition when it's the only
+ * partition since it can not have any partition constraint.
  */
 static List *
-get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
+get_qual_for_list(Relation parent, PartitionBoundSpec *spec)
 {
+	PartitionKey key = RelationGetPartitionKey(parent);
 	List	   *result;
 	Expr	   *keyCol;
 	ArrayExpr  *arr;
@@ -1317,15 +1370,63 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 	else
 		keyCol = (Expr *) copyObject(linitial(key->partexprs));
 
-	/* Create list of Consts for the allowed values, excluding any nulls */
-	foreach(cell, spec->listdatums)
+	/*
+	 * For default list partition, collect datums for all the partitions. The
+	 * default partition constraint should check that the partition key is
+	 * equal to none of those.
+	 */
+	if (spec->is_default)
 	{
-		Const	   *val = castNode(Const, lfirst(cell));
+		int			i;
+		int			ndatums = 0;
+		PartitionDesc pdesc = RelationGetPartitionDesc(parent);
+		PartitionBoundInfo boundinfo = pdesc->boundinfo;
 
-		if (val->constisnull)
-			list_has_null = true;
-		else
-			arrelems = lappend(arrelems, copyObject(val));
+		if (boundinfo)
+		{
+			ndatums = boundinfo->ndatums;
+
+			if (partition_bound_accepts_nulls(boundinfo))
+				list_has_null = true;
+		}
+
+		/*
+		 * If default is the only partition, there need not be any partition
+		 * constraint on it.
+		 */
+		if (ndatums == 0 && !list_has_null)
+			return NIL;
+
+		for (i = 0; i < ndatums; i++)
+		{
+			Const	   *val;
+
+			/* Construct const from datum */
+			val = makeConst(key->parttypid[0],
+							key->parttypmod[0],
+							key->parttypcoll[0],
+							key->parttyplen[0],
+							*boundinfo->datums[i],
+							false,	/* isnull */
+							key->parttypbyval[0]);
+
+			arrelems = lappend(arrelems, val);
+		}
+	}
+	else
+	{
+		/*
+		 * Create list of Consts for the allowed values, excluding any nulls.
+		 */
+		foreach(cell, spec->listdatums)
+		{
+			Const	   *val = castNode(Const, lfirst(cell));
+
+			if (val->constisnull)
+				list_has_null = true;
+			else
+				arrelems = lappend(arrelems, copyObject(val));
+		}
 	}
 
 	if (arrelems)
@@ -1389,6 +1490,25 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 			result = list_make1(nulltest);
 	}
 
+	/*
+	 * In case of the default partition, the constraint is of the form
+	 * "!(result)" i.e. one of the following two forms:
+	 *
+	 * 1. NOT ((keycol IS NULL) OR (keycol = ANY (arr)))
+	 *
+	 * 2. NOT ((keycol IS NOT NULL) AND (keycol = ANY (arr)))
+	 *
+	 * Note that, in general, applying NOT to a constraint expression doesn't
+	 * necessarily invert the set of rows it accepts, because NOT (NULL) is
+	 * NULL.  However, the partition constraints we construct here never
+	 * evaluate to NULL, so applying NOT works as intended.
+	 */
+	if (spec->is_default)
+	{
+		result = list_make1(make_ands_explicit(result));
+		result = list_make1(makeBoolExpr(NOT_EXPR, result, -1));
+	}
+
 	return result;
 }
 
@@ -2009,8 +2129,9 @@ get_partition_for_tuple(PartitionDispatch *pd,
 		}
 
 		/*
-		 * A null partition key is only acceptable if null-accepting list
-		 * partition exists.
+		 * If this is a null partition key, route it to the null-accepting
+		 * partition. Otherwise, route by searching the array of partition
+		 * bounds.
 		 */
 		cur_index = -1;
 		if (isnull[0] && partition_bound_accepts_nulls(partdesc->boundinfo))
@@ -2048,11 +2169,17 @@ get_partition_for_tuple(PartitionDispatch *pd,
 		}
 
 		/*
-		 * cur_index < 0 means we failed to find a partition of this parent.
-		 * cur_index >= 0 means we either found the leaf partition, or the
-		 * next parent to find a partition of.
+		 * cur_index < 0 means we could not find a non-default partition of
+		 * this parent. cur_index >= 0 means we either found the leaf
+		 * partition, or the next parent to find a partition of.
+		 *
+		 * If we couldn't find a non-default partition check if the default
+		 * partition exists, if it does, get its index.
 		 */
 		if (cur_index < 0)
+			cur_index = partdesc->boundinfo->default_index;
+
+		if (cur_index < 0)
 		{
 			result = -1;
 			*failed_at = parent;
@@ -2340,3 +2467,58 @@ partition_bound_bsearch(PartitionKey key, PartitionBoundInfo boundinfo,
 
 	return lo;
 }
+
+/*
+ * get_default_partition_oid
+ *
+ * If the given relation has a default partition return the OID of the default
+ * partition, otherwise return InvalidOid.
+ */
+Oid
+get_default_partition_oid(Oid parentId)
+{
+	HeapTuple	tuple;
+	Oid			defaultPartId = InvalidOid;
+
+	tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(parentId));
+
+	if (HeapTupleIsValid(tuple))
+	{
+		Form_pg_partitioned_table part_table_form;
+
+		part_table_form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+		defaultPartId = part_table_form->partdefid;
+	}
+
+	ReleaseSysCache(tuple);
+	return defaultPartId;
+}
+
+/*
+ * update_default_partition_oid
+ *
+ * Updates the pg_partition_table catalog partdefid field for the given parent
+ * with the given default partition oid.
+ */
+void
+update_default_partition_oid(Oid parentId, Oid defaultPartId)
+{
+	HeapTuple	tuple;
+	Relation	pg_partitioned_table;
+	Form_pg_partitioned_table part_table_form;
+
+	pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCacheCopy1(PARTRELID, ObjectIdGetDatum(parentId));
+
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of relation %u",
+			 parentId);
+
+	part_table_form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+	part_table_form->partdefid = defaultPartId;
+	CatalogTupleUpdate(pg_partitioned_table, &tuple->t_self, tuple);
+
+	heap_freetuple(tuple);
+	heap_close(pg_partitioned_table, RowExclusiveLock);
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 0fb8238795..9aad58fee9 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -774,7 +774,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	{
 		PartitionBoundSpec *bound;
 		ParseState *pstate;
-		Oid			parentId = linitial_oid(inheritOids);
+		Oid			parentId = linitial_oid(inheritOids),
+					defaultPartOid;
 		Relation	parent;
 
 		/* Already have strong enough lock on the parent */
@@ -790,6 +791,17 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 					 errmsg("\"%s\" is not partitioned",
 							RelationGetRelationName(parent))));
 
+		/*
+		 * A table cannot be created as a partition of a parent already having
+		 * a default partition.
+		 */
+		defaultPartOid = get_default_partition_oid(RelationGetRelid(parent));
+		if (OidIsValid(defaultPartOid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("cannot add a new partition to table \"%s\" having a default partition",
+							RelationGetRelationName(parent))));
+
 		/* Tranform the bound values */
 		pstate = make_parsestate(NULL);
 		pstate->p_sourcetext = queryString;
@@ -806,6 +818,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		/* Update the pg_class entry. */
 		StorePartitionBound(rel, parent, bound);
 
+		/* Update the default partition oid */
+		if (bound->is_default)
+			update_default_partition_oid(RelationGetRelid(parent), relationId);
+
 		heap_close(parent, NoLock);
 
 		/*
@@ -13627,6 +13643,15 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	ObjectAddress address;
 	const char *trigger_name;
 	bool		found_whole_row;
+	Oid			defaultPartOid;
+
+	/* A partition cannot be attached if there exists a default partition */
+	defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
+	if (OidIsValid(defaultPartOid))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot attach a new partition to table \"%s\" having a default partition",
+						RelationGetRelationName(rel))));
 
 	attachrel = heap_openrv(cmd->name, AccessExclusiveLock);
 
@@ -13783,6 +13808,11 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	/* OK to create inheritance.  Rest of the checks performed there */
 	CreateInheritance(attachrel, rel);
 
+	/* Update the default partition oid */
+	if (cmd->bound->is_default)
+		update_default_partition_oid(RelationGetRelid(rel),
+									 RelationGetRelid(attachrel));
+
 	/*
 	 * Check that the new partition's bound is valid and does not overlap any
 	 * of existing partitions of the parent - note that it does not return on
@@ -13851,6 +13881,15 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 				new_null[Natts_pg_class],
 				new_repl[Natts_pg_class];
 	ObjectAddress address;
+	Oid			defaultPartOid;
+
+	/*
+	 * We must lock the default partition, for the same reasons explained in
+	 * heap_drop_with_catalog().
+	 */
+	defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
+	if (OidIsValid(defaultPartOid))
+		LockRelationOid(defaultPartOid, AccessExclusiveLock);
 
 	partRel = heap_openrv(name, AccessShareLock);
 
@@ -13882,6 +13921,22 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 	heap_freetuple(newtuple);
 	heap_close(classRel, RowExclusiveLock);
 
+	if (OidIsValid(defaultPartOid))
+	{
+		/*
+		 * If the detach relation is the default partition itself, invalidate
+		 * its entry in pg_partitioned_table.
+		 */
+		if (RelationGetRelid(partRel) == defaultPartOid)
+			update_default_partition_oid(RelationGetRelid(rel), InvalidOid);
+
+		/*
+		 * We must invalidate default partition's relcache, for the same
+		 * reasons explained in heap_drop_with_catalog().
+		 */
+		CacheInvalidateRelcacheByRelid(defaultPartOid);
+	}
+
 	/*
 	 * Invalidate the parent's relcache so that the partition is no longer
 	 * included in its partition descriptor.
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 72041693df..ad15e46d14 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4447,6 +4447,7 @@ _copyPartitionBoundSpec(const PartitionBoundSpec *from)
 	PartitionBoundSpec *newnode = makeNode(PartitionBoundSpec);
 
 	COPY_SCALAR_FIELD(strategy);
+	COPY_SCALAR_FIELD(is_default);
 	COPY_NODE_FIELD(listdatums);
 	COPY_NODE_FIELD(lowerdatums);
 	COPY_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8d92c03633..64ecfff912 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2838,6 +2838,7 @@ static bool
 _equalPartitionBoundSpec(const PartitionBoundSpec *a, const PartitionBoundSpec *b)
 {
 	COMPARE_SCALAR_FIELD(strategy);
+	COMPARE_SCALAR_FIELD(is_default);
 	COMPARE_NODE_FIELD(listdatums);
 	COMPARE_NODE_FIELD(lowerdatums);
 	COMPARE_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 5ce3c7c599..a20cbc5ea8 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3571,6 +3571,7 @@ _outPartitionBoundSpec(StringInfo str, const PartitionBoundSpec *node)
 	WRITE_NODE_TYPE("PARTITIONBOUNDSPEC");
 
 	WRITE_CHAR_FIELD(strategy);
+	WRITE_BOOL_FIELD(is_default);
 	WRITE_NODE_FIELD(listdatums);
 	WRITE_NODE_FIELD(lowerdatums);
 	WRITE_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 86c811de49..2840ca7828 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2388,6 +2388,7 @@ _readPartitionBoundSpec(void)
 	READ_LOCALS(PartitionBoundSpec);
 
 	READ_CHAR_FIELD(strategy);
+	READ_BOOL_FIELD(is_default);
 	READ_NODE_FIELD(listdatums);
 	READ_NODE_FIELD(lowerdatums);
 	READ_NODE_FIELD(upperdatums);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7d0de99baf..1db35e25b5 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -575,7 +575,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>			part_strategy
 %type <partelem>	part_elem
 %type <list>		part_params
-%type <partboundspec> ForValues
+%type <partboundspec> PartitionBoundSpec
 %type <node>		partbound_datum PartitionRangeDatum
 %type <list>		partbound_datum_list range_datum_list
 
@@ -1980,7 +1980,7 @@ alter_table_cmds:
 
 partition_cmd:
 			/* ALTER TABLE <name> ATTACH PARTITION <table_name> FOR VALUES */
-			ATTACH PARTITION qualified_name ForValues
+			ATTACH PARTITION qualified_name PartitionBoundSpec
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
 					PartitionCmd *cmd = makeNode(PartitionCmd);
@@ -2619,13 +2619,14 @@ alter_identity_column_option:
 				}
 		;
 
-ForValues:
+PartitionBoundSpec:
 			/* a LIST partition */
 			FOR VALUES IN_P '(' partbound_datum_list ')'
 				{
 					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
 
 					n->strategy = PARTITION_STRATEGY_LIST;
+					n->is_default = false;
 					n->listdatums = $5;
 					n->location = @3;
 
@@ -2638,12 +2639,24 @@ ForValues:
 					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
 
 					n->strategy = PARTITION_STRATEGY_RANGE;
+					n->is_default = false;
 					n->lowerdatums = $5;
 					n->upperdatums = $9;
 					n->location = @3;
 
 					$$ = n;
 				}
+
+			/* a DEFAULT partition */
+			| DEFAULT
+				{
+					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+					n->is_default = true;
+					n->location = @1;
+
+					$$ = n;
+				}
 		;
 
 partbound_datum:
@@ -3114,7 +3127,7 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
-			OptTypedTableElementList ForValues OptPartitionSpec OptWith
+			OptTypedTableElementList PartitionBoundSpec OptPartitionSpec OptWith
 			OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
@@ -3133,7 +3146,7 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
-			qualified_name OptTypedTableElementList ForValues OptPartitionSpec
+			qualified_name OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
 			OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
@@ -4848,7 +4861,7 @@ CreateForeignTableStmt:
 					$$ = (Node *) n;
 				}
 		| CREATE FOREIGN TABLE qualified_name
-			PARTITION OF qualified_name OptTypedTableElementList ForValues
+			PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
 			SERVER name create_generic_options
 				{
 					CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
@@ -4869,7 +4882,7 @@ CreateForeignTableStmt:
 					$$ = (Node *) n;
 				}
 		| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
-			PARTITION OF qualified_name OptTypedTableElementList ForValues
+			PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
 			SERVER name create_generic_options
 				{
 					CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 20586797cc..22e7aa6e13 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -60,6 +61,7 @@
 #include "parser/parse_utilcmd.h"
 #include "parser/parser.h"
 #include "rewrite/rewriteManip.h"
+#include "storage/lmgr.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
@@ -3307,6 +3309,23 @@ transformPartitionBound(ParseState *pstate, Relation parent,
 	/* Avoid scribbling on input */
 	result_spec = copyObject(spec);
 
+	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
+		 * partition bound spec.
+		 */
+		result_spec->strategy = strategy;
+
+		return result_spec;
+	}
+
 	if (strategy == PARTITION_STRATEGY_LIST)
 	{
 		ListCell   *cell;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 43646d2c4f..65df1a8717 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8696,10 +8696,18 @@ get_rule_expr(Node *node, deparse_context *context,
 		case T_PartitionBoundSpec:
 			{
 				PartitionBoundSpec *spec = (PartitionBoundSpec *) node;
+				char		strategy = spec->strategy;
 				ListCell   *cell;
 				char	   *sep;
 
-				switch (spec->strategy)
+				if (spec->is_default)
+				{
+					Assert(strategy == PARTITION_STRATEGY_LIST);
+					appendStringInfoString(buf, "DEFAULT");
+					break;
+				}
+
+				switch (strategy)
 				{
 					case PARTITION_STRATEGY_LIST:
 						Assert(spec->listdatums != NIL);
@@ -8731,7 +8739,7 @@ get_rule_expr(Node *node, deparse_context *context,
 
 					default:
 						elog(ERROR, "unrecognized partition strategy: %d",
-							 (int) spec->strategy);
+							 (int) strategy);
 						break;
 				}
 			}
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index f6049cc9e5..4d42f8d87f 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1900,8 +1900,12 @@ describeOneTableDetails(const char *schemaname,
 
 			if (partconstraintdef)
 			{
-				printfPQExpBuffer(&tmpbuf, _("Partition constraint: %s"),
-								  partconstraintdef);
+				/* If there isn't any constraint, show that explicitly */
+				if (partconstraintdef[0] == '\0')
+					printfPQExpBuffer(&tmpbuf, _("No partition constraint"));
+				else
+					printfPQExpBuffer(&tmpbuf, _("Partition constraint: %s"),
+									  partconstraintdef);
 				printTableAddFooter(&cont, tmpbuf.data);
 			}
 
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 1583cfa998..fb1091a78e 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2050,7 +2050,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("ATTACH", "PARTITION", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 	else if (TailMatches2("FOR", "VALUES"))
 		COMPLETE_WITH_LIST2("FROM (", "IN (");
 
@@ -2489,7 +2489,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("PARTITION", "OF", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 
 /* CREATE TABLESPACE */
 	else if (Matches3("CREATE", "TABLESPACE", MatchAny))
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 2283c675e9..1dd70f2c6d 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -99,4 +99,7 @@ extern int get_partition_for_tuple(PartitionDispatch *pd,
 						EState *estate,
 						PartitionDispatchData **failed_at,
 						TupleTableSlot **failed_slot);
+extern Oid	get_default_partition_oid(Oid parentId);
+extern void update_default_partition_oid(Oid parentId, Oid defaultPartId);
+
 #endif							/* PARTITION_H */
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
index 38d64d6511..525e541f93 100644
--- a/src/include/catalog/pg_partitioned_table.h
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -32,6 +32,8 @@ CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
 	Oid			partrelid;		/* partitioned table oid */
 	char		partstrat;		/* partitioning strategy */
 	int16		partnatts;		/* number of partition key columns */
+	Oid			partdefid;		/* default partition oid; InvalidOid if there
+								 * isn't one */
 
 	/*
 	 * variable-length fields start here, but we allow direct access to
@@ -62,13 +64,14 @@ typedef FormData_pg_partitioned_table *Form_pg_partitioned_table;
  *		compiler constants for pg_partitioned_table
  * ----------------
  */
-#define Natts_pg_partitioned_table				7
+#define Natts_pg_partitioned_table				8
 #define Anum_pg_partitioned_table_partrelid		1
 #define Anum_pg_partitioned_table_partstrat		2
 #define Anum_pg_partitioned_table_partnatts		3
-#define Anum_pg_partitioned_table_partattrs		4
-#define Anum_pg_partitioned_table_partclass		5
-#define Anum_pg_partitioned_table_partcollation 6
-#define Anum_pg_partitioned_table_partexprs		7
+#define Anum_pg_partitioned_table_partdefid		4
+#define Anum_pg_partitioned_table_partattrs		5
+#define Anum_pg_partitioned_table_partclass		6
+#define Anum_pg_partitioned_table_partcollation 7
+#define Anum_pg_partitioned_table_partexprs		8
 
 #endif							/* PG_PARTITIONED_TABLE_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 5f2a4a75da..6e05b7908e 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -797,6 +797,7 @@ typedef struct PartitionBoundSpec
 	NodeTag		type;
 
 	char		strategy;		/* see PARTITION_STRATEGY codes above */
+	bool		is_default;		/* is it a default partition bound? */
 
 	/* Partitioning info for LIST strategy: */
 	List	   *listdatums;		/* List of Consts (or A_Consts in raw tree) */
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 58192d2c6a..c1e090aef4 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3273,6 +3273,14 @@ SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::reg
 CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
 ERROR:  partition "fail_part" would overlap partition "part_1"
+-- check that an existing table can be attached as a default partition
+CREATE TABLE def_part (LIKE list_parted INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION def_part DEFAULT;
+-- check attaching default partition fails if a default partition already
+-- exists
+CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT;
+ERROR:  cannot attach a new partition to table "list_parted" having a default partition
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
 	a int,
@@ -3286,6 +3294,15 @@ ERROR:  partition constraint is violated by some row
 -- should be ok after deleting the bad row
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+-- check partition cannot be attached if default exists
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
+INSERT INTO list_parted2_def VALUES (11, 'z');
+CREATE TABLE part_3 (LIKE list_parted2);
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+ERROR:  cannot attach a new partition to table "list_parted2" having a default partition
+-- should be ok after detaching a default partition
+ALTER TABLE list_parted2 DETACH PARTITION list_parted2_def;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
 CREATE TABLE part_3_4 (
@@ -3514,6 +3531,7 @@ ALTER TABLE list_parted2 ALTER COLUMN b TYPE text;
 ERROR:  cannot alter type of column named in partition key
 -- cleanup
 DROP TABLE list_parted, list_parted2, range_parted;
+DROP TABLE fail_def_part;
 -- more tests for certain multi-level partitioning scenarios
 create table p (a int, b int) partition by range (a, b);
 create table p1 (b int, a int not null) partition by range (b);
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index babda8978c..a6a7bf485b 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -467,6 +467,13 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
 ERROR:  invalid bound specification for a list partition
 LINE 1: ...BLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) T...
                                                              ^
+-- check default partition cannot be created more than once
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
+ERROR:  cannot add a new partition to table "list_parted" having a default partition
+-- check that no partition can be created after default partition
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (3);
+ERROR:  cannot add a new partition to table "list_parted" having a default partition
 -- specified literal can't be cast to the partition column data type
 CREATE TABLE bools (
 	a bool
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index a2d9469592..17f0210726 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -219,17 +219,66 @@ insert into part_null values (null, 0);
 create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b);
 create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
 create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
+-- test default partition
+create table part_default partition of list_parted default;
+-- Negative test: a row, which would fit in other partition, does not fit
+-- default partition, even when inserted directly
+insert into part_default values ('aa', 2);
+ERROR:  new row for relation "part_default" violates partition constraint
+DETAIL:  Failing row contains (aa, 2).
+insert into part_default values (null, 2);
+ERROR:  new row for relation "part_default" violates partition constraint
+DETAIL:  Failing row contains (null, 2).
+-- ok
+insert into part_default values ('Zz', 2);
+-- test if default partition works as expected for multi-level partitioned
+-- table as well as when default partition itself is further partitioned
+drop table part_default;
+create table part_xx_yy partition of list_parted for values in ('xx', 'yy') partition by list (a);
+create table part_xx_yy_p1 partition of part_xx_yy for values in ('xx');
+create table part_xx_yy_defpart partition of part_xx_yy default;
+create table part_default partition of list_parted default partition by range(b);
+create table part_default_p1 partition of part_default for values from (20) to (30);
+create table part_default_p2 partition of part_default for values from (30) to (40);
 -- fail
 insert into part_ee_ff1 values ('EE', 11);
 ERROR:  new row for relation "part_ee_ff1" violates partition constraint
 DETAIL:  Failing row contains (EE, 11).
+insert into part_default_p2 values ('gg', 43);
+ERROR:  new row for relation "part_default_p2" violates partition constraint
+DETAIL:  Failing row contains (gg, 43).
 -- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
 insert into part_ee_ff1 values ('cc', 1);
 ERROR:  new row for relation "part_ee_ff1" violates partition constraint
 DETAIL:  Failing row contains (cc, 1).
+insert into part_default values ('gg', 43);
+ERROR:  no partition of relation "part_default" found for row
+DETAIL:  Partition key of the failing row contains (b) = (43).
 -- ok
 insert into part_ee_ff1 values ('ff', 1);
 insert into part_ee_ff2 values ('ff', 11);
+insert into part_default_p1 values ('cd', 25);
+insert into part_default_p2 values ('de', 35);
+insert into list_parted values ('ab', 21);
+insert into list_parted values ('xx', 1);
+insert into list_parted values ('yy', 2);
+select tableoid::regclass, * from list_parted;
+      tableoid      | a  | b  
+--------------------+----+----
+ part_cc_dd         | cC |  1
+ part_null          |    |  0
+ part_ee_ff1        | ff |  1
+ part_ee_ff2        | ff | 11
+ part_xx_yy_p1      | xx |  1
+ part_xx_yy_defpart | yy |  2
+ part_default_p1    | cd | 25
+ part_default_p1    | ab | 21
+ part_default_p2    | de | 35
+(9 rows)
+
+-- drop default partition, as we need to add some more partitions to test tuple
+-- routing
+drop table part_default;
 -- Check tuple routing for partitioned tables
 -- fail
 insert into range_parted values ('a', 0);
@@ -274,17 +323,19 @@ DETAIL:  Partition key of the failing row contains (b) = (0).
 insert into list_parted values ('EE', 1);
 insert into part_ee_ff values ('EE', 10);
 select tableoid::regclass, * from list_parted;
-  tableoid   | a  | b  
--------------+----+----
- part_aa_bb  | aA |   
- part_cc_dd  | cC |  1
- part_null   |    |  0
- part_null   |    |  1
- part_ee_ff1 | ff |  1
- part_ee_ff1 | EE |  1
- part_ee_ff2 | ff | 11
- part_ee_ff2 | EE | 10
-(8 rows)
+      tableoid      | a  | b  
+--------------------+----+----
+ part_aa_bb         | aA |   
+ part_cc_dd         | cC |  1
+ part_null          |    |  0
+ part_null          |    |  1
+ part_ee_ff1        | ff |  1
+ part_ee_ff1        | EE |  1
+ part_ee_ff2        | ff | 11
+ part_ee_ff2        | EE | 10
+ part_xx_yy_p1      | xx |  1
+ part_xx_yy_defpart | yy |  2
+(10 rows)
 
 -- some more tests to exercise tuple-routing with multi-level partitioning
 create table part_gg partition of list_parted for values in ('gg') partition by range (b);
@@ -316,6 +367,23 @@ select tableoid::regclass::text, a, min(b) as min_b, max(b) as max_b from list_p
 
 -- cleanup
 drop table range_parted, list_parted;
+-- test adding default partition as first partition accepts any value including
+-- null
+create table list_parted (a int) partition by list (a);
+create table part_default partition of list_parted default;
+insert into part_default values (null);
+insert into part_default values (1);
+insert into part_default values (-1);
+select tableoid::regclass, a from list_parted;
+   tableoid   | a  
+--------------+----
+ part_default |   
+ part_default |  1
+ part_default | -1
+(3 rows)
+
+-- cleanup
+drop table list_parted;
 -- more tests for certain multi-level partitioning scenarios
 create table mlparted (a int, b int) partition by range (a, b);
 create table mlparted1 (b int not null, a int not null) partition by range ((b+0));
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
index 3f3db337c5..fb903574c5 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -252,3 +252,25 @@ NOTICE:  3
  
 (1 row)
 
+-- Check that addition or removal of any partition is correctly dealt with by
+-- default partition table when it is being used in prepared statement.
+create table list_parted (a int) partition by list(a);
+create table list_part_null partition of list_parted for values in (null);
+create table list_part_1 partition of list_parted for values in (1);
+create table list_part_def partition of list_parted default;
+prepare pstmt_def_insert (int) as insert into list_part_def values($1);
+-- should fail
+execute pstmt_def_insert(null);
+ERROR:  new row for relation "list_part_def" violates partition constraint
+DETAIL:  Failing row contains (null).
+execute pstmt_def_insert(1);
+ERROR:  new row for relation "list_part_def" violates partition constraint
+DETAIL:  Failing row contains (1).
+alter table list_parted detach partition list_part_null;
+-- should be ok
+execute pstmt_def_insert(null);
+drop table list_part_1;
+-- should be ok
+execute pstmt_def_insert(1);
+drop table list_parted, list_part_null;
+deallocate pstmt_def_insert;
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index 9366f04255..9912ef29a1 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -218,5 +218,20 @@ 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;
+create table list_parted (
+	a text,
+	b int
+) partition by list (a);
+create table list_part1  partition of list_parted for values in ('a', 'b');
+create table list_default partition of list_parted default;
+insert into list_part1 values ('a', 1);
+insert into list_default values ('d', 10);
+-- fail
+update list_default set a = 'a' where a = 'd';
+ERROR:  new row for relation "list_default" violates partition constraint
+DETAIL:  Failing row contains (a, 10).
+-- ok
+update list_default set a = 'x' where a = 'd';
 -- cleanup
 drop table range_parted;
+drop table list_parted;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 9a20dd141a..47641a458d 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2095,6 +2095,13 @@ SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::reg
 -- check that the new partition won't overlap with an existing partition
 CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+-- check that an existing table can be attached as a default partition
+CREATE TABLE def_part (LIKE list_parted INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION def_part DEFAULT;
+-- check attaching default partition fails if a default partition already
+-- exists
+CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT;
 
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
@@ -2111,6 +2118,15 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 
+-- check partition cannot be attached if default exists
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
+INSERT INTO list_parted2_def VALUES (11, 'z');
+CREATE TABLE part_3 (LIKE list_parted2);
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+-- should be ok after detaching a default partition
+ALTER TABLE list_parted2 DETACH PARTITION list_parted2_def;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
 CREATE TABLE part_3_4 (
@@ -2311,6 +2327,7 @@ ALTER TABLE list_parted2 ALTER COLUMN b TYPE text;
 
 -- cleanup
 DROP TABLE list_parted, list_parted2, range_parted;
+DROP TABLE fail_def_part;
 
 -- more tests for certain multi-level partitioning scenarios
 create table p (a int, b int) partition by range (a, b);
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 1c0ce92763..8d0c252ff4 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -447,6 +447,12 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
 -- trying to specify range for list partitioned table
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
 
+-- check default partition cannot be created more than once
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
+-- check that no partition can be created after default partition
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (3);
+
 -- specified literal can't be cast to the partition column data type
 CREATE TABLE bools (
 	a bool
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 6f17872087..a44d454379 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -132,13 +132,42 @@ create table part_ee_ff partition of list_parted for values in ('ee', 'ff') part
 create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
 create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
 
+-- test default partition
+create table part_default partition of list_parted default;
+-- Negative test: a row, which would fit in other partition, does not fit
+-- default partition, even when inserted directly
+insert into part_default values ('aa', 2);
+insert into part_default values (null, 2);
+-- ok
+insert into part_default values ('Zz', 2);
+-- test if default partition works as expected for multi-level partitioned
+-- table as well as when default partition itself is further partitioned
+drop table part_default;
+create table part_xx_yy partition of list_parted for values in ('xx', 'yy') partition by list (a);
+create table part_xx_yy_p1 partition of part_xx_yy for values in ('xx');
+create table part_xx_yy_defpart partition of part_xx_yy default;
+create table part_default partition of list_parted default partition by range(b);
+create table part_default_p1 partition of part_default for values from (20) to (30);
+create table part_default_p2 partition of part_default for values from (30) to (40);
+
 -- fail
 insert into part_ee_ff1 values ('EE', 11);
+insert into part_default_p2 values ('gg', 43);
 -- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
 insert into part_ee_ff1 values ('cc', 1);
+insert into part_default values ('gg', 43);
 -- ok
 insert into part_ee_ff1 values ('ff', 1);
 insert into part_ee_ff2 values ('ff', 11);
+insert into part_default_p1 values ('cd', 25);
+insert into part_default_p2 values ('de', 35);
+insert into list_parted values ('ab', 21);
+insert into list_parted values ('xx', 1);
+insert into list_parted values ('yy', 2);
+select tableoid::regclass, * from list_parted;
+-- drop default partition, as we need to add some more partitions to test tuple
+-- routing
+drop table part_default;
 
 -- Check tuple routing for partitioned tables
 
@@ -188,6 +217,17 @@ select tableoid::regclass::text, a, min(b) as min_b, max(b) as max_b from list_p
 -- cleanup
 drop table range_parted, list_parted;
 
+-- test adding default partition as first partition accepts any value including
+-- null
+create table list_parted (a int) partition by list (a);
+create table part_default partition of list_parted default;
+insert into part_default values (null);
+insert into part_default values (1);
+insert into part_default values (-1);
+select tableoid::regclass, a from list_parted;
+-- cleanup
+drop table list_parted;
+
 -- more tests for certain multi-level partitioning scenarios
 create table mlparted (a int, b int) partition by range (a, b);
 create table mlparted1 (b int not null, a int not null) partition by range ((b+0));
diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql
index bc2086166b..a8e357c34c 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -156,3 +156,22 @@ end$$ language plpgsql;
 
 select cachebug();
 select cachebug();
+
+-- Check that addition or removal of any partition is correctly dealt with by
+-- default partition table when it is being used in prepared statement.
+create table list_parted (a int) partition by list(a);
+create table list_part_null partition of list_parted for values in (null);
+create table list_part_1 partition of list_parted for values in (1);
+create table list_part_def partition of list_parted default;
+prepare pstmt_def_insert (int) as insert into list_part_def values($1);
+-- should fail
+execute pstmt_def_insert(null);
+execute pstmt_def_insert(1);
+alter table list_parted detach partition list_part_null;
+-- should be ok
+execute pstmt_def_insert(null);
+drop table list_part_1;
+-- should be ok
+execute pstmt_def_insert(1);
+drop table list_parted, list_part_null;
+deallocate pstmt_def_insert;
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 663711997b..44fb0dc40a 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -125,5 +125,20 @@ update range_parted set b = b - 1 where b = 10;
 -- ok
 update range_parted set b = b + 1 where b = 10;
 
+create table list_parted (
+	a text,
+	b int
+) partition by list (a);
+create table list_part1  partition of list_parted for values in ('a', 'b');
+create table list_default partition of list_parted default;
+insert into list_part1 values ('a', 1);
+insert into list_default values ('d', 10);
+
+-- fail
+update list_default set a = 'a' where a = 'd';
+-- ok
+update list_default set a = 'x' where a = 'd';
+
 -- cleanup
 drop table range_parted;
+drop table list_parted;
-- 
2.13.3

0003-Default-partition-extended-for-create-and-attach.patch0000644000076500000240000010766713146531753023063 0ustar  jeevanstaffFrom 5b1a8434048e576d82fe526ff2ce4f75c9b30a3d Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Mon, 21 Aug 2017 15:35:15 +0530
Subject: [PATCH 3/5] Default partition extended for create and attach

1. This patch extends the previous patch in this series to allow a
new partition to be created or attached even when a default
partition on a list partition exists.
2. Also, extends the regression tests to test this functionality.
3. This patch uses the refactored functions created in patch 0001
in this series.
4. While adding a new partition we need to make sure that default
partition does not contain any rows that would fit in newly added
partition. So, in this patch adds a code to negate the partition
constraints of new partition so as these constraints form the part
of would be default partition constraints. Then the default
partition is scanned to check if there exist a row that does not
hold these negated partition constraints, if there exists such a
row error out.
5. While doing 4, to optimize the validation scan a check is done,
if the existing constraints on the default partition imply that it
will not contain any row that would belong to the new partition,
then the validation scan is skipped.

Jeevan Ladhe, some refactoring and code-reuse of patch 0001 by
Ashutosh bapat.
---
 src/backend/catalog/heap.c                 |  33 +++---
 src/backend/catalog/partition.c            | 183 ++++++++++++++++++++++++++++-
 src/backend/commands/tablecmds.c           | 112 ++++++++++++++----
 src/include/catalog/partition.h            |   3 +
 src/include/commands/tablecmds.h           |   4 +
 src/test/regress/expected/alter_table.out  |  28 ++++-
 src/test/regress/expected/create_table.out |  10 +-
 src/test/regress/expected/insert.out       |   8 +-
 src/test/regress/expected/plancache.out    |   4 +
 src/test/regress/sql/alter_table.sql       |  21 +++-
 src/test/regress/sql/create_table.sql      |   6 +-
 src/test/regress/sql/insert.sql            |   3 -
 src/test/regress/sql/plancache.sql         |   2 +
 13 files changed, 354 insertions(+), 63 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 06c8324e8c..159e6cae10 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1778,15 +1778,8 @@ heap_drop_with_catalog(Oid relid)
 		LockRelationOid(parentOid, AccessExclusiveLock);
 
 		/*
-		 * The partition constraint of the default partition depends on the
-		 * partition bounds of every other partition. It is possible that
-		 * other backend might be about to execute a query on the default
-		 * partition table and the query relies on previously cached default
-		 * partition constraints, which won't be correct after removal of a
-		 * partition. We must therefore take a table lock strong enough to
-		 * prevent all queries on the default partition from proceeding until
-		 * we commit and send out a shared-cache-inval notice that will make
-		 * them update their index lists.
+		 * We must also lock the default partition, for the same reasons
+		 * explained in DefineRelation().
 		 */
 		defaultPartOid = get_default_partition_oid(parentOid);
 		if (OidIsValid(defaultPartOid))
@@ -1908,10 +1901,8 @@ heap_drop_with_catalog(Oid relid)
 	if (OidIsValid(parentOid))
 	{
 		/*
-		 * The partition constraint for the default partition depends on the
-		 * partition bounds of every other partition, so we must invalidate
-		 * the relcache entry for that partition every time a partition is
-		 * added or removed.
+		 * We must invalidate default partition's relcache, for the same
+		 * reasons explained in StorePartitionBound().
 		 */
 		if (OidIsValid(defaultPartOid))
 			CacheInvalidateRelcacheByRelid(defaultPartOid);
@@ -3255,8 +3246,9 @@ RemovePartitionKeyByRelId(Oid relid)
  *		Update pg_class tuple of rel to store the partition bound and set
  *		relispartition to true
  *
- * Also, invalidate the parent's relcache, so that the next rebuild will load
- * the new partition's info into its partition descriptor.
+ * Also, invalidate the parent's relcache entry, so that the next rebuild will
+ * load he new partition's info into its partition descriptor.  If there is a
+ * default partition, we must invalidate its relcache entry as well.
  */
 void
 StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
@@ -3267,6 +3259,7 @@ StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
 	Datum		new_val[Natts_pg_class];
 	bool		new_null[Natts_pg_class],
 				new_repl[Natts_pg_class];
+	Oid			defaultPartOid;
 
 	/* Update pg_class tuple */
 	classRel = heap_open(RelationRelationId, RowExclusiveLock);
@@ -3304,5 +3297,15 @@ StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
 	heap_freetuple(newtuple);
 	heap_close(classRel, RowExclusiveLock);
 
+	/*
+	 * The partition constraint for the default partition depends on the
+	 * partition bounds of every other partition, so we must invalidate the
+	 * relcache entry for that partition every time a partition is added or
+	 * removed.
+	 */
+	defaultPartOid = get_default_partition_oid(RelationGetRelid(parent));
+	if (OidIsValid(defaultPartOid))
+		CacheInvalidateRelcacheByRelid(defaultPartOid);
+
 	CacheInvalidateRelcache(parent);
 }
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 1eafb891d3..b07b18b5a0 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -29,6 +29,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_type.h"
+#include "commands/tablecmds.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -36,6 +37,7 @@
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planmain.h"
+#include "optimizer/prep.h"
 #include "optimizer/var.h"
 #include "rewrite/rewriteManip.h"
 #include "storage/lmgr.h"
@@ -704,10 +706,24 @@ check_new_partition_bound(char *relname, Relation parent,
 {
 	PartitionKey key = RelationGetPartitionKey(parent);
 	PartitionDesc partdesc = RelationGetPartitionDesc(parent);
+	PartitionBoundInfo boundinfo = partdesc->boundinfo;
 	ParseState *pstate = make_parsestate(NULL);
 	int			with = -1;
 	bool		overlap = false;
 
+	if (spec->is_default)
+	{
+		if (boundinfo == NULL || !partition_bound_has_default(boundinfo))
+			return;
+
+		/* Default partition already exists, error out. */
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("partition \"%s\" conflicts with existing default partition \"%s\"",
+						relname, get_rel_name(partdesc->oids[boundinfo->default_index])),
+				 parser_errposition(pstate, spec->location)));
+	}
+
 	switch (key->strategy)
 	{
 		case PARTITION_STRATEGY_LIST:
@@ -716,13 +732,13 @@ check_new_partition_bound(char *relname, Relation parent,
 
 				if (partdesc->nparts > 0)
 				{
-					PartitionBoundInfo boundinfo = partdesc->boundinfo;
 					ListCell   *cell;
 
 					Assert(boundinfo &&
 						   boundinfo->strategy == PARTITION_STRATEGY_LIST &&
 						   (boundinfo->ndatums > 0 ||
-							partition_bound_accepts_nulls(boundinfo)));
+							partition_bound_accepts_nulls(boundinfo) ||
+							partition_bound_has_default(boundinfo)));
 
 					foreach(cell, spec->listdatums)
 					{
@@ -866,6 +882,140 @@ check_new_partition_bound(char *relname, Relation parent,
 }
 
 /*
+ * check_default_allows_bound
+ *
+ * This function checks if there exists a row in the default partition that
+ * fits in the new partition being added and throws an error if it finds one.
+ */
+void
+check_default_allows_bound(Relation parent, Relation default_rel,
+						   PartitionBoundSpec *new_spec)
+{
+	List	   *new_part_constraints;
+	List	   *def_part_constraints;
+	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);
+	def_part_constraints =
+		get_default_part_validation_constraint(new_part_constraints);
+
+	/*
+	 * If the existing constraints on the default partition imply that it will
+	 * not contain any row that would belong to the new partition, we can
+	 * avoid scanning the default partition.
+	 */
+	if (PartConstraintImpliedByRelConstraint(default_rel, def_part_constraints))
+	{
+		ereport(INFO,
+				(errmsg("partition constraint for table \"%s\" is implied by existing constraints",
+						RelationGetRelationName(default_rel))));
+		return;
+	}
+
+	/*
+	 * Bad luck, scan the default partition and its subpartitions, and check
+	 * if any of the row does not satisfy the partition constraints that are
+	 * going to be imposed as additional constraints on default partition by
+	 * addition of the new partition.
+	 */
+	if (default_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		all_parts = find_all_inheritors(RelationGetRelid(default_rel),
+										AccessExclusiveLock, NULL);
+	else
+		all_parts = list_make1_oid(RelationGetRelid(default_rel));
+
+	foreach(lc, all_parts)
+	{
+		Oid			part_relid = lfirst_oid(lc);
+		Relation	part_rel;
+		Expr	   *constr;
+		Expr	   *partition_constraint;
+		EState	   *estate;
+		HeapTuple	tuple;
+		ExprState  *partqualstate = NULL;
+		Snapshot	snapshot;
+		TupleDesc	tupdesc;
+		ExprContext *econtext;
+		HeapScanDesc scan;
+		MemoryContext oldCxt;
+		TupleTableSlot *tupslot;
+
+		/* Lock already taken above. */
+		if (part_relid != RelationGetRelid(default_rel))
+			part_rel = heap_open(part_relid, NoLock);
+		else
+			part_rel = default_rel;
+
+		/*
+		 * Only RELKIND_RELATION relations (i.e. leaf partitions) need to be
+		 * scanned.
+		 */
+		if (part_rel->rd_rel->relkind != RELKIND_RELATION)
+		{
+			if (part_rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+				ereport(WARNING,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("skipped scanning foreign table \"%s\" which is a partition of default partition \"%s\"",
+								RelationGetRelationName(part_rel),
+								RelationGetRelationName(default_rel))));
+
+			if (RelationGetRelid(default_rel) != RelationGetRelid(part_rel))
+				heap_close(part_rel, NoLock);
+
+			continue;
+		}
+
+		tupdesc = CreateTupleDescCopy(RelationGetDescr(part_rel));
+		constr = linitial(def_part_constraints);
+		partition_constraint = (Expr *) map_partition_varattnos((List *) constr,
+																1, part_rel, parent, NULL);
+		estate = CreateExecutorState();
+
+		/* Build expression execution states for partition check quals */
+		partqualstate = ExecPrepareExpr(partition_constraint, estate);
+
+		econtext = GetPerTupleExprContext(estate);
+		snapshot = RegisterSnapshot(GetLatestSnapshot());
+		scan = heap_beginscan(part_rel, snapshot, 0, NULL);
+		tupslot = MakeSingleTupleTableSlot(tupdesc);
+
+		/*
+		 * Switch to per-tuple memory context and reset it for each tuple
+		 * produced, so we don't leak memory.
+		 */
+		oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		{
+			ExecStoreTuple(tuple, tupslot, InvalidBuffer, false);
+			econtext->ecxt_scantuple = tupslot;
+
+			if (!ExecCheck(partqualstate, econtext))
+				ereport(ERROR,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("updated partition constraint for default partition \"%s\" would be violated by some row",
+								RelationGetRelationName(default_rel))));
+
+			ResetExprContext(econtext);
+			CHECK_FOR_INTERRUPTS();
+		}
+
+		MemoryContextSwitchTo(oldCxt);
+		heap_endscan(scan);
+		UnregisterSnapshot(snapshot);
+		ExecDropSingleTupleTableSlot(tupslot);
+		FreeExecutorState(estate);
+
+		if (RelationGetRelid(default_rel) != RelationGetRelid(part_rel))
+			heap_close(part_rel, NoLock);	/* keep the lock until commit */
+	}
+}
+
+/*
  * get_partition_parent
  *
  * Returns inheritance parent of a partition by scanning pg_inherits
@@ -2522,3 +2672,32 @@ update_default_partition_oid(Oid parentId, Oid defaultPartId)
 	heap_freetuple(tuple);
 	heap_close(pg_partitioned_table, RowExclusiveLock);
 }
+
+/*
+ * get_default_part_validation_constraint
+ *
+ * This function returns negated constraint of new_part_constraints which
+ * would be an integral part of the default partition constraints after
+ * addition of the partition to which the new_part_constraints belongs.
+ */
+List *
+get_default_part_validation_constraint(List *new_part_constraints)
+{
+	Expr	   *defPartConstraint;
+
+	defPartConstraint = make_ands_explicit(new_part_constraints);
+
+	/*
+	 * Derieve the partition constraints of default partition by negating the
+	 * given partition constraints. The partition constraint never evaluates
+	 * to NULL, so negating it like this is safe.
+	 */
+	defPartConstraint = makeBoolExpr(NOT_EXPR,
+									 list_make1(defPartConstraint),
+									 -1);
+	defPartConstraint = (Expr *) eval_const_expressions(NULL,
+														(Node *) defPartConstraint);
+	defPartConstraint = canonicalize_qual(defPartConstraint);
+
+	return list_make1(defPartConstraint);
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 9aad58fee9..94a5992994 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -168,6 +168,8 @@ typedef struct AlteredTableInfo
 	bool		chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
 	char		newrelpersistence;	/* if above is true */
 	Expr	   *partition_constraint;	/* for attach partition validation */
+	/* true, if is a default partition or a child of default partition */
+	bool		is_default_partition;
 	/* Objects to rebuild after completing ALTER TYPE operations */
 	List	   *changedConstraintOids;	/* OIDs of constraints to rebuild */
 	List	   *changedConstraintDefs;	/* string definitions of same */
@@ -473,11 +475,10 @@ static void CreateInheritance(Relation child_rel, Relation parent_rel);
 static void RemoveInheritance(Relation child_rel, Relation parent_rel);
 static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
 					  PartitionCmd *cmd);
-static bool PartConstraintImpliedByRelConstraint(Relation scanrel,
-									 List *partConstraint);
 static void ValidatePartitionConstraints(List **wqueue, Relation scanrel,
 							 List *scanrel_children,
-							 List *partConstraint);
+							 List *partConstraint,
+							 bool scanrel_is_default);
 static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
 
 
@@ -776,7 +777,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		ParseState *pstate;
 		Oid			parentId = linitial_oid(inheritOids),
 					defaultPartOid;
-		Relation	parent;
+		Relation	parent,
+					defaultRel;
 
 		/* Already have strong enough lock on the parent */
 		parent = heap_open(parentId, NoLock);
@@ -792,15 +794,28 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 							RelationGetRelationName(parent))));
 
 		/*
-		 * A table cannot be created as a partition of a parent already having
-		 * a default partition.
+		 * The partition constraint of the default partition depends on the
+		 * partition bounds of every other partition. It is possible that
+		 * other backend might be about to execute a query on the default
+		 * partition table and the query relies on previously cached default
+		 * partition constraints, which won't be correct after removal of a
+		 * partition. We must therefore take a table lock strong enough to
+		 * prevent all queries on the default partition from proceeding until
+		 * we commit and send out a shared-cache-inval notice that will make
+		 * them update their index lists.
+		 *
+		 * Order of locking: The relation being added won't be visible to
+		 * other backends until it is committed, hence here in
+		 * DefineRelation() the order of locking the default partition and the
+		 * relation being added does not matter. But at all other places we
+		 * need to lock the default relation before we lock the relation being
+		 * added or removed i.e. we should take the lock in same order at all
+		 * the places such that lock parent, lock default partition and then
+		 * lock the partition so as to avoid a deadlock.
 		 */
 		defaultPartOid = get_default_partition_oid(RelationGetRelid(parent));
 		if (OidIsValid(defaultPartOid))
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-					 errmsg("cannot add a new partition to table \"%s\" having a default partition",
-							RelationGetRelationName(parent))));
+			defaultRel = heap_open(defaultPartOid, AccessExclusiveLock);
 
 		/* Tranform the bound values */
 		pstate = make_parsestate(NULL);
@@ -815,6 +830,20 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		 */
 		check_new_partition_bound(relname, parent, bound);
 
+		/*
+		 * If the default partition exists, its partition constraints will
+		 * change after the addition of this new partition such that it won't
+		 * allow any row that qualifies for this new partition. So, check that
+		 * the existing data in the default partition satisfies the constraint
+		 * as it will exist after adding this partition.
+		 */
+		if (OidIsValid(defaultPartOid))
+		{
+			check_default_allows_bound(parent, defaultRel, bound);
+			/* Keep the lock until commit. */
+			heap_close(defaultRel, NoLock);
+		}
+
 		/* Update the pg_class entry. */
 		StorePartitionBound(rel, parent, bound);
 
@@ -4611,9 +4640,16 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 			}
 
 			if (partqualstate && !ExecCheck(partqualstate, econtext))
-				ereport(ERROR,
-						(errcode(ERRCODE_CHECK_VIOLATION),
-						 errmsg("partition constraint is violated by some row")));
+			{
+				if (tab->is_default_partition)
+					ereport(ERROR,
+							(errcode(ERRCODE_CHECK_VIOLATION),
+							 errmsg("updated partition constraint for default partition would be violated by some row")));
+				else
+					ereport(ERROR,
+							(errcode(ERRCODE_CHECK_VIOLATION),
+							 errmsg("partition constraint is violated by some row")));
+			}
 
 			/* Write the tuple out to the new relation */
 			if (newrel)
@@ -13467,7 +13503,7 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
  * Existing constraints includes its check constraints and column-level
  * NOT NULL constraints and partConstraint describes the partition constraint.
  */
-static bool
+bool
 PartConstraintImpliedByRelConstraint(Relation scanrel,
 									 List *partConstraint)
 {
@@ -13554,7 +13590,8 @@ PartConstraintImpliedByRelConstraint(Relation scanrel,
 static void
 ValidatePartitionConstraints(List **wqueue, Relation scanrel,
 							 List *scanrel_children,
-							 List *partConstraint)
+							 List *partConstraint,
+							 bool scanrel_is_default)
 {
 	bool		found_whole_row;
 	ListCell   *lc;
@@ -13616,6 +13653,7 @@ ValidatePartitionConstraints(List **wqueue, Relation scanrel,
 		/* Grab a work queue entry. */
 		tab = ATGetQueueEntry(wqueue, part_rel);
 		tab->partition_constraint = (Expr *) linitial(my_partconstr);
+		tab->is_default_partition = scanrel_is_default;
 
 		/* keep our lock until commit */
 		if (part_rel != scanrel)
@@ -13644,14 +13682,15 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	const char *trigger_name;
 	bool		found_whole_row;
 	Oid			defaultPartOid;
+	List	   *partBoundConstraint;
 
-	/* A partition cannot be attached if there exists a default partition */
+	/*
+	 * We must lock the default partition, for the same reasons explained in
+	 * DefineRelation().
+	 */
 	defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
 	if (OidIsValid(defaultPartOid))
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-				 errmsg("cannot attach a new partition to table \"%s\" having a default partition",
-						RelationGetRelationName(rel))));
+		LockRelationOid(defaultPartOid, AccessExclusiveLock);
 
 	attachrel = heap_openrv(cmd->name, AccessExclusiveLock);
 
@@ -13829,8 +13868,8 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	 * If the parent itself is a partition, make sure to include its
 	 * constraint as well.
 	 */
-	partConstraint = list_concat(get_qual_from_partbound(attachrel, rel,
-														 cmd->bound),
+	partBoundConstraint = get_qual_from_partbound(attachrel, rel, cmd->bound);
+	partConstraint = list_concat(partBoundConstraint,
 								 RelationGetPartitionQual(rel));
 
 	/* Skip validation if there are no constraints to validate. */
@@ -13853,7 +13892,30 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 
 		/* Validate partition constraints against the table being attached. */
 		ValidatePartitionConstraints(wqueue, attachrel, attachrel_children,
-									 partConstraint);
+									 partConstraint, false);
+
+		/*
+		 * Check whether default partition has a row that would fit the
+		 * partition being attached.
+		 */
+		defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
+		if (OidIsValid(defaultPartOid))
+		{
+			Relation	defaultrel;
+			List	   *defaultrel_children;
+			List	   *defPartConstraint;
+
+			/* We already have taken a lock on default partition. */
+			defaultrel = heap_open(defaultPartOid, NoLock);
+			defPartConstraint = get_default_part_validation_constraint(partBoundConstraint);
+			defaultrel_children = find_all_inheritors(defaultPartOid,
+													  AccessExclusiveLock, NULL);
+			ValidatePartitionConstraints(wqueue, defaultrel, defaultrel_children,
+										 defPartConstraint, true);
+
+			/* keep our lock until commit. */
+			heap_close(defaultrel, NoLock);
+		}
 	}
 
 	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachrel));
@@ -13885,7 +13947,7 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 
 	/*
 	 * We must lock the default partition, for the same reasons explained in
-	 * heap_drop_with_catalog().
+	 * DefineRelation().
 	 */
 	defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
 	if (OidIsValid(defaultPartOid))
@@ -13932,7 +13994,7 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 
 		/*
 		 * We must invalidate default partition's relcache, for the same
-		 * reasons explained in heap_drop_with_catalog().
+		 * reasons explained in StorePartitionBound().
 		 */
 		CacheInvalidateRelcacheByRelid(defaultPartOid);
 	}
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 1dd70f2c6d..ee0cc151bd 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -101,5 +101,8 @@ extern int get_partition_for_tuple(PartitionDispatch *pd,
 						TupleTableSlot **failed_slot);
 extern Oid	get_default_partition_oid(Oid parentId);
 extern void update_default_partition_oid(Oid parentId, Oid defaultPartId);
+extern void check_default_allows_bound(Relation parent, Relation defaultRel,
+						   PartitionBoundSpec *new_spec);
+extern List *get_default_part_validation_constraint(List *new_part_constaints);
 
 #endif							/* PARTITION_H */
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index abd31b68d4..da3ff5dbee 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -18,6 +18,7 @@
 #include "catalog/dependency.h"
 #include "catalog/objectaddress.h"
 #include "nodes/parsenodes.h"
+#include "catalog/partition.h"
 #include "storage/lock.h"
 #include "utils/relcache.h"
 
@@ -87,4 +88,7 @@ extern void RangeVarCallbackOwnsTable(const RangeVar *relation,
 
 extern void RangeVarCallbackOwnsRelation(const RangeVar *relation,
 							 Oid relId, Oid oldRelId, void *noCatalogs);
+extern bool PartConstraintImpliedByRelConstraint(Relation scanrel,
+									 List *partConstraint);
+
 #endif							/* TABLECMDS_H */
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index c1e090aef4..a3e2c82c3b 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3280,7 +3280,7 @@ ALTER TABLE list_parted ATTACH PARTITION def_part DEFAULT;
 -- exists
 CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT;
-ERROR:  cannot attach a new partition to table "list_parted" having a default partition
+ERROR:  partition "fail_def_part" conflicts with existing default partition "def_part"
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
 	a int,
@@ -3294,14 +3294,14 @@ ERROR:  partition constraint is violated by some row
 -- should be ok after deleting the bad row
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
--- check partition cannot be attached if default exists
+-- check partition cannot be attached if default has some row for its values
 CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 INSERT INTO list_parted2_def VALUES (11, 'z');
 CREATE TABLE part_3 (LIKE list_parted2);
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
-ERROR:  cannot attach a new partition to table "list_parted2" having a default partition
--- should be ok after detaching a default partition
-ALTER TABLE list_parted2 DETACH PARTITION list_parted2_def;
+ERROR:  updated partition constraint for default partition would be violated by some row
+-- should be ok after deleting the bad row
+DELETE FROM list_parted2_def WHERE a = 11;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
@@ -3318,6 +3318,10 @@ ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
 ALTER TABLE part_3_4 ALTER a SET NOT NULL;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
 INFO:  partition constraint for table "part_3_4" is implied by existing constraints
+-- check if default partition scan skipped
+ALTER TABLE list_parted2_def ADD CONSTRAINT check_a CHECK (a IN (5, 6));
+CREATE TABLE part_55_66 PARTITION OF list_parted2 FOR VALUES IN (55, 66);
+INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
 -- check validation when attaching range partitions
 CREATE TABLE range_parted (
 	a int,
@@ -3395,6 +3399,7 @@ ALTER TABLE part_7 ATTACH PARTITION part_7_a_null FOR VALUES IN ('a', null);
 INFO:  partition constraint for table "part_7_a_null" is implied by existing constraints
 ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
 INFO:  partition constraint for table "part_7" is implied by existing constraints
+INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
 -- Same example, but check this time that the constraint correctly detects
 -- violating rows
 ALTER TABLE list_parted2 DETACH PARTITION part_7;
@@ -3408,7 +3413,20 @@ SELECT tableoid::regclass, a, b FROM part_7 order by a;
 (2 rows)
 
 ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
+INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
 ERROR:  partition constraint is violated by some row
+-- check that leaf partitions of default partition are scanned when
+-- attaching a partitioned table.
+ALTER TABLE part_5 DROP CONSTRAINT check_a;
+CREATE TABLE part5_def PARTITION OF part_5 DEFAULT PARTITION BY LIST(a);
+CREATE TABLE part5_def_p1 PARTITION OF part5_def FOR VALUES IN (5);
+INSERT INTO part5_def_p1 VALUES (5, 'y');
+CREATE TABLE part5_p1 (LIKE part_5);
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+ERROR:  updated partition constraint for default partition would be violated by some row
+-- should be ok after deleting the bad row
+DELETE FROM part5_def_p1 WHERE b = 'y';
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
 -- check that the table being attached is not already a partition
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 ERROR:  "part_2" is already a partition
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index a6a7bf485b..bf0acadc4b 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -470,10 +470,7 @@ LINE 1: ...BLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) T...
 -- check default partition cannot be created more than once
 CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
 CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
-ERROR:  cannot add a new partition to table "list_parted" having a default partition
--- check that no partition can be created after default partition
-CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (3);
-ERROR:  cannot add a new partition to table "list_parted" having a default partition
+ERROR:  partition "fail_default_part" conflicts with existing default partition "part_default"
 -- specified literal can't be cast to the partition column data type
 CREATE TABLE bools (
 	a bool
@@ -565,10 +562,15 @@ CREATE TABLE list_parted2 (
 ) PARTITION BY LIST (a);
 CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
 CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
 ERROR:  partition "fail_part" would overlap partition "part_null_z"
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
 ERROR:  partition "fail_part" would overlap partition "part_ab"
+-- check default partition overlap
+INSERT INTO list_parted2 VALUES('X');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y');
+ERROR:  updated partition constraint for default partition "list_parted2_def" would be violated by some row
 CREATE TABLE range_parted2 (
 	a int
 ) PARTITION BY RANGE (a);
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 17f0210726..5ca6e5ea23 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -276,9 +276,6 @@ select tableoid::regclass, * from list_parted;
  part_default_p2    | de | 35
 (9 rows)
 
--- drop default partition, as we need to add some more partitions to test tuple
--- routing
-drop table part_default;
 -- Check tuple routing for partitioned tables
 -- fail
 insert into range_parted values ('a', 0);
@@ -335,7 +332,10 @@ select tableoid::regclass, * from list_parted;
  part_ee_ff2        | EE | 10
  part_xx_yy_p1      | xx |  1
  part_xx_yy_defpart | yy |  2
-(10 rows)
+ part_default_p1    | cd | 25
+ part_default_p1    | ab | 21
+ part_default_p2    | de | 35
+(13 rows)
 
 -- some more tests to exercise tuple-routing with multi-level partitioning
 create table part_gg partition of list_parted for values in ('gg') partition by range (b);
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
index fb903574c5..c2eeff1614 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -266,6 +266,10 @@ DETAIL:  Failing row contains (null).
 execute pstmt_def_insert(1);
 ERROR:  new row for relation "list_part_def" violates partition constraint
 DETAIL:  Failing row contains (1).
+create table list_part_2 partition of list_parted for values in (2);
+execute pstmt_def_insert(2);
+ERROR:  new row for relation "list_part_def" violates partition constraint
+DETAIL:  Failing row contains (2).
 alter table list_parted detach partition list_part_null;
 -- should be ok
 execute pstmt_def_insert(null);
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 47641a458d..c565d7f516 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2118,13 +2118,13 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 
--- check partition cannot be attached if default exists
+-- check partition cannot be attached if default has some row for its values
 CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 INSERT INTO list_parted2_def VALUES (11, 'z');
 CREATE TABLE part_3 (LIKE list_parted2);
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
--- should be ok after detaching a default partition
-ALTER TABLE list_parted2 DETACH PARTITION list_parted2_def;
+-- should be ok after deleting the bad row
+DELETE FROM list_parted2_def WHERE a = 11;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
 
 -- adding constraints that describe the desired partition constraint
@@ -2144,6 +2144,9 @@ ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
 ALTER TABLE part_3_4 ALTER a SET NOT NULL;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
 
+-- check if default partition scan skipped
+ALTER TABLE list_parted2_def ADD CONSTRAINT check_a CHECK (a IN (5, 6));
+CREATE TABLE part_55_66 PARTITION OF list_parted2 FOR VALUES IN (55, 66);
 
 -- check validation when attaching range partitions
 CREATE TABLE range_parted (
@@ -2232,6 +2235,18 @@ INSERT INTO part_7 (a, b) VALUES (8, null), (9, 'a');
 SELECT tableoid::regclass, a, b FROM part_7 order by a;
 ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
 
+-- check that leaf partitions of default partition are scanned when
+-- attaching a partitioned table.
+ALTER TABLE part_5 DROP CONSTRAINT check_a;
+CREATE TABLE part5_def PARTITION OF part_5 DEFAULT PARTITION BY LIST(a);
+CREATE TABLE part5_def_p1 PARTITION OF part5_def FOR VALUES IN (5);
+INSERT INTO part5_def_p1 VALUES (5, 'y');
+CREATE TABLE part5_p1 (LIKE part_5);
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+-- should be ok after deleting the bad row
+DELETE FROM part5_def_p1 WHERE b = 'y';
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+
 -- check that the table being attached is not already a partition
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 8d0c252ff4..07d653a087 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -450,8 +450,6 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
 -- check default partition cannot be created more than once
 CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
 CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
--- check that no partition can be created after default partition
-CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (3);
 
 -- specified literal can't be cast to the partition column data type
 CREATE TABLE bools (
@@ -530,9 +528,13 @@ CREATE TABLE list_parted2 (
 ) PARTITION BY LIST (a);
 CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
 CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+-- check default partition overlap
+INSERT INTO list_parted2 VALUES('X');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y');
 
 CREATE TABLE range_parted2 (
 	a int
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index a44d454379..7bd06198ce 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -165,9 +165,6 @@ insert into list_parted values ('ab', 21);
 insert into list_parted values ('xx', 1);
 insert into list_parted values ('yy', 2);
 select tableoid::regclass, * from list_parted;
--- drop default partition, as we need to add some more partitions to test tuple
--- routing
-drop table part_default;
 
 -- Check tuple routing for partitioned tables
 
diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql
index a8e357c34c..cb2a551487 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -167,6 +167,8 @@ prepare pstmt_def_insert (int) as insert into list_part_def values($1);
 -- should fail
 execute pstmt_def_insert(null);
 execute pstmt_def_insert(1);
+create table list_part_2 partition of list_parted for values in (2);
+execute pstmt_def_insert(2);
 alter table list_parted detach partition list_part_null;
 -- should be ok
 execute pstmt_def_insert(null);
-- 
2.13.3

0004-Check-default-partitition-child-validation-scan-is.patch0000644000076500000240000001301713146531753023374 0ustar  jeevanstaffFrom ad7cbcf38cac4feeb83a3235e09b081d668cb1b9 Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Mon, 21 Aug 2017 15:36:49 +0530
Subject: [PATCH 4/5] Check default partitition child validation scan is
 skippable

Add code to check_default_allows_bound() such that the default
partition children constraints are checked against new partition
constraints for implication and avoid scan of the child of which
existing constraints are implied by new default partition
constraints. Also, added testcase for the same.

Jeevan Ladhe.
---
 src/backend/catalog/partition.c           | 18 ++++++++++++++++++
 src/test/regress/expected/alter_table.out | 14 ++++++++++++--
 src/test/regress/sql/alter_table.sql      |  9 +++++++++
 3 files changed, 39 insertions(+), 2 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index b07b18b5a0..06444f4931 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -946,7 +946,25 @@ check_default_allows_bound(Relation parent, Relation default_rel,
 
 		/* Lock already taken above. */
 		if (part_relid != RelationGetRelid(default_rel))
+		{
 			part_rel = heap_open(part_relid, NoLock);
+
+			/*
+			 * If the partition constraints on default partition child imply
+			 * that it will not contain any row that would belong to the new
+			 * partition, we can avoid scanning the child table.
+			 */
+			if (PartConstraintImpliedByRelConstraint(part_rel,
+													 def_part_constraints))
+			{
+				ereport(INFO,
+						(errmsg("partition constraint for table \"%s\" is implied by existing constraints",
+								RelationGetRelationName(part_rel))));
+
+				heap_close(part_rel, NoLock);
+				continue;
+			}
+		}
 		else
 			part_rel = default_rel;
 
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index a3e2c82c3b..bef8d5531f 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3322,6 +3322,18 @@ INFO:  partition constraint for table "part_3_4" is implied by existing constrai
 ALTER TABLE list_parted2_def ADD CONSTRAINT check_a CHECK (a IN (5, 6));
 CREATE TABLE part_55_66 PARTITION OF list_parted2 FOR VALUES IN (55, 66);
 INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
+-- check if default partition child relation scan skipped
+DROP TABLE list_parted2_def;
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT PARTITION BY RANGE (a);
+CREATE TABLE list_parted2_def_p1 PARTITION OF list_parted2_def FOR VALUES FROM (21) TO (30);
+CREATE TABLE list_parted2_def_p2 PARTITION OF list_parted2_def FOR VALUES FROM (31) TO (40);
+ALTER TABLE list_parted2_def_p1 ADD CONSTRAINT check_a CHECK (a IN (21, 22));
+ALTER TABLE list_parted2_def_p2 ADD CONSTRAINT check_a CHECK (a IN (31, 32));
+CREATE TABLE part_9 PARTITION OF list_parted2 FOR VALUES IN (9);
+INFO:  partition constraint for table "list_parted2_def_p1" is implied by existing constraints
+INFO:  partition constraint for table "list_parted2_def_p2" is implied by existing constraints
+CREATE TABLE part_31 PARTITION OF list_parted2 FOR VALUES IN (31);
+INFO:  partition constraint for table "list_parted2_def_p1" is implied by existing constraints
 -- check validation when attaching range partitions
 CREATE TABLE range_parted (
 	a int,
@@ -3399,7 +3411,6 @@ ALTER TABLE part_7 ATTACH PARTITION part_7_a_null FOR VALUES IN ('a', null);
 INFO:  partition constraint for table "part_7_a_null" is implied by existing constraints
 ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
 INFO:  partition constraint for table "part_7" is implied by existing constraints
-INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
 -- Same example, but check this time that the constraint correctly detects
 -- violating rows
 ALTER TABLE list_parted2 DETACH PARTITION part_7;
@@ -3413,7 +3424,6 @@ SELECT tableoid::regclass, a, b FROM part_7 order by a;
 (2 rows)
 
 ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
-INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
 ERROR:  partition constraint is violated by some row
 -- check that leaf partitions of default partition are scanned when
 -- attaching a partitioned table.
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index c565d7f516..92a93ebd8e 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2147,6 +2147,15 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
 -- check if default partition scan skipped
 ALTER TABLE list_parted2_def ADD CONSTRAINT check_a CHECK (a IN (5, 6));
 CREATE TABLE part_55_66 PARTITION OF list_parted2 FOR VALUES IN (55, 66);
+-- check if default partition child relation scan skipped
+DROP TABLE list_parted2_def;
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT PARTITION BY RANGE (a);
+CREATE TABLE list_parted2_def_p1 PARTITION OF list_parted2_def FOR VALUES FROM (21) TO (30);
+CREATE TABLE list_parted2_def_p2 PARTITION OF list_parted2_def FOR VALUES FROM (31) TO (40);
+ALTER TABLE list_parted2_def_p1 ADD CONSTRAINT check_a CHECK (a IN (21, 22));
+ALTER TABLE list_parted2_def_p2 ADD CONSTRAINT check_a CHECK (a IN (31, 32));
+CREATE TABLE part_9 PARTITION OF list_parted2 FOR VALUES IN (9);
+CREATE TABLE part_31 PARTITION OF list_parted2 FOR VALUES IN (31);
 
 -- check validation when attaching range partitions
 CREATE TABLE range_parted (
-- 
2.13.3

0005-Documentation-for-default-partition.patch0000644000076500000240000002247713146531753020666 0ustar  jeevanstaffFrom d98ad7634ebb45fbcf6650d3775c448ec9564fb7 Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Mon, 21 Aug 2017 15:42:37 +0530
Subject: [PATCH 5/5] Documentation for default partition.

This patch adds documentation for default partition for
CREATE TABLE and ALTER TABLE commands with example.
Also, added documentation for pg_partitioned_table new
field partdefid.

Jeevan Ladhe.
---
 doc/src/sgml/catalogs.sgml         | 11 +++++++++++
 doc/src/sgml/ref/alter_table.sgml  | 35 ++++++++++++++++++++++++++++++++---
 doc/src/sgml/ref/create_table.sgml | 32 +++++++++++++++++++++++++++++---
 3 files changed, 72 insertions(+), 6 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index ef7054cf26..b381e7ecad 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -4739,6 +4739,17 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</>:<replaceable>&lt;salt&gt;<
      </row>
 
      <row>
+      <entry><structfield>partdefid</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+      <entry>
+       The OID of the <structname>pg_class</> entry for the default partition
+       of this partitioned table, or zero if this partitioned table does not
+       have a default partition.
+     </entry>
+     </row>
+
+     <row>
       <entry><structfield>partattrs</structfield></entry>
       <entry><type>int2vector</type></entry>
       <entry><literal><link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.attnum</literal></entry>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 69600321e6..8dd0f14e38 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -34,7 +34,7 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
 ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable> [ OWNED BY <replaceable class="PARAMETER">role_name</replaceable> [, ... ] ]
     SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable> [ NOWAIT ]
 ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
-    ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+    ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> { FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> | DEFAULT }
 ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
     DETACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable>
 
@@ -765,11 +765,18 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
    </varlistentry>
 
    <varlistentry>
-    <term><literal>ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable></literal></term>
+    <term><literal>ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> { FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> | DEFAULT }</literal></term>
     <listitem>
      <para>
       This form attaches an existing table (which might itself be partitioned)
-      as a partition of the target table using the same syntax for
+      as a partition of the target table. The table can be attached
+      as a partition for specific values using <literal>FOR VALUES
+      </literal> or as a default partition by using <literal>DEFAULT
+      </literal>.
+     </para>
+
+     <para>
+      A partition using <literal>FOR VALUES</literal> uses same syntax for
       <replaceable class="PARAMETER">partition_bound_spec</replaceable> as
       <xref linkend="sql-createtable">.  The partition bound specification
       must correspond to the partitioning strategy and partition key of the
@@ -798,6 +805,10 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       a list partition that will not accept <literal>NULL</literal> values,
       also add <literal>NOT NULL</literal> constraint to the partition key
       column, unless it's an expression.
+      Also, if there exists a default partition table for the parent table,
+      then the default partition (if it is a regular table) is scanned to
+      check that no existing row in default partition would fit in the
+      partition that is being attached.
      </para>
 
      <para>
@@ -806,6 +817,17 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       (See the discussion in <xref linkend="SQL-CREATEFOREIGNTABLE"> about
       constraints on the foreign table.)
      </para>
+
+     <para>
+      When a table has a default partition, defining a new partition changes
+      the partition constraint for the default partition. The default
+      partition can't contain any rows that would need to be moved to the new
+      partition, and will be scanned to verify that none are present. This
+      scan, like the scan of the new partition, can be avoided if an
+      appropriate <literal>CHECK</literal> constraint is present. Also like
+      the scan of the new partition, it is always skipped when the default
+      partition is a foreign table.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -1396,6 +1418,13 @@ ALTER TABLE cities
 </programlisting></para>
 
   <para>
+   Attach a default partition to a partitioned table:
+<programlisting>
+ALTER TABLE cities
+    ATTACH PARTITION cities_partdef DEFAULT;
+</programlisting></para>
+
+  <para>
    Detach a partition from partitioned table:
 <programlisting>
 ALTER TABLE measurement
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index e9c2c49533..c08abacae6 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -49,7 +49,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
   { <replaceable class="PARAMETER">column_name</replaceable> [ WITH OPTIONS ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
     | <replaceable>table_constraint</replaceable> }
     [, ... ]
-) ] FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+) ] { FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> | DEFAULT }
 [ PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
 [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
 [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
@@ -250,11 +250,13 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
    </varlistentry>
 
    <varlistentry id="SQL-CREATETABLE-PARTITION">
-    <term><literal>PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable> FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable></literal></term>
+    <term><literal>PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable> { FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> | DEFAULT }</literal></term>
     <listitem>
      <para>
       Creates the table as a <firstterm>partition</firstterm> of the specified
-      parent table.
+      parent table. The table can be created either as a partition for specific
+      values using <literal>FOR VALUES</literal> or as a default partition
+      using <literal>DEFAULT</literal>.
      </para>
 
      <para>
@@ -343,6 +345,23 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
      </para>
 
      <para>
+     If <literal>DEFAULT</literal> is specified, the table will be
+     created as a default partition of the parent table. The parent can
+     either be a list or range partitioned table. A partition key value
+     not fitting into any other partition of the given parent will be
+     routed to the default partition. There can be only one default
+     partition for a given parent table.
+     </para>
+
+     <para>
+     If the given parent is already having a default partition then
+     adding a new partition would result in an error if the default
+     partition contains a record that would fit in the new partition
+     being added. This check is not performed if the default partition
+     is a foreign table.
+     </para>
+
+     <para>
       A partition must have the same column names and types as the partitioned
       table to which it belongs.  If the parent is specified <literal>WITH
       OIDS</literal> then all partitions must have OIDs; the parent's OID
@@ -1679,6 +1698,13 @@ CREATE TABLE cities_ab
 CREATE TABLE cities_ab_10000_to_100000
     PARTITION OF cities_ab FOR VALUES FROM (10000) TO (100000);
 </programlisting></para>
+
+  <para>
+   Create a default partition of partitioned table:
+<programlisting>
+CREATE TABLE cities_partdef
+    PARTITION OF cities DEFAULT;
+</programlisting></para>
  </refsect1>
 
  <refsect1 id="SQL-CREATETABLE-compatibility">
-- 
2.13.3

#171Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Jeevan Ladhe (#170)
Re: Adding support for Default partition in partitioning

On Mon, Aug 21, 2017 at 4:47 PM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

Hi,

On Thu, Aug 17, 2017 at 3:29 PM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

Hi,

On Tue, Aug 15, 2017 at 7:20 PM, Robert Haas <robertmhaas@gmail.com>
wrote:

On Wed, Jul 26, 2017 at 8:14 AM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

I have rebased the patches on the latest commit.

This needs another rebase.

I have rebased the patch and addressed your and Ashutosh comments on last
set of patches.

Thanks for the rebased patches.

The current set of patches contains 6 patches as below:

0001:
Refactoring existing ATExecAttachPartition code so that it can be used
for
default partitioning as well

  * Returns an expression tree describing the passed-in relation's partition
- * constraint.
+ * constraint. If there are no partition constraints returns NULL e.g. in case
+ * default partition is the only partition.
The first sentence uses singular constraint. The second uses plural. Given that
partition bounds together form a single constraint we should use singular
constraint in the second sentence as well.

Do we want to add a similar comment in the prologue of
generate_partition_qual(). The current wording there seems to cover this case,
but do we want to explicitly mention this case?

+        if (!and_args)
+            result = NULL;
While this is correct, I am increasingly seeing (and_args != NIL) usage.

get_partition_qual_relid() is called from pg_get_partition_constraintdef(),
constr_expr = get_partition_qual_relid(relationId);

/* Quick exit if not a partition */
if (constr_expr == NULL)
PG_RETURN_NULL();
The comment is now wrong since a default partition may have no constraints. May
be rewrite it as simply, "Quick exit if no partition constraint."

generate_partition_qual() has three callers and all of them are capable of
handling NIL partition constraint for default partition. May be it's better to
mention in the commit message that we have checked that the callers of
this function
can handle NIL partition constraint.

0002:
This patch teaches the partitioning code to handle the NIL returned by
get_qual_for_list().
This is needed because a default partition will not have any constraints
in case
it is the only partition of its parent.

If the partition being dropped is the default partition,
heap_drop_with_catalog() locks default partition twice, once as the default
partition and the second time as the partition being dropped. So, it will be
counted as locked twice. There doesn't seem to be any harm in this, since the
lock will be help till the transaction ends, by when all the locks will be
released.

Same is the case with cache invalidation message. If we are dropping default
partition, the cache invalidation message on "default partition" is not
required. Again this might be harmless, but better to avoid it.

Similar problems exists with ATExecDetachPartition(), default partition will be
locked twice if it's being detached.

+        /*
+         * If this is a default partition, pg_partitioned_table must have it's
+         * OID as value of 'partdefid' for it's parent (i.e. rel) entry.
+         */
+        if (castNode(PartitionBoundSpec, boundspec)->is_default)
+        {
+            Oid            partdefid;
+
+            partdefid = get_default_partition_oid(RelationGetRelid(rel));
+            Assert(partdefid == inhrelid);
+        }
Since an accidental change or database corruption may change the default
partition OID in pg_partition_table. An Assert won't help in such a case. May
be we should throw an error or at least report a warning. If we throw an error,
the table will become useless (or even the database will become useless
RelationBuildPartitionDesc is called from RelationCacheInitializePhase3() on
such a corrupted table). To avoid that we may raise a warning.

I am wondering whether we could avoid call to get_default_partition_oid() in
the above block, thus avoiding a sys cache lookup. The sys cache search
shouldn't be expensive since the cache should already have that entry, but
still if we can avoid it, we save some CPU cycles. The default partition OID is
stored in pg_partition_table catalog, which is looked up in
RelationGetPartitionKey(), a function which precedes RelationGetPartitionDesc()
everywhere. What if that RelationGetPartitionKey() also returns the default
partition OID and the common caller passes it to RelationGetPartitionDesc()?.

+    /* A partition cannot be attached if there exists a default partition */
+    defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
+    if (OidIsValid(defaultPartOid))
+        ereport(ERROR,
+                (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                 errmsg("cannot attach a new partition to table
\"%s\" having a default partition",
+                        RelationGetRelationName(rel))));
get_default_partition_oid() searches the catalogs, which is not needed when we
have relation descriptor of the partitioned table (to which a new partition is
being attached). You should get the default partition OID from partition
descriptor. That will be cheaper.
+                /* If there isn't any constraint, show that explicitly */
+                if (partconstraintdef[0] == '\0')
+                    printfPQExpBuffer(&tmpbuf, _("No partition constraint"));
I think we need to change the way we set partconstraintdef at
            if (PQnfields(result) == 3)
                partconstraintdef = PQgetvalue(result, 0, 2);
Before this commit, constraints will never be NULL so this code works, but now
that the cosntraints could be NULL, we need to check whether 3rd value is NULL
or not using PQgetisnull() and assigning a value only when it's not NULL.

+-- test adding default partition as first partition accepts any value including
grammar, reword as "test that a default partition added as the first
partition accepts any
value including".

0003:
Support for default partition with the restriction of preventing addition
of any
new partition after default partition. This is a merge of 0003 and 0004 in
V24 series.

The commit message of this patch has following line, which no more applies to
patch 0001. May be you want to remove this line or update the patch number.
3. This patch uses the refactored functions created in patch 0001
in this series.
Similarly the credit line refers to patch 0001. That too needs correction.

- * Also, invalidate the parent's relcache, so that the next rebuild will load
- * the new partition's info into its partition descriptor.
+ * Also, invalidate the parent's relcache entry, so that the next rebuild will
+ * load he new partition's info into its partition descriptor.  If there is a
+ * default partition, we must invalidate its relcache entry as well.
Replacing "relcache" with "relcache entry" in the first sentence  may be a good
idea, but is unrelated to this patch. I would leave that change aside and just
add comment about default partition.
+    /*
+     * The partition constraint for the default partition depends on the
+     * partition bounds of every other partition, so we must invalidate the
+     * relcache entry for that partition every time a partition is added or
+     * removed.
+     */
+    defaultPartOid = get_default_partition_oid(RelationGetRelid(parent));
+    if (OidIsValid(defaultPartOid))
+        CacheInvalidateRelcacheByRelid(defaultPartOid);
Again, since we have access to the parent's relcache, we should get the default
partition OID from relcache rather than catalogs.

The commit message of this patch has following line, which no more applies to
patch 0001. May be you want to remove this line or update the patch number.
3. This patch uses the refactored functions created in patch 0001
in this series.
Similarly the credit line refers to patch 0001. That too needs correction.

- * Also, invalidate the parent's relcache, so that the next rebuild will load
- * the new partition's info into its partition descriptor.
+ * Also, invalidate the parent's relcache entry, so that the next rebuild will
+ * load he new partition's info into its partition descriptor.  If there is a
+ * default partition, we must invalidate its relcache entry as well.
Replacing "relcache" with "relcache entry" in the first sentence  may be a good
idea, but is unrelated to this patch. I would leave that change aside and just
add comment about default partition.
+    /*
+     * The partition constraint for the default partition depends on the
+     * partition bounds of every other partition, so we must invalidate the
+     * relcache entry for that partition every time a partition is added or
+     * removed.
+     */
+    defaultPartOid = get_default_partition_oid(RelationGetRelid(parent));
+    if (OidIsValid(defaultPartOid))
+        CacheInvalidateRelcacheByRelid(defaultPartOid);
Again, since we have access to the parent's relcache, we should get the default
partition OID from relcache rather than catalogs.

I haven't gone through the full patch yet, so there may be more
comments here. There is some duplication of code in
check_default_allows_bound() and ValidatePartitionConstraints() to
scan the children of partition being validated. The difference is that
the first one scans the relations in the same function and the second
adds them to work queue. May be we could use
ValidatePartitionConstraints() to scan the relation or add to the
queue based on some input flag may be wqueue argument itself. But I
haven't thought through this completely. Any thoughts?

0004:
Extend default partitioning support to allow addition of new partitions
after
default partition is created/attached. This patch is a merge of patches
0005 and 0006 in V24 series to simplify the review process. The
commit message has more details regarding what all is included.

0005:
This patch introduces code to check if the scanning of default partition
child
can be skipped if it's constraints are proven.

0006:
Documentation.

I will get to these patches in a short while.

--
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

#172Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Ashutosh Bapat (#171)
1 attachment(s)
Re: Adding support for Default partition in partitioning

Hi,

I have merged the default partition for range[1]/messages/by-id/CAOgcT0MWj9Ao=f7C6ZyFce4obNwa_jSWTETMd_-bRfg51zCXbg@mail.gmail.com patches in attached V26
series of patches.

Here are how the patches now look like:

0001:
This patch refactors RelationBuildPartitionDesc(), basically this is patch
0001 of default range partition[1]/messages/by-id/CAOgcT0MWj9Ao=f7C6ZyFce4obNwa_jSWTETMd_-bRfg51zCXbg@mail.gmail.com.

0002:
This patch teaches the partitioning code to handle the NIL returned by
get_qual_for_list().
This is needed because a default partition will not have any constraints in
case
it is the only partition of its parent.

0003:
Support for default partition with the restriction of preventing addition
of any
new partition after default partition. This patch now has support for both
default partition for list and range.

0004:
Extend default partitioning support to allow addition of new partitions
after
default partition is created/attached. This patch is a merge of patches
0005 and 0006 in V24 series to simplify the review process. The
commit message has more details regarding what all is included.

0005:
This patch introduces code to check if the scanning of default partition
child
can be skipped if it's constraints are proven.

0006:
Documentation.

[1]: /messages/by-id/CAOgcT0MWj9Ao=f7C6ZyFce4obNwa_jSWTETMd_-bRfg51zCXbg@mail.gmail.com
/messages/by-id/CAOgcT0MWj9Ao=f7C6ZyFce4obNwa_jSWTETMd_-bRfg51zCXbg@mail.gmail.com

Regards,
Jeevan Ladhe

Attachments:

default_partition_V26.tarapplication/x-tar; name=default_partition_V26.tarDownload
0001-Refactor-RelationBuildPartitionDesc.patch0000664000175000017500000000712013152002657020753 0ustar  jeevanjeevanFrom e0943f88391a9467a92e4015eb44ca768cca83a9 Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Thu, 31 Aug 2017 17:06:40 +0530
Subject: [PATCH 1/6] Refactor RelationBuildPartitionDesc().

This patch simplifies RelationBuildPartitionDesc() to optimise the
way we find out the distinct bounds in case of range partitioned
table.
This would also help to simplify keeping track of ndatums
when we add support for default partition for range partitioned
table.

Beena Emerson, refactoring 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 96a64ce..e2fba73 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -303,21 +303,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,
@@ -332,12 +329,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,
@@ -345,13 +342,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;
@@ -388,34 +383,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.7.4

0002-Fix-assumptions-that-get_qual_from_partbound-cannot.patch0000664000175000017500000000734313152002657024221 0ustar  jeevanjeevanFrom 98410c0d683e7c5cb13edf2fc7b3052a0a54741e Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Thu, 31 Aug 2017 17:09:22 +0530
Subject: [PATCH 2/6] Fix assumptions that get_qual_from_partbound() cannot
 return NIL list.

Current partitioning code assumes that there cannot be any partition
without partition constraints, but in future this assumption might
not hold true.
e.g. if we introduce support for default partition, then default
partition will not have any constraints in case it is the only
partition of its parent.

Jeevan Ladhe.
---
 src/backend/catalog/partition.c  |  8 ++++++--
 src/backend/commands/tablecmds.c | 37 +++++++++++++++++++++----------------
 2 files changed, 27 insertions(+), 18 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index e2fba73..974aa28 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -934,7 +934,8 @@ RelationGetPartitionQual(Relation rel)
  * get_partition_qual_relid
  *
  * Returns an expression tree describing the passed-in relation's partition
- * constraint.
+ * constraint. If there are no partition constraints returns NULL e.g. in case
+ * default partition is the only partition.
  */
 Expr *
 get_partition_qual_relid(Oid relid)
@@ -947,7 +948,10 @@ get_partition_qual_relid(Oid relid)
 	if (rel->rd_rel->relispartition)
 	{
 		and_args = generate_partition_qual(rel);
-		if (list_length(and_args) > 1)
+
+		if (!and_args)
+			result = NULL;
+		else if (list_length(and_args) > 1)
 			result = makeBoolExpr(AND_EXPR, and_args, -1);
 		else
 			result = linitial(and_args);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 0f08245..0fb8238 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13802,24 +13802,29 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	partConstraint = list_concat(get_qual_from_partbound(attachrel, rel,
 														 cmd->bound),
 								 RelationGetPartitionQual(rel));
-	partConstraint = (List *) eval_const_expressions(NULL,
-													 (Node *) partConstraint);
-	partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
-	partConstraint = list_make1(make_ands_explicit(partConstraint));
 
-	/*
-	 * Adjust the generated constraint to match this partition's attribute
-	 * numbers.
-	 */
-	partConstraint = map_partition_varattnos(partConstraint, 1, attachrel,
-											 rel, &found_whole_row);
-	/* There can never be a whole-row reference here */
-	if (found_whole_row)
-		elog(ERROR, "unexpected whole-row reference found in partition key");
+	/* Skip validation if there are no constraints to validate. */
+	if (partConstraint)
+	{
+		partConstraint = (List *) eval_const_expressions(NULL,
+														 (Node *) partConstraint);
+		partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
+		partConstraint = list_make1(make_ands_explicit(partConstraint));
 
-	/* Validate partition constraints against the table being attached. */
-	ValidatePartitionConstraints(wqueue, attachrel, attachrel_children,
-								 partConstraint);
+		/*
+		 * Adjust the generated constraint to match this partition's attribute
+		 * numbers.
+		 */
+		partConstraint = map_partition_varattnos(partConstraint, 1, attachrel,
+												 rel, &found_whole_row);
+		/* There can never be a whole-row reference here */
+		if (found_whole_row)
+			elog(ERROR, "unexpected whole-row reference found in partition key");
+
+		/* Validate partition constraints against the table being attached. */
+		ValidatePartitionConstraints(wqueue, attachrel, attachrel_children,
+									 partConstraint);
+	}
 
 	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachrel));
 
-- 
2.7.4

0003-Implement-default-partition-support.patch0000664000175000017500000020604113152002660021056 0ustar  jeevanjeevanFrom e3ddeb79f5158ffb5019129ea9d656207df3d994 Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Thu, 31 Aug 2017 17:12:59 +0530
Subject: [PATCH 3/6] Implement default partition support

This patch introduces default partition for a list or range
partitioned table. However, in this version of patch no other
partition can be attached to a partitioned table if it has a
default partition. To ease the retrieval of default partition
OID, this patch adds a new column 'partdefid' to catalog
pg_partitioned_table.

Jeevan Ladhe, range partitioning related changes by Beena Emerson.
---
 src/backend/catalog/heap.c                 |  35 ++-
 src/backend/catalog/partition.c            | 412 ++++++++++++++++++++++++-----
 src/backend/commands/tablecmds.c           |  57 +++-
 src/backend/nodes/copyfuncs.c              |   1 +
 src/backend/nodes/equalfuncs.c             |   1 +
 src/backend/nodes/outfuncs.c               |   1 +
 src/backend/nodes/readfuncs.c              |   1 +
 src/backend/parser/gram.y                  |  27 +-
 src/backend/parser/parse_utilcmd.c         |  14 +
 src/backend/utils/adt/ruleutils.c          |  11 +-
 src/bin/psql/describe.c                    |   8 +-
 src/bin/psql/tab-complete.c                |   4 +-
 src/include/catalog/partition.h            |   3 +
 src/include/catalog/pg_partitioned_table.h |  13 +-
 src/include/nodes/parsenodes.h             |   1 +
 src/test/regress/expected/alter_table.out  |  24 ++
 src/test/regress/expected/create_table.out |  14 +
 src/test/regress/expected/insert.out       | 139 +++++++++-
 src/test/regress/expected/plancache.out    |  22 ++
 src/test/regress/expected/sanity_check.out |   4 +
 src/test/regress/expected/update.out       |  24 ++
 src/test/regress/sql/alter_table.sql       |  24 ++
 src/test/regress/sql/create_table.sql      |  15 ++
 src/test/regress/sql/insert.sql            |  71 ++++-
 src/test/regress/sql/plancache.sql         |  19 ++
 src/test/regress/sql/update.sql            |  23 ++
 26 files changed, 862 insertions(+), 106 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 45ee9ac..06c8324 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1759,7 +1759,8 @@ heap_drop_with_catalog(Oid relid)
 {
 	Relation	rel;
 	HeapTuple	tuple;
-	Oid			parentOid = InvalidOid;
+	Oid			parentOid = InvalidOid,
+				defaultPartOid = InvalidOid;
 
 	/*
 	 * To drop a partition safely, we must grab exclusive lock on its parent,
@@ -1775,6 +1776,21 @@ heap_drop_with_catalog(Oid relid)
 	{
 		parentOid = get_partition_parent(relid);
 		LockRelationOid(parentOid, AccessExclusiveLock);
+
+		/*
+		 * The partition constraint of the default partition depends on the
+		 * partition bounds of every other partition. It is possible that
+		 * other backend might be about to execute a query on the default
+		 * partition table and the query relies on previously cached default
+		 * partition constraints, which won't be correct after removal of a
+		 * partition. We must therefore take a table lock strong enough to
+		 * prevent all queries on the default partition from proceeding until
+		 * we commit and send out a shared-cache-inval notice that will make
+		 * them update their index lists.
+		 */
+		defaultPartOid = get_default_partition_oid(parentOid);
+		if (OidIsValid(defaultPartOid))
+			LockRelationOid(defaultPartOid, AccessExclusiveLock);
 	}
 
 	ReleaseSysCache(tuple);
@@ -1826,6 +1842,13 @@ heap_drop_with_catalog(Oid relid)
 		RemovePartitionKeyByRelId(relid);
 
 	/*
+	 * If the relation being dropped is the default partition itself,
+	 * invalidate its entry in pg_partitioned_table.
+	 */
+	if (relid == defaultPartOid)
+		update_default_partition_oid(parentOid, InvalidOid);
+
+	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -1885,6 +1908,15 @@ heap_drop_with_catalog(Oid relid)
 	if (OidIsValid(parentOid))
 	{
 		/*
+		 * The partition constraint for the default partition depends on the
+		 * partition bounds of every other partition, so we must invalidate
+		 * the relcache entry for that partition every time a partition is
+		 * added or removed.
+		 */
+		if (OidIsValid(defaultPartOid))
+			CacheInvalidateRelcacheByRelid(defaultPartOid);
+
+		/*
 		 * Invalidate the parent's relcache so that the partition is no longer
 		 * included in its partition descriptor.
 		 */
@@ -3138,6 +3170,7 @@ StorePartitionKey(Relation rel,
 	values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
 	values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
 	values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
+	values[Anum_pg_partitioned_table_partdefid - 1] = ObjectIdGetDatum(InvalidOid);
 	values[Anum_pg_partitioned_table_partattrs - 1] = PointerGetDatum(partattrs_vec);
 	values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
 	values[Anum_pg_partitioned_table_partcollation - 1] = PointerGetDatum(partcollation_vec);
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 974aa28..5c2f23f 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -27,6 +27,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_inherits_fn.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_type.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
@@ -80,9 +81,12 @@ typedef struct PartitionBoundInfoData
 								 * partitioned table) */
 	int			null_index;		/* Index of the null-accepting partition; -1
 								 * if there isn't one */
+	int			default_index;	/* Index of the default partition; -1 if there
+								 * isn't one */
 } 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
@@ -120,8 +124,10 @@ static void get_range_key_properties(PartitionKey key, int keynum,
 						 ListCell **partexprs_item,
 						 Expr **keyCol,
 						 Const **lower_val, Const **upper_val);
-static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
-static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
+static List *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 *generate_partition_qual(Relation rel);
 
 static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index,
@@ -162,6 +168,7 @@ RelationBuildPartitionDesc(Relation rel)
 	MemoryContext oldcxt;
 
 	int			ndatums = 0;
+	int			default_index = -1;
 
 	/* List partitioning specific */
 	PartitionListValue **all_values = NULL;
@@ -213,6 +220,19 @@ RelationBuildPartitionDesc(Relation rel)
 								&isnull);
 		Assert(!isnull);
 		boundspec = (Node *) stringToNode(TextDatumGetCString(datum));
+
+		/*
+		 * If this is a default partition, pg_partitioned_table must have it's
+		 * OID as value of 'partdefid' for it's parent (i.e. rel) entry.
+		 */
+		if (castNode(PartitionBoundSpec, boundspec)->is_default)
+		{
+			Oid			partdefid;
+
+			partdefid = get_default_partition_oid(RelationGetRelid(rel));
+			Assert(partdefid == inhrelid);
+		}
+
 		boundspecs = lappend(boundspecs, boundspec);
 		partoids = lappend_oid(partoids, inhrelid);
 		ReleaseSysCache(tuple);
@@ -246,6 +266,18 @@ RelationBuildPartitionDesc(Relation rel)
 				if (spec->strategy != PARTITION_STRATEGY_LIST)
 					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 list of non-null
+				 * datums for this partition.
+				 */
+				if (spec->is_default)
+				{
+					default_index = i;
+					i++;
+					continue;
+				}
+
 				foreach(c, spec->listdatums)
 				{
 					Const	   *val = castNode(Const, lfirst(c));
@@ -325,6 +357,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,
@@ -334,10 +377,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);
@@ -420,6 +464,7 @@ RelationBuildPartitionDesc(Relation rel)
 		boundinfo = (PartitionBoundInfoData *)
 			palloc0(sizeof(PartitionBoundInfoData));
 		boundinfo->strategy = key->strategy;
+		boundinfo->default_index = -1;
 		boundinfo->ndatums = ndatums;
 		boundinfo->null_index = -1;
 		boundinfo->datums = (Datum **) palloc0(ndatums * sizeof(Datum *));
@@ -472,6 +517,21 @@ RelationBuildPartitionDesc(Relation rel)
 						boundinfo->null_index = mapping[null_index];
 					}
 
+					/* Assign mapped index for the default partition. */
+					if (default_index != -1)
+					{
+						/*
+						 * The default partition accepts any value not
+						 * specified in the lists of other partitions, hence
+						 * it should not get mapped index while assigning
+						 * those for non-null datums.
+						 */
+						Assert(default_index >= 0 &&
+							   mapping[default_index] == -1);
+						mapping[default_index] = next_index++;
+						boundinfo->default_index = mapping[default_index];
+					}
+
 					/* All partition must now have a valid mapping */
 					Assert(next_index == nparts);
 					break;
@@ -526,6 +586,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;
 				}
@@ -576,6 +644,9 @@ partition_bounds_equal(int partnatts, int16 *parttyplen, bool *parttypbyval,
 	if (b1->null_index != b2->null_index)
 		return false;
 
+	if (b1->default_index != b2->default_index)
+		return false;
+
 	for (i = 0; i < b1->ndatums; i++)
 	{
 		int			j;
@@ -638,6 +709,9 @@ check_new_partition_bound(char *relname, Relation parent,
 	int			with = -1;
 	bool		overlap = false;
 
+	if (spec->is_default)
+		return;
+
 	switch (key->strategy)
 	{
 		case PARTITION_STRATEGY_LIST:
@@ -859,12 +933,12 @@ get_qual_from_partbound(Relation rel, Relation parent,
 	{
 		case PARTITION_STRATEGY_LIST:
 			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
-			my_qual = get_qual_for_list(key, spec);
+			my_qual = get_qual_for_list(parent, spec);
 			break;
 
 		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:
@@ -1266,10 +1340,14 @@ make_partition_op_expr(PartitionKey key, int keynum,
  *
  * Returns an implicit-AND list of expressions to use as a list partition's
  * constraint, given the partition key and bound structures.
+ *
+ * The function returns NIL for a default partition when it's the only
+ * partition since it can not have any partition constraint.
  */
 static List *
-get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
+get_qual_for_list(Relation parent, PartitionBoundSpec *spec)
 {
+	PartitionKey key = RelationGetPartitionKey(parent);
 	List	   *result;
 	Expr	   *keyCol;
 	ArrayExpr  *arr;
@@ -1296,15 +1374,63 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 	else
 		keyCol = (Expr *) copyObject(linitial(key->partexprs));
 
-	/* Create list of Consts for the allowed values, excluding any nulls */
-	foreach(cell, spec->listdatums)
+	/*
+	 * For default list partition, collect datums for all the partitions. The
+	 * default partition constraint should check that the partition key is
+	 * equal to none of those.
+	 */
+	if (spec->is_default)
 	{
-		Const	   *val = castNode(Const, lfirst(cell));
+		int			i;
+		int			ndatums = 0;
+		PartitionDesc pdesc = RelationGetPartitionDesc(parent);
+		PartitionBoundInfo boundinfo = pdesc->boundinfo;
 
-		if (val->constisnull)
-			list_has_null = true;
-		else
-			arrelems = lappend(arrelems, copyObject(val));
+		if (boundinfo)
+		{
+			ndatums = boundinfo->ndatums;
+
+			if (partition_bound_accepts_nulls(boundinfo))
+				list_has_null = true;
+		}
+
+		/*
+		 * If default is the only partition, there need not be any partition
+		 * constraint on it.
+		 */
+		if (ndatums == 0 && !list_has_null)
+			return NIL;
+
+		for (i = 0; i < ndatums; i++)
+		{
+			Const	   *val;
+
+			/* Construct const from datum */
+			val = makeConst(key->parttypid[0],
+							key->parttypmod[0],
+							key->parttypcoll[0],
+							key->parttyplen[0],
+							*boundinfo->datums[i],
+							false,	/* isnull */
+							key->parttypbyval[0]);
+
+			arrelems = lappend(arrelems, val);
+		}
+	}
+	else
+	{
+		/*
+		 * Create list of Consts for the allowed values, excluding any nulls.
+		 */
+		foreach(cell, spec->listdatums)
+		{
+			Const	   *val = castNode(Const, lfirst(cell));
+
+			if (val->constisnull)
+				list_has_null = true;
+			else
+				arrelems = lappend(arrelems, copyObject(val));
+		}
 	}
 
 	if (arrelems)
@@ -1368,6 +1494,25 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 			result = list_make1(nulltest);
 	}
 
+	/*
+	 * In case of the default partition, the constraint is of the form
+	 * "!(result)" i.e. one of the following two forms:
+	 *
+	 * 1. NOT ((keycol IS NULL) OR (keycol = ANY (arr)))
+	 *
+	 * 2. NOT ((keycol IS NOT NULL) AND (keycol = ANY (arr)))
+	 *
+	 * Note that, in general, applying NOT to a constraint expression doesn't
+	 * necessarily invert the set of rows it accepts, because NOT (NULL) is
+	 * NULL.  However, the partition constraints we construct here never
+	 * evaluate to NULL, so applying NOT works as intended.
+	 */
+	if (spec->is_default)
+	{
+		result = list_make1(make_ands_explicit(result));
+		result = list_make1(makeBoolExpr(NOT_EXPR, result, -1));
+	}
+
 	return result;
 }
 
@@ -1424,6 +1569,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
  *
@@ -1462,11 +1654,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,
@@ -1477,10 +1672,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,
@@ -1490,44 +1685,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
@@ -1751,7 +1970,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;
 }
@@ -1924,8 +2145,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;
@@ -1969,27 +2189,10 @@ 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;
-				}
-			}
-		}
-
 		/*
-		 * A null partition key is only acceptable if null-accepting list
-		 * partition exists.
+		 * If this is a null partition key, route it to the null-accepting
+		 * partition. Otherwise, route by searching the array of partition
+		 * bounds.
 		 */
 		cur_index = -1;
 		if (isnull[0] && partition_bound_accepts_nulls(partdesc->boundinfo))
@@ -2027,11 +2230,17 @@ get_partition_for_tuple(PartitionDispatch *pd,
 		}
 
 		/*
-		 * cur_index < 0 means we failed to find a partition of this parent.
-		 * cur_index >= 0 means we either found the leaf partition, or the
-		 * next parent to find a partition of.
+		 * cur_index < 0 means we could not find a non-default partition of
+		 * this parent. cur_index >= 0 means we either found the leaf
+		 * partition, or the next parent to find a partition of.
+		 *
+		 * If we couldn't find a non-default partition check if the default
+		 * partition exists, if it does, get its index.
 		 */
 		if (cur_index < 0)
+			cur_index = partdesc->boundinfo->default_index;
+
+		if (cur_index < 0)
 		{
 			result = -1;
 			*failed_at = parent;
@@ -2083,6 +2292,8 @@ make_one_range_bound(PartitionKey key, int index, List *datums, bool lower)
 	ListCell   *lc;
 	int			i;
 
+	Assert(datums != NIL);
+
 	bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
 	bound->index = index;
 	bound->datums = (Datum *) palloc0(key->partnatts * sizeof(Datum));
@@ -2319,3 +2530,58 @@ partition_bound_bsearch(PartitionKey key, PartitionBoundInfo boundinfo,
 
 	return lo;
 }
+
+/*
+ * get_default_partition_oid
+ *
+ * If the given relation has a default partition return the OID of the default
+ * partition, otherwise return InvalidOid.
+ */
+Oid
+get_default_partition_oid(Oid parentId)
+{
+	HeapTuple	tuple;
+	Oid			defaultPartId = InvalidOid;
+
+	tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(parentId));
+
+	if (HeapTupleIsValid(tuple))
+	{
+		Form_pg_partitioned_table part_table_form;
+
+		part_table_form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+		defaultPartId = part_table_form->partdefid;
+	}
+
+	ReleaseSysCache(tuple);
+	return defaultPartId;
+}
+
+/*
+ * update_default_partition_oid
+ *
+ * Updates the pg_partition_table catalog partdefid field for the given parent
+ * with the given default partition oid.
+ */
+void
+update_default_partition_oid(Oid parentId, Oid defaultPartId)
+{
+	HeapTuple	tuple;
+	Relation	pg_partitioned_table;
+	Form_pg_partitioned_table part_table_form;
+
+	pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCacheCopy1(PARTRELID, ObjectIdGetDatum(parentId));
+
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of relation %u",
+			 parentId);
+
+	part_table_form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+	part_table_form->partdefid = defaultPartId;
+	CatalogTupleUpdate(pg_partitioned_table, &tuple->t_self, tuple);
+
+	heap_freetuple(tuple);
+	heap_close(pg_partitioned_table, RowExclusiveLock);
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 0fb8238..9aad58f 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -774,7 +774,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	{
 		PartitionBoundSpec *bound;
 		ParseState *pstate;
-		Oid			parentId = linitial_oid(inheritOids);
+		Oid			parentId = linitial_oid(inheritOids),
+					defaultPartOid;
 		Relation	parent;
 
 		/* Already have strong enough lock on the parent */
@@ -790,6 +791,17 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 					 errmsg("\"%s\" is not partitioned",
 							RelationGetRelationName(parent))));
 
+		/*
+		 * A table cannot be created as a partition of a parent already having
+		 * a default partition.
+		 */
+		defaultPartOid = get_default_partition_oid(RelationGetRelid(parent));
+		if (OidIsValid(defaultPartOid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("cannot add a new partition to table \"%s\" having a default partition",
+							RelationGetRelationName(parent))));
+
 		/* Tranform the bound values */
 		pstate = make_parsestate(NULL);
 		pstate->p_sourcetext = queryString;
@@ -806,6 +818,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		/* Update the pg_class entry. */
 		StorePartitionBound(rel, parent, bound);
 
+		/* Update the default partition oid */
+		if (bound->is_default)
+			update_default_partition_oid(RelationGetRelid(parent), relationId);
+
 		heap_close(parent, NoLock);
 
 		/*
@@ -13627,6 +13643,15 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	ObjectAddress address;
 	const char *trigger_name;
 	bool		found_whole_row;
+	Oid			defaultPartOid;
+
+	/* A partition cannot be attached if there exists a default partition */
+	defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
+	if (OidIsValid(defaultPartOid))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot attach a new partition to table \"%s\" having a default partition",
+						RelationGetRelationName(rel))));
 
 	attachrel = heap_openrv(cmd->name, AccessExclusiveLock);
 
@@ -13783,6 +13808,11 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	/* OK to create inheritance.  Rest of the checks performed there */
 	CreateInheritance(attachrel, rel);
 
+	/* Update the default partition oid */
+	if (cmd->bound->is_default)
+		update_default_partition_oid(RelationGetRelid(rel),
+									 RelationGetRelid(attachrel));
+
 	/*
 	 * Check that the new partition's bound is valid and does not overlap any
 	 * of existing partitions of the parent - note that it does not return on
@@ -13851,6 +13881,15 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 				new_null[Natts_pg_class],
 				new_repl[Natts_pg_class];
 	ObjectAddress address;
+	Oid			defaultPartOid;
+
+	/*
+	 * We must lock the default partition, for the same reasons explained in
+	 * heap_drop_with_catalog().
+	 */
+	defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
+	if (OidIsValid(defaultPartOid))
+		LockRelationOid(defaultPartOid, AccessExclusiveLock);
 
 	partRel = heap_openrv(name, AccessShareLock);
 
@@ -13882,6 +13921,22 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 	heap_freetuple(newtuple);
 	heap_close(classRel, RowExclusiveLock);
 
+	if (OidIsValid(defaultPartOid))
+	{
+		/*
+		 * If the detach relation is the default partition itself, invalidate
+		 * its entry in pg_partitioned_table.
+		 */
+		if (RelationGetRelid(partRel) == defaultPartOid)
+			update_default_partition_oid(RelationGetRelid(rel), InvalidOid);
+
+		/*
+		 * We must invalidate default partition's relcache, for the same
+		 * reasons explained in heap_drop_with_catalog().
+		 */
+		CacheInvalidateRelcacheByRelid(defaultPartOid);
+	}
+
 	/*
 	 * Invalidate the parent's relcache so that the partition is no longer
 	 * included in its partition descriptor.
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index f9ddf4e..822e7f8 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4449,6 +4449,7 @@ _copyPartitionBoundSpec(const PartitionBoundSpec *from)
 	PartitionBoundSpec *newnode = makeNode(PartitionBoundSpec);
 
 	COPY_SCALAR_FIELD(strategy);
+	COPY_SCALAR_FIELD(is_default);
 	COPY_NODE_FIELD(listdatums);
 	COPY_NODE_FIELD(lowerdatums);
 	COPY_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8d92c03..64ecfff 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2838,6 +2838,7 @@ static bool
 _equalPartitionBoundSpec(const PartitionBoundSpec *a, const PartitionBoundSpec *b)
 {
 	COMPARE_SCALAR_FIELD(strategy);
+	COMPARE_SCALAR_FIELD(is_default);
 	COMPARE_NODE_FIELD(listdatums);
 	COMPARE_NODE_FIELD(lowerdatums);
 	COMPARE_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 9ee3e23..b83d919 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3573,6 +3573,7 @@ _outPartitionBoundSpec(StringInfo str, const PartitionBoundSpec *node)
 	WRITE_NODE_TYPE("PARTITIONBOUNDSPEC");
 
 	WRITE_CHAR_FIELD(strategy);
+	WRITE_BOOL_FIELD(is_default);
 	WRITE_NODE_FIELD(listdatums);
 	WRITE_NODE_FIELD(lowerdatums);
 	WRITE_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 67b9e19..fbf8330 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2390,6 +2390,7 @@ _readPartitionBoundSpec(void)
 	READ_LOCALS(PartitionBoundSpec);
 
 	READ_CHAR_FIELD(strategy);
+	READ_BOOL_FIELD(is_default);
 	READ_NODE_FIELD(listdatums);
 	READ_NODE_FIELD(lowerdatums);
 	READ_NODE_FIELD(upperdatums);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7d0de99..1db35e2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -575,7 +575,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>			part_strategy
 %type <partelem>	part_elem
 %type <list>		part_params
-%type <partboundspec> ForValues
+%type <partboundspec> PartitionBoundSpec
 %type <node>		partbound_datum PartitionRangeDatum
 %type <list>		partbound_datum_list range_datum_list
 
@@ -1980,7 +1980,7 @@ alter_table_cmds:
 
 partition_cmd:
 			/* ALTER TABLE <name> ATTACH PARTITION <table_name> FOR VALUES */
-			ATTACH PARTITION qualified_name ForValues
+			ATTACH PARTITION qualified_name PartitionBoundSpec
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
 					PartitionCmd *cmd = makeNode(PartitionCmd);
@@ -2619,13 +2619,14 @@ alter_identity_column_option:
 				}
 		;
 
-ForValues:
+PartitionBoundSpec:
 			/* a LIST partition */
 			FOR VALUES IN_P '(' partbound_datum_list ')'
 				{
 					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
 
 					n->strategy = PARTITION_STRATEGY_LIST;
+					n->is_default = false;
 					n->listdatums = $5;
 					n->location = @3;
 
@@ -2638,12 +2639,24 @@ ForValues:
 					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
 
 					n->strategy = PARTITION_STRATEGY_RANGE;
+					n->is_default = false;
 					n->lowerdatums = $5;
 					n->upperdatums = $9;
 					n->location = @3;
 
 					$$ = n;
 				}
+
+			/* a DEFAULT partition */
+			| DEFAULT
+				{
+					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+					n->is_default = true;
+					n->location = @1;
+
+					$$ = n;
+				}
 		;
 
 partbound_datum:
@@ -3114,7 +3127,7 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
-			OptTypedTableElementList ForValues OptPartitionSpec OptWith
+			OptTypedTableElementList PartitionBoundSpec OptPartitionSpec OptWith
 			OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
@@ -3133,7 +3146,7 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
-			qualified_name OptTypedTableElementList ForValues OptPartitionSpec
+			qualified_name OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
 			OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
@@ -4848,7 +4861,7 @@ CreateForeignTableStmt:
 					$$ = (Node *) n;
 				}
 		| CREATE FOREIGN TABLE qualified_name
-			PARTITION OF qualified_name OptTypedTableElementList ForValues
+			PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
 			SERVER name create_generic_options
 				{
 					CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
@@ -4869,7 +4882,7 @@ CreateForeignTableStmt:
 					$$ = (Node *) n;
 				}
 		| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
-			PARTITION OF qualified_name OptTypedTableElementList ForValues
+			PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
 			SERVER name create_generic_options
 				{
 					CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 2058679..e285964 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -60,6 +61,7 @@
 #include "parser/parse_utilcmd.h"
 #include "parser/parser.h"
 #include "rewrite/rewriteManip.h"
+#include "storage/lmgr.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
@@ -3307,6 +3309,18 @@ transformPartitionBound(ParseState *pstate, Relation parent,
 	/* Avoid scribbling on input */
 	result_spec = copyObject(spec);
 
+	if (spec->is_default)
+	{
+		/*
+		 * In case of the default partition, parser had no way to identify the
+		 * partition strategy. Assign the parent's strategy to the default
+		 * partition bound spec.
+		 */
+		result_spec->strategy = strategy;
+
+		return result_spec;
+	}
+
 	if (strategy == PARTITION_STRATEGY_LIST)
 	{
 		ListCell   *cell;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 43646d2..7f7622e 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8696,10 +8696,17 @@ get_rule_expr(Node *node, deparse_context *context,
 		case T_PartitionBoundSpec:
 			{
 				PartitionBoundSpec *spec = (PartitionBoundSpec *) node;
+				char		strategy = spec->strategy;
 				ListCell   *cell;
 				char	   *sep;
 
-				switch (spec->strategy)
+				if (spec->is_default)
+				{
+					appendStringInfoString(buf, "DEFAULT");
+					break;
+				}
+
+				switch (strategy)
 				{
 					case PARTITION_STRATEGY_LIST:
 						Assert(spec->listdatums != NIL);
@@ -8731,7 +8738,7 @@ get_rule_expr(Node *node, deparse_context *context,
 
 					default:
 						elog(ERROR, "unrecognized partition strategy: %d",
-							 (int) spec->strategy);
+							 (int) strategy);
 						break;
 				}
 			}
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index f6049cc..4d42f8d 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1900,8 +1900,12 @@ describeOneTableDetails(const char *schemaname,
 
 			if (partconstraintdef)
 			{
-				printfPQExpBuffer(&tmpbuf, _("Partition constraint: %s"),
-								  partconstraintdef);
+				/* If there isn't any constraint, show that explicitly */
+				if (partconstraintdef[0] == '\0')
+					printfPQExpBuffer(&tmpbuf, _("No partition constraint"));
+				else
+					printfPQExpBuffer(&tmpbuf, _("Partition constraint: %s"),
+									  partconstraintdef);
 				printTableAddFooter(&cont, tmpbuf.data);
 			}
 
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 1583cfa..fb1091a 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2050,7 +2050,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("ATTACH", "PARTITION", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 	else if (TailMatches2("FOR", "VALUES"))
 		COMPLETE_WITH_LIST2("FROM (", "IN (");
 
@@ -2489,7 +2489,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("PARTITION", "OF", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 
 /* CREATE TABLESPACE */
 	else if (Matches3("CREATE", "TABLESPACE", MatchAny))
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 2283c67..1dd70f2 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -99,4 +99,7 @@ extern int get_partition_for_tuple(PartitionDispatch *pd,
 						EState *estate,
 						PartitionDispatchData **failed_at,
 						TupleTableSlot **failed_slot);
+extern Oid	get_default_partition_oid(Oid parentId);
+extern void update_default_partition_oid(Oid parentId, Oid defaultPartId);
+
 #endif							/* PARTITION_H */
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
index 38d64d6..525e541 100644
--- a/src/include/catalog/pg_partitioned_table.h
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -32,6 +32,8 @@ CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
 	Oid			partrelid;		/* partitioned table oid */
 	char		partstrat;		/* partitioning strategy */
 	int16		partnatts;		/* number of partition key columns */
+	Oid			partdefid;		/* default partition oid; InvalidOid if there
+								 * isn't one */
 
 	/*
 	 * variable-length fields start here, but we allow direct access to
@@ -62,13 +64,14 @@ typedef FormData_pg_partitioned_table *Form_pg_partitioned_table;
  *		compiler constants for pg_partitioned_table
  * ----------------
  */
-#define Natts_pg_partitioned_table				7
+#define Natts_pg_partitioned_table				8
 #define Anum_pg_partitioned_table_partrelid		1
 #define Anum_pg_partitioned_table_partstrat		2
 #define Anum_pg_partitioned_table_partnatts		3
-#define Anum_pg_partitioned_table_partattrs		4
-#define Anum_pg_partitioned_table_partclass		5
-#define Anum_pg_partitioned_table_partcollation 6
-#define Anum_pg_partitioned_table_partexprs		7
+#define Anum_pg_partitioned_table_partdefid		4
+#define Anum_pg_partitioned_table_partattrs		5
+#define Anum_pg_partitioned_table_partclass		6
+#define Anum_pg_partitioned_table_partcollation 7
+#define Anum_pg_partitioned_table_partexprs		8
 
 #endif							/* PG_PARTITIONED_TABLE_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 5f2a4a7..6e05b79 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -797,6 +797,7 @@ typedef struct PartitionBoundSpec
 	NodeTag		type;
 
 	char		strategy;		/* see PARTITION_STRATEGY codes above */
+	bool		is_default;		/* is it a default partition bound? */
 
 	/* Partitioning info for LIST strategy: */
 	List	   *listdatums;		/* List of Consts (or A_Consts in raw tree) */
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index ed03cb9..053e472 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3273,6 +3273,14 @@ SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::reg
 CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
 ERROR:  partition "fail_part" would overlap partition "part_1"
+-- check that an existing table can be attached as a default partition
+CREATE TABLE def_part (LIKE list_parted INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION def_part DEFAULT;
+-- check attaching default partition fails if a default partition already
+-- exists
+CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT;
+ERROR:  cannot attach a new partition to table "list_parted" having a default partition
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
 	a int,
@@ -3286,6 +3294,15 @@ ERROR:  partition constraint is violated by some row
 -- should be ok after deleting the bad row
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+-- check partition cannot be attached if default exists
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
+INSERT INTO list_parted2_def VALUES (11, 'z');
+CREATE TABLE part_3 (LIKE list_parted2);
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+ERROR:  cannot attach a new partition to table "list_parted2" having a default partition
+-- should be ok after detaching a default partition
+ALTER TABLE list_parted2 DETACH PARTITION list_parted2_def;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
 CREATE TABLE part_3_4 (
@@ -3326,6 +3343,12 @@ 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;
+-- New partition cannot be attached if a default partition exists
+CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS);
+ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT;
+ERROR:  cannot attach a new partition to table "range_parted" having a default partition
 -- check that leaf partitions are scanned when attaching a partitioned
 -- table
 CREATE TABLE part_5 (
@@ -3514,6 +3537,7 @@ ALTER TABLE list_parted2 ALTER COLUMN b TYPE text;
 ERROR:  cannot alter type of column named in partition key
 -- cleanup
 DROP TABLE list_parted, list_parted2, range_parted;
+DROP TABLE fail_def_part;
 -- more tests for certain multi-level partitioning scenarios
 create table p (a int, b int) partition by range (a, b);
 create table p1 (b int, a int not null) partition by range (b);
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index babda89..4f79612 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -467,6 +467,13 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
 ERROR:  invalid bound specification for a list partition
 LINE 1: ...BLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) T...
                                                              ^
+-- check default partition cannot be created more than once
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
+ERROR:  cannot add a new partition to table "list_parted" having a default partition
+-- check that no partition can be created after default partition
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (3);
+ERROR:  cannot add a new partition to table "list_parted" having a default partition
 -- specified literal can't be cast to the partition column data type
 CREATE TABLE bools (
 	a bool
@@ -585,6 +592,11 @@ 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;
+-- New partition cannot be created if a default partition exists
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90);
+ERROR:  cannot add a new partition to table "range_parted2" having a default partition
 -- now check for multi-column range partition key
 CREATE TABLE range_parted3 (
 	a int,
@@ -603,6 +615,8 @@ ERROR:  partition "fail_part" would overlap partition "part12"
 -- more specific ranges
 CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, minvalue) TO (1, maxvalue);
 ERROR:  partition "fail_part" would overlap partition "part10"
+-- check default partition can be added to multi-column range partitioned table
+CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
 -- check schema propagation from parent
 CREATE TABLE parted (
 	a text,
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index a2d9469..a0ef3ff 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -219,17 +219,66 @@ insert into part_null values (null, 0);
 create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b);
 create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
 create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
+-- test default partition
+create table part_default partition of list_parted default;
+-- Negative test: a row, which would fit in other partition, does not fit
+-- default partition, even when inserted directly
+insert into part_default values ('aa', 2);
+ERROR:  new row for relation "part_default" violates partition constraint
+DETAIL:  Failing row contains (aa, 2).
+insert into part_default values (null, 2);
+ERROR:  new row for relation "part_default" violates partition constraint
+DETAIL:  Failing row contains (null, 2).
+-- ok
+insert into part_default values ('Zz', 2);
+-- test if default partition works as expected for multi-level partitioned
+-- table as well as when default partition itself is further partitioned
+drop table part_default;
+create table part_xx_yy partition of list_parted for values in ('xx', 'yy') partition by list (a);
+create table part_xx_yy_p1 partition of part_xx_yy for values in ('xx');
+create table part_xx_yy_defpart partition of part_xx_yy default;
+create table part_default partition of list_parted default partition by range(b);
+create table part_default_p1 partition of part_default for values from (20) to (30);
+create table part_default_p2 partition of part_default for values from (30) to (40);
 -- fail
 insert into part_ee_ff1 values ('EE', 11);
 ERROR:  new row for relation "part_ee_ff1" violates partition constraint
 DETAIL:  Failing row contains (EE, 11).
+insert into part_default_p2 values ('gg', 43);
+ERROR:  new row for relation "part_default_p2" violates partition constraint
+DETAIL:  Failing row contains (gg, 43).
 -- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
 insert into part_ee_ff1 values ('cc', 1);
 ERROR:  new row for relation "part_ee_ff1" violates partition constraint
 DETAIL:  Failing row contains (cc, 1).
+insert into part_default values ('gg', 43);
+ERROR:  no partition of relation "part_default" found for row
+DETAIL:  Partition key of the failing row contains (b) = (43).
 -- ok
 insert into part_ee_ff1 values ('ff', 1);
 insert into part_ee_ff2 values ('ff', 11);
+insert into part_default_p1 values ('cd', 25);
+insert into part_default_p2 values ('de', 35);
+insert into list_parted values ('ab', 21);
+insert into list_parted values ('xx', 1);
+insert into list_parted values ('yy', 2);
+select tableoid::regclass, * from list_parted;
+      tableoid      | a  | b  
+--------------------+----+----
+ part_cc_dd         | cC |  1
+ part_null          |    |  0
+ part_ee_ff1        | ff |  1
+ part_ee_ff2        | ff | 11
+ part_xx_yy_p1      | xx |  1
+ part_xx_yy_defpart | yy |  2
+ part_default_p1    | cd | 25
+ part_default_p1    | ab | 21
+ part_default_p2    | de | 35
+(9 rows)
+
+-- drop default partition, as we need to add some more partitions to test tuple
+-- routing
+drop table part_default;
 -- Check tuple routing for partitioned tables
 -- fail
 insert into range_parted values ('a', 0);
@@ -249,6 +298,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  
 ----------+---+----
@@ -258,7 +319,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);
@@ -274,17 +340,19 @@ DETAIL:  Partition key of the failing row contains (b) = (0).
 insert into list_parted values ('EE', 1);
 insert into part_ee_ff values ('EE', 10);
 select tableoid::regclass, * from list_parted;
-  tableoid   | a  | b  
--------------+----+----
- part_aa_bb  | aA |   
- part_cc_dd  | cC |  1
- part_null   |    |  0
- part_null   |    |  1
- part_ee_ff1 | ff |  1
- part_ee_ff1 | EE |  1
- part_ee_ff2 | ff | 11
- part_ee_ff2 | EE | 10
-(8 rows)
+      tableoid      | a  | b  
+--------------------+----+----
+ part_aa_bb         | aA |   
+ part_cc_dd         | cC |  1
+ part_null          |    |  0
+ part_null          |    |  1
+ part_ee_ff1        | ff |  1
+ part_ee_ff1        | EE |  1
+ part_ee_ff2        | ff | 11
+ part_ee_ff2        | EE | 10
+ part_xx_yy_p1      | xx |  1
+ part_xx_yy_defpart | yy |  2
+(10 rows)
 
 -- some more tests to exercise tuple-routing with multi-level partitioning
 create table part_gg partition of list_parted for values in ('gg') partition by range (b);
@@ -316,6 +384,23 @@ select tableoid::regclass::text, a, min(b) as min_b, max(b) as max_b from list_p
 
 -- cleanup
 drop table range_parted, list_parted;
+-- test adding default partition as first partition accepts any value including
+-- null
+create table list_parted (a int) partition by list (a);
+create table part_default partition of list_parted default;
+insert into part_default values (null);
+insert into part_default values (1);
+insert into part_default values (-1);
+select tableoid::regclass, a from list_parted;
+   tableoid   | a  
+--------------+----
+ part_default |   
+ part_default |  1
+ part_default | -1
+(3 rows)
+
+-- cleanup
+drop table list_parted;
 -- more tests for certain multi-level partitioning scenarios
 create table mlparted (a int, b int) partition by range (a, b);
 create table mlparted1 (b int not null, a int not null) partition by range ((b+0));
@@ -425,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/plancache.out b/src/test/regress/expected/plancache.out
index 3f3db33..fb90357 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -252,3 +252,25 @@ NOTICE:  3
  
 (1 row)
 
+-- Check that addition or removal of any partition is correctly dealt with by
+-- default partition table when it is being used in prepared statement.
+create table list_parted (a int) partition by list(a);
+create table list_part_null partition of list_parted for values in (null);
+create table list_part_1 partition of list_parted for values in (1);
+create table list_part_def partition of list_parted default;
+prepare pstmt_def_insert (int) as insert into list_part_def values($1);
+-- should fail
+execute pstmt_def_insert(null);
+ERROR:  new row for relation "list_part_def" violates partition constraint
+DETAIL:  Failing row contains (null).
+execute pstmt_def_insert(1);
+ERROR:  new row for relation "list_part_def" violates partition constraint
+DETAIL:  Failing row contains (1).
+alter table list_parted detach partition list_part_null;
+-- should be ok
+execute pstmt_def_insert(null);
+drop table list_part_1;
+-- should be ok
+execute pstmt_def_insert(1);
+drop table list_parted, list_part_null;
+deallocate pstmt_def_insert;
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 9366f04..cfb6aa8 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -218,5 +218,29 @@ 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
+) partition by list (a);
+create table list_part1  partition of list_parted for values in ('a', 'b');
+create table list_default partition of list_parted default;
+insert into list_part1 values ('a', 1);
+insert into list_default values ('d', 10);
+-- fail
+update list_default set a = 'a' where a = 'd';
+ERROR:  new row for relation "list_default" violates partition constraint
+DETAIL:  Failing row contains (a, 10).
+-- ok
+update list_default set a = 'x' where a = 'd';
 -- cleanup
 drop table range_parted;
+drop table list_parted;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 9a20dd1..b6aa9e3 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2095,6 +2095,13 @@ SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::reg
 -- check that the new partition won't overlap with an existing partition
 CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+-- check that an existing table can be attached as a default partition
+CREATE TABLE def_part (LIKE list_parted INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION def_part DEFAULT;
+-- check attaching default partition fails if a default partition already
+-- exists
+CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT;
 
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
@@ -2111,6 +2118,15 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 
+-- check partition cannot be attached if default exists
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
+INSERT INTO list_parted2_def VALUES (11, 'z');
+CREATE TABLE part_3 (LIKE list_parted2);
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+-- should be ok after detaching a default partition
+ALTER TABLE list_parted2 DETACH PARTITION list_parted2_def;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
 CREATE TABLE part_3_4 (
@@ -2156,6 +2172,13 @@ 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;
+
+-- New partition cannot be attached if a default partition exists
+CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS);
+ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT;
+
 -- check that leaf partitions are scanned when attaching a partitioned
 -- table
 CREATE TABLE part_5 (
@@ -2311,6 +2334,7 @@ ALTER TABLE list_parted2 ALTER COLUMN b TYPE text;
 
 -- cleanup
 DROP TABLE list_parted, list_parted2, range_parted;
+DROP TABLE fail_def_part;
 
 -- more tests for certain multi-level partitioning scenarios
 create table p (a int, b int) partition by range (a, b);
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 1c0ce927..24f005e 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -447,6 +447,12 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
 -- trying to specify range for list partitioned table
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
 
+-- check default partition cannot be created more than once
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
+-- check that no partition can be created after default partition
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (3);
+
 -- specified literal can't be cast to the partition column data type
 CREATE TABLE bools (
 	a bool
@@ -546,6 +552,12 @@ 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;
+
+-- New partition cannot be created if a default partition exists
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90);
+
 -- now check for multi-column range partition key
 CREATE TABLE range_parted3 (
 	a int,
@@ -565,6 +577,9 @@ CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1,
 -- more specific ranges
 CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, minvalue) TO (1, maxvalue);
 
+-- check default partition can be added to multi-column range partitioned table
+CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
+
 -- check schema propagation from parent
 
 CREATE TABLE parted (
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 6f17872..b7d11d5 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -132,13 +132,42 @@ create table part_ee_ff partition of list_parted for values in ('ee', 'ff') part
 create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
 create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
 
+-- test default partition
+create table part_default partition of list_parted default;
+-- Negative test: a row, which would fit in other partition, does not fit
+-- default partition, even when inserted directly
+insert into part_default values ('aa', 2);
+insert into part_default values (null, 2);
+-- ok
+insert into part_default values ('Zz', 2);
+-- test if default partition works as expected for multi-level partitioned
+-- table as well as when default partition itself is further partitioned
+drop table part_default;
+create table part_xx_yy partition of list_parted for values in ('xx', 'yy') partition by list (a);
+create table part_xx_yy_p1 partition of part_xx_yy for values in ('xx');
+create table part_xx_yy_defpart partition of part_xx_yy default;
+create table part_default partition of list_parted default partition by range(b);
+create table part_default_p1 partition of part_default for values from (20) to (30);
+create table part_default_p2 partition of part_default for values from (30) to (40);
+
 -- fail
 insert into part_ee_ff1 values ('EE', 11);
+insert into part_default_p2 values ('gg', 43);
 -- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
 insert into part_ee_ff1 values ('cc', 1);
+insert into part_default values ('gg', 43);
 -- ok
 insert into part_ee_ff1 values ('ff', 1);
 insert into part_ee_ff2 values ('ff', 11);
+insert into part_default_p1 values ('cd', 25);
+insert into part_default_p2 values ('de', 35);
+insert into list_parted values ('ab', 21);
+insert into list_parted values ('xx', 1);
+insert into list_parted values ('yy', 2);
+select tableoid::regclass, * from list_parted;
+-- drop default partition, as we need to add some more partitions to test tuple
+-- routing
+drop table part_default;
 
 -- Check tuple routing for partitioned tables
 
@@ -154,8 +183,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');
@@ -188,6 +228,17 @@ select tableoid::regclass::text, a, min(b) as min_b, max(b) as max_b from list_p
 -- cleanup
 drop table range_parted, list_parted;
 
+-- test adding default partition as first partition accepts any value including
+-- null
+create table list_parted (a int) partition by list (a);
+create table part_default partition of list_parted default;
+insert into part_default values (null);
+insert into part_default values (1);
+insert into part_default values (-1);
+select tableoid::regclass, a from list_parted;
+-- cleanup
+drop table list_parted;
+
 -- more tests for certain multi-level partitioning scenarios
 create table mlparted (a int, b int) partition by range (a, b);
 create table mlparted1 (b int not null, a int not null) partition by range ((b+0));
@@ -274,6 +325,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/plancache.sql b/src/test/regress/sql/plancache.sql
index bc20861..a8e357c 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -156,3 +156,22 @@ end$$ language plpgsql;
 
 select cachebug();
 select cachebug();
+
+-- Check that addition or removal of any partition is correctly dealt with by
+-- default partition table when it is being used in prepared statement.
+create table list_parted (a int) partition by list(a);
+create table list_part_null partition of list_parted for values in (null);
+create table list_part_1 partition of list_parted for values in (1);
+create table list_part_def partition of list_parted default;
+prepare pstmt_def_insert (int) as insert into list_part_def values($1);
+-- should fail
+execute pstmt_def_insert(null);
+execute pstmt_def_insert(1);
+alter table list_parted detach partition list_part_null;
+-- should be ok
+execute pstmt_def_insert(null);
+drop table list_part_1;
+-- should be ok
+execute pstmt_def_insert(1);
+drop table list_parted, list_part_null;
+deallocate pstmt_def_insert;
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 6637119..1ce7a94 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -125,5 +125,28 @@ 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
+) partition by list (a);
+create table list_part1  partition of list_parted for values in ('a', 'b');
+create table list_default partition of list_parted default;
+insert into list_part1 values ('a', 1);
+insert into list_default values ('d', 10);
+
+-- fail
+update list_default set a = 'a' where a = 'd';
+-- ok
+update list_default set a = 'x' where a = 'd';
+
 -- cleanup
 drop table range_parted;
+drop table list_parted;
-- 
2.7.4

0004-Default-partition-extended-for-create-and-attach.patch0000664000175000017500000012416613152002660023211 0ustar  jeevanjeevanFrom 6fa7435f27f948faef715b679db645f141766719 Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Thu, 31 Aug 2017 17:38:23 +0530
Subject: [PATCH 4/6] Default partition extended for create and attach

1. This patch extends the previous patch in this series to allow a
new partition to be created or attached even when a default
partition exists.
2. Also, extends the regression tests to test this functionality.
in this series.
3. While adding a new partition we need to make sure that default
partition does not contain any rows that would fit in newly added
partition. So, in this patch adds a code to negate the partition
constraints of new partition so as these constraints form the part
of would be default partition constraints. Then the default
partition is scanned to check if there exist a row that does not
hold these negated partition constraints, if there exists such a
row error out.
4. While doing 3, to optimize the validation scan a check is done,
if the existing constraints on the default partition imply that it
will not contain any row that would belong to the new partition,
then the validation scan is skipped.

Jeevan Ladhe, some refactoring by Ashutosh bapat.
---
 src/backend/catalog/heap.c                 |  33 ++---
 src/backend/catalog/partition.c            | 187 ++++++++++++++++++++++++++++-
 src/backend/commands/tablecmds.c           | 112 +++++++++++++----
 src/include/catalog/partition.h            |   3 +
 src/include/commands/tablecmds.h           |   4 +
 src/test/regress/expected/alter_table.out  |  39 ++++--
 src/test/regress/expected/create_table.out |  22 ++--
 src/test/regress/expected/insert.out       |   8 +-
 src/test/regress/expected/plancache.out    |   4 +
 src/test/regress/sql/alter_table.sql       |  31 ++++-
 src/test/regress/sql/create_table.sql      |  17 ++-
 src/test/regress/sql/insert.sql            |   3 -
 src/test/regress/sql/plancache.sql         |   2 +
 13 files changed, 388 insertions(+), 77 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 06c8324..159e6ca 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1778,15 +1778,8 @@ heap_drop_with_catalog(Oid relid)
 		LockRelationOid(parentOid, AccessExclusiveLock);
 
 		/*
-		 * The partition constraint of the default partition depends on the
-		 * partition bounds of every other partition. It is possible that
-		 * other backend might be about to execute a query on the default
-		 * partition table and the query relies on previously cached default
-		 * partition constraints, which won't be correct after removal of a
-		 * partition. We must therefore take a table lock strong enough to
-		 * prevent all queries on the default partition from proceeding until
-		 * we commit and send out a shared-cache-inval notice that will make
-		 * them update their index lists.
+		 * We must also lock the default partition, for the same reasons
+		 * explained in DefineRelation().
 		 */
 		defaultPartOid = get_default_partition_oid(parentOid);
 		if (OidIsValid(defaultPartOid))
@@ -1908,10 +1901,8 @@ heap_drop_with_catalog(Oid relid)
 	if (OidIsValid(parentOid))
 	{
 		/*
-		 * The partition constraint for the default partition depends on the
-		 * partition bounds of every other partition, so we must invalidate
-		 * the relcache entry for that partition every time a partition is
-		 * added or removed.
+		 * We must invalidate default partition's relcache, for the same
+		 * reasons explained in StorePartitionBound().
 		 */
 		if (OidIsValid(defaultPartOid))
 			CacheInvalidateRelcacheByRelid(defaultPartOid);
@@ -3255,8 +3246,9 @@ RemovePartitionKeyByRelId(Oid relid)
  *		Update pg_class tuple of rel to store the partition bound and set
  *		relispartition to true
  *
- * Also, invalidate the parent's relcache, so that the next rebuild will load
- * the new partition's info into its partition descriptor.
+ * Also, invalidate the parent's relcache entry, so that the next rebuild will
+ * load he new partition's info into its partition descriptor.  If there is a
+ * default partition, we must invalidate its relcache entry as well.
  */
 void
 StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
@@ -3267,6 +3259,7 @@ StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
 	Datum		new_val[Natts_pg_class];
 	bool		new_null[Natts_pg_class],
 				new_repl[Natts_pg_class];
+	Oid			defaultPartOid;
 
 	/* Update pg_class tuple */
 	classRel = heap_open(RelationRelationId, RowExclusiveLock);
@@ -3304,5 +3297,15 @@ StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
 	heap_freetuple(newtuple);
 	heap_close(classRel, RowExclusiveLock);
 
+	/*
+	 * The partition constraint for the default partition depends on the
+	 * partition bounds of every other partition, so we must invalidate the
+	 * relcache entry for that partition every time a partition is added or
+	 * removed.
+	 */
+	defaultPartOid = get_default_partition_oid(RelationGetRelid(parent));
+	if (OidIsValid(defaultPartOid))
+		CacheInvalidateRelcacheByRelid(defaultPartOid);
+
 	CacheInvalidateRelcache(parent);
 }
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 5c2f23f..58bb509 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -29,6 +29,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_type.h"
+#include "commands/tablecmds.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -36,6 +37,7 @@
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planmain.h"
+#include "optimizer/prep.h"
 #include "optimizer/var.h"
 #include "rewrite/rewriteManip.h"
 #include "storage/lmgr.h"
@@ -705,12 +707,23 @@ check_new_partition_bound(char *relname, Relation parent,
 {
 	PartitionKey key = RelationGetPartitionKey(parent);
 	PartitionDesc partdesc = RelationGetPartitionDesc(parent);
+	PartitionBoundInfo boundinfo = partdesc->boundinfo;
 	ParseState *pstate = make_parsestate(NULL);
 	int			with = -1;
 	bool		overlap = false;
 
 	if (spec->is_default)
-		return;
+	{
+		if (boundinfo == NULL || !partition_bound_has_default(boundinfo))
+			return;
+
+		/* Default partition already exists, error out. */
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("partition \"%s\" conflicts with existing default partition \"%s\"",
+						relname, get_rel_name(partdesc->oids[boundinfo->default_index])),
+				 parser_errposition(pstate, spec->location)));
+	}
 
 	switch (key->strategy)
 	{
@@ -720,13 +733,13 @@ check_new_partition_bound(char *relname, Relation parent,
 
 				if (partdesc->nparts > 0)
 				{
-					PartitionBoundInfo boundinfo = partdesc->boundinfo;
 					ListCell   *cell;
 
 					Assert(boundinfo &&
 						   boundinfo->strategy == PARTITION_STRATEGY_LIST &&
 						   (boundinfo->ndatums > 0 ||
-							partition_bound_accepts_nulls(boundinfo)));
+							partition_bound_accepts_nulls(boundinfo) ||
+							partition_bound_has_default(boundinfo)));
 
 					foreach(cell, spec->listdatums)
 					{
@@ -791,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
@@ -870,6 +885,139 @@ check_new_partition_bound(char *relname, Relation parent,
 }
 
 /*
+ * check_default_allows_bound
+ *
+ * This function checks if there exists a row in the default partition that
+ * fits in the new partition being added and throws an error if it finds one.
+ */
+void
+check_default_allows_bound(Relation parent, Relation default_rel,
+						   PartitionBoundSpec *new_spec)
+{
+	List	   *new_part_constraints;
+	List	   *def_part_constraints;
+	List	   *all_parts;
+	ListCell   *lc;
+
+	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);
+
+	/*
+	 * If the existing constraints on the default partition imply that it will
+	 * not contain any row that would belong to the new partition, we can
+	 * avoid scanning the default partition.
+	 */
+	if (PartConstraintImpliedByRelConstraint(default_rel, def_part_constraints))
+	{
+		ereport(INFO,
+				(errmsg("partition constraint for table \"%s\" is implied by existing constraints",
+						RelationGetRelationName(default_rel))));
+		return;
+	}
+
+	/*
+	 * Bad luck, scan the default partition and its subpartitions, and check
+	 * if any of the row does not satisfy the partition constraints that are
+	 * going to be imposed as additional constraints on default partition by
+	 * addition of the new partition.
+	 */
+	if (default_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		all_parts = find_all_inheritors(RelationGetRelid(default_rel),
+										AccessExclusiveLock, NULL);
+	else
+		all_parts = list_make1_oid(RelationGetRelid(default_rel));
+
+	foreach(lc, all_parts)
+	{
+		Oid			part_relid = lfirst_oid(lc);
+		Relation	part_rel;
+		Expr	   *constr;
+		Expr	   *partition_constraint;
+		EState	   *estate;
+		HeapTuple	tuple;
+		ExprState  *partqualstate = NULL;
+		Snapshot	snapshot;
+		TupleDesc	tupdesc;
+		ExprContext *econtext;
+		HeapScanDesc scan;
+		MemoryContext oldCxt;
+		TupleTableSlot *tupslot;
+
+		/* Lock already taken above. */
+		if (part_relid != RelationGetRelid(default_rel))
+			part_rel = heap_open(part_relid, NoLock);
+		else
+			part_rel = default_rel;
+
+		/*
+		 * Only RELKIND_RELATION relations (i.e. leaf partitions) need to be
+		 * scanned.
+		 */
+		if (part_rel->rd_rel->relkind != RELKIND_RELATION)
+		{
+			if (part_rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+				ereport(WARNING,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("skipped scanning foreign table \"%s\" which is a partition of default partition \"%s\"",
+								RelationGetRelationName(part_rel),
+								RelationGetRelationName(default_rel))));
+
+			if (RelationGetRelid(default_rel) != RelationGetRelid(part_rel))
+				heap_close(part_rel, NoLock);
+
+			continue;
+		}
+
+		tupdesc = CreateTupleDescCopy(RelationGetDescr(part_rel));
+		constr = linitial(def_part_constraints);
+		partition_constraint = (Expr *) map_partition_varattnos((List *) constr,
+																1, part_rel, parent, NULL);
+		estate = CreateExecutorState();
+
+		/* Build expression execution states for partition check quals */
+		partqualstate = ExecPrepareExpr(partition_constraint, estate);
+
+		econtext = GetPerTupleExprContext(estate);
+		snapshot = RegisterSnapshot(GetLatestSnapshot());
+		scan = heap_beginscan(part_rel, snapshot, 0, NULL);
+		tupslot = MakeSingleTupleTableSlot(tupdesc);
+
+		/*
+		 * Switch to per-tuple memory context and reset it for each tuple
+		 * produced, so we don't leak memory.
+		 */
+		oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		{
+			ExecStoreTuple(tuple, tupslot, InvalidBuffer, false);
+			econtext->ecxt_scantuple = tupslot;
+
+			if (!ExecCheck(partqualstate, econtext))
+				ereport(ERROR,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("updated partition constraint for default partition \"%s\" would be violated by some row",
+								RelationGetRelationName(default_rel))));
+
+			ResetExprContext(econtext);
+			CHECK_FOR_INTERRUPTS();
+		}
+
+		MemoryContextSwitchTo(oldCxt);
+		heap_endscan(scan);
+		UnregisterSnapshot(snapshot);
+		ExecDropSingleTupleTableSlot(tupslot);
+		FreeExecutorState(estate);
+
+		if (RelationGetRelid(default_rel) != RelationGetRelid(part_rel))
+			heap_close(part_rel, NoLock);	/* keep the lock until commit */
+	}
+}
+
+/*
  * get_partition_parent
  *
  * Returns inheritance parent of a partition by scanning pg_inherits
@@ -2585,3 +2733,32 @@ update_default_partition_oid(Oid parentId, Oid defaultPartId)
 	heap_freetuple(tuple);
 	heap_close(pg_partitioned_table, RowExclusiveLock);
 }
+
+/*
+ * get_default_part_validation_constraint
+ *
+ * This function returns negated constraint of new_part_constraints which
+ * would be an integral part of the default partition constraints after
+ * addition of the partition to which the new_part_constraints belongs.
+ */
+List *
+get_default_part_validation_constraint(List *new_part_constraints)
+{
+	Expr	   *defPartConstraint;
+
+	defPartConstraint = make_ands_explicit(new_part_constraints);
+
+	/*
+	 * Derieve the partition constraints of default partition by negating the
+	 * given partition constraints. The partition constraint never evaluates
+	 * to NULL, so negating it like this is safe.
+	 */
+	defPartConstraint = makeBoolExpr(NOT_EXPR,
+									 list_make1(defPartConstraint),
+									 -1);
+	defPartConstraint = (Expr *) eval_const_expressions(NULL,
+														(Node *) defPartConstraint);
+	defPartConstraint = canonicalize_qual(defPartConstraint);
+
+	return list_make1(defPartConstraint);
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 9aad58f..94a5992 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -168,6 +168,8 @@ typedef struct AlteredTableInfo
 	bool		chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
 	char		newrelpersistence;	/* if above is true */
 	Expr	   *partition_constraint;	/* for attach partition validation */
+	/* true, if is a default partition or a child of default partition */
+	bool		is_default_partition;
 	/* Objects to rebuild after completing ALTER TYPE operations */
 	List	   *changedConstraintOids;	/* OIDs of constraints to rebuild */
 	List	   *changedConstraintDefs;	/* string definitions of same */
@@ -473,11 +475,10 @@ static void CreateInheritance(Relation child_rel, Relation parent_rel);
 static void RemoveInheritance(Relation child_rel, Relation parent_rel);
 static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
 					  PartitionCmd *cmd);
-static bool PartConstraintImpliedByRelConstraint(Relation scanrel,
-									 List *partConstraint);
 static void ValidatePartitionConstraints(List **wqueue, Relation scanrel,
 							 List *scanrel_children,
-							 List *partConstraint);
+							 List *partConstraint,
+							 bool scanrel_is_default);
 static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
 
 
@@ -776,7 +777,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		ParseState *pstate;
 		Oid			parentId = linitial_oid(inheritOids),
 					defaultPartOid;
-		Relation	parent;
+		Relation	parent,
+					defaultRel;
 
 		/* Already have strong enough lock on the parent */
 		parent = heap_open(parentId, NoLock);
@@ -792,15 +794,28 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 							RelationGetRelationName(parent))));
 
 		/*
-		 * A table cannot be created as a partition of a parent already having
-		 * a default partition.
+		 * The partition constraint of the default partition depends on the
+		 * partition bounds of every other partition. It is possible that
+		 * other backend might be about to execute a query on the default
+		 * partition table and the query relies on previously cached default
+		 * partition constraints, which won't be correct after removal of a
+		 * partition. We must therefore take a table lock strong enough to
+		 * prevent all queries on the default partition from proceeding until
+		 * we commit and send out a shared-cache-inval notice that will make
+		 * them update their index lists.
+		 *
+		 * Order of locking: The relation being added won't be visible to
+		 * other backends until it is committed, hence here in
+		 * DefineRelation() the order of locking the default partition and the
+		 * relation being added does not matter. But at all other places we
+		 * need to lock the default relation before we lock the relation being
+		 * added or removed i.e. we should take the lock in same order at all
+		 * the places such that lock parent, lock default partition and then
+		 * lock the partition so as to avoid a deadlock.
 		 */
 		defaultPartOid = get_default_partition_oid(RelationGetRelid(parent));
 		if (OidIsValid(defaultPartOid))
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-					 errmsg("cannot add a new partition to table \"%s\" having a default partition",
-							RelationGetRelationName(parent))));
+			defaultRel = heap_open(defaultPartOid, AccessExclusiveLock);
 
 		/* Tranform the bound values */
 		pstate = make_parsestate(NULL);
@@ -815,6 +830,20 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		 */
 		check_new_partition_bound(relname, parent, bound);
 
+		/*
+		 * If the default partition exists, its partition constraints will
+		 * change after the addition of this new partition such that it won't
+		 * allow any row that qualifies for this new partition. So, check that
+		 * the existing data in the default partition satisfies the constraint
+		 * as it will exist after adding this partition.
+		 */
+		if (OidIsValid(defaultPartOid))
+		{
+			check_default_allows_bound(parent, defaultRel, bound);
+			/* Keep the lock until commit. */
+			heap_close(defaultRel, NoLock);
+		}
+
 		/* Update the pg_class entry. */
 		StorePartitionBound(rel, parent, bound);
 
@@ -4611,9 +4640,16 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 			}
 
 			if (partqualstate && !ExecCheck(partqualstate, econtext))
-				ereport(ERROR,
-						(errcode(ERRCODE_CHECK_VIOLATION),
-						 errmsg("partition constraint is violated by some row")));
+			{
+				if (tab->is_default_partition)
+					ereport(ERROR,
+							(errcode(ERRCODE_CHECK_VIOLATION),
+							 errmsg("updated partition constraint for default partition would be violated by some row")));
+				else
+					ereport(ERROR,
+							(errcode(ERRCODE_CHECK_VIOLATION),
+							 errmsg("partition constraint is violated by some row")));
+			}
 
 			/* Write the tuple out to the new relation */
 			if (newrel)
@@ -13467,7 +13503,7 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
  * Existing constraints includes its check constraints and column-level
  * NOT NULL constraints and partConstraint describes the partition constraint.
  */
-static bool
+bool
 PartConstraintImpliedByRelConstraint(Relation scanrel,
 									 List *partConstraint)
 {
@@ -13554,7 +13590,8 @@ PartConstraintImpliedByRelConstraint(Relation scanrel,
 static void
 ValidatePartitionConstraints(List **wqueue, Relation scanrel,
 							 List *scanrel_children,
-							 List *partConstraint)
+							 List *partConstraint,
+							 bool scanrel_is_default)
 {
 	bool		found_whole_row;
 	ListCell   *lc;
@@ -13616,6 +13653,7 @@ ValidatePartitionConstraints(List **wqueue, Relation scanrel,
 		/* Grab a work queue entry. */
 		tab = ATGetQueueEntry(wqueue, part_rel);
 		tab->partition_constraint = (Expr *) linitial(my_partconstr);
+		tab->is_default_partition = scanrel_is_default;
 
 		/* keep our lock until commit */
 		if (part_rel != scanrel)
@@ -13644,14 +13682,15 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	const char *trigger_name;
 	bool		found_whole_row;
 	Oid			defaultPartOid;
+	List	   *partBoundConstraint;
 
-	/* A partition cannot be attached if there exists a default partition */
+	/*
+	 * We must lock the default partition, for the same reasons explained in
+	 * DefineRelation().
+	 */
 	defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
 	if (OidIsValid(defaultPartOid))
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-				 errmsg("cannot attach a new partition to table \"%s\" having a default partition",
-						RelationGetRelationName(rel))));
+		LockRelationOid(defaultPartOid, AccessExclusiveLock);
 
 	attachrel = heap_openrv(cmd->name, AccessExclusiveLock);
 
@@ -13829,8 +13868,8 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	 * If the parent itself is a partition, make sure to include its
 	 * constraint as well.
 	 */
-	partConstraint = list_concat(get_qual_from_partbound(attachrel, rel,
-														 cmd->bound),
+	partBoundConstraint = get_qual_from_partbound(attachrel, rel, cmd->bound);
+	partConstraint = list_concat(partBoundConstraint,
 								 RelationGetPartitionQual(rel));
 
 	/* Skip validation if there are no constraints to validate. */
@@ -13853,7 +13892,30 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 
 		/* Validate partition constraints against the table being attached. */
 		ValidatePartitionConstraints(wqueue, attachrel, attachrel_children,
-									 partConstraint);
+									 partConstraint, false);
+
+		/*
+		 * Check whether default partition has a row that would fit the
+		 * partition being attached.
+		 */
+		defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
+		if (OidIsValid(defaultPartOid))
+		{
+			Relation	defaultrel;
+			List	   *defaultrel_children;
+			List	   *defPartConstraint;
+
+			/* We already have taken a lock on default partition. */
+			defaultrel = heap_open(defaultPartOid, NoLock);
+			defPartConstraint = get_default_part_validation_constraint(partBoundConstraint);
+			defaultrel_children = find_all_inheritors(defaultPartOid,
+													  AccessExclusiveLock, NULL);
+			ValidatePartitionConstraints(wqueue, defaultrel, defaultrel_children,
+										 defPartConstraint, true);
+
+			/* keep our lock until commit. */
+			heap_close(defaultrel, NoLock);
+		}
 	}
 
 	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachrel));
@@ -13885,7 +13947,7 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 
 	/*
 	 * We must lock the default partition, for the same reasons explained in
-	 * heap_drop_with_catalog().
+	 * DefineRelation().
 	 */
 	defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
 	if (OidIsValid(defaultPartOid))
@@ -13932,7 +13994,7 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 
 		/*
 		 * We must invalidate default partition's relcache, for the same
-		 * reasons explained in heap_drop_with_catalog().
+		 * reasons explained in StorePartitionBound().
 		 */
 		CacheInvalidateRelcacheByRelid(defaultPartOid);
 	}
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 1dd70f2..ee0cc15 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -101,5 +101,8 @@ extern int get_partition_for_tuple(PartitionDispatch *pd,
 						TupleTableSlot **failed_slot);
 extern Oid	get_default_partition_oid(Oid parentId);
 extern void update_default_partition_oid(Oid parentId, Oid defaultPartId);
+extern void check_default_allows_bound(Relation parent, Relation defaultRel,
+						   PartitionBoundSpec *new_spec);
+extern List *get_default_part_validation_constraint(List *new_part_constaints);
 
 #endif							/* PARTITION_H */
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index abd31b6..da3ff5d 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -18,6 +18,7 @@
 #include "catalog/dependency.h"
 #include "catalog/objectaddress.h"
 #include "nodes/parsenodes.h"
+#include "catalog/partition.h"
 #include "storage/lock.h"
 #include "utils/relcache.h"
 
@@ -87,4 +88,7 @@ extern void RangeVarCallbackOwnsTable(const RangeVar *relation,
 
 extern void RangeVarCallbackOwnsRelation(const RangeVar *relation,
 							 Oid relId, Oid oldRelId, void *noCatalogs);
+extern bool PartConstraintImpliedByRelConstraint(Relation scanrel,
+									 List *partConstraint);
+
 #endif							/* TABLECMDS_H */
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 053e472..b9d843b 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3280,7 +3280,7 @@ ALTER TABLE list_parted ATTACH PARTITION def_part DEFAULT;
 -- exists
 CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT;
-ERROR:  cannot attach a new partition to table "list_parted" having a default partition
+ERROR:  partition "fail_def_part" conflicts with existing default partition "def_part"
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
 	a int,
@@ -3294,14 +3294,14 @@ ERROR:  partition constraint is violated by some row
 -- should be ok after deleting the bad row
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
--- check partition cannot be attached if default exists
+-- check partition cannot be attached if default has some row for its values
 CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 INSERT INTO list_parted2_def VALUES (11, 'z');
 CREATE TABLE part_3 (LIKE list_parted2);
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
-ERROR:  cannot attach a new partition to table "list_parted2" having a default partition
--- should be ok after detaching a default partition
-ALTER TABLE list_parted2 DETACH PARTITION list_parted2_def;
+ERROR:  updated partition constraint for default partition would be violated by some row
+-- should be ok after deleting the bad row
+DELETE FROM list_parted2_def WHERE a = 11;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
@@ -3318,6 +3318,10 @@ ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
 ALTER TABLE part_3_4 ALTER a SET NOT NULL;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
 INFO:  partition constraint for table "part_3_4" is implied by existing constraints
+-- check if default partition scan skipped
+ALTER TABLE list_parted2_def ADD CONSTRAINT check_a CHECK (a IN (5, 6));
+CREATE TABLE part_55_66 PARTITION OF list_parted2 FOR VALUES IN (55, 66);
+INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
 -- check validation when attaching range partitions
 CREATE TABLE range_parted (
 	a int,
@@ -3345,10 +3349,17 @@ ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 2
 INFO:  partition constraint for table "part2" is implied by existing constraints
 -- Create default partition
 CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
--- New partition cannot be attached if a default partition exists
+-- 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:  cannot attach a new partition to table "range_parted" having a default partition
+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 (
@@ -3401,6 +3412,7 @@ ALTER TABLE part_7 ATTACH PARTITION part_7_a_null FOR VALUES IN ('a', null);
 INFO:  partition constraint for table "part_7_a_null" is implied by existing constraints
 ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
 INFO:  partition constraint for table "part_7" is implied by existing constraints
+INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
 -- Same example, but check this time that the constraint correctly detects
 -- violating rows
 ALTER TABLE list_parted2 DETACH PARTITION part_7;
@@ -3414,7 +3426,20 @@ SELECT tableoid::regclass, a, b FROM part_7 order by a;
 (2 rows)
 
 ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
+INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
 ERROR:  partition constraint is violated by some row
+-- check that leaf partitions of default partition are scanned when
+-- attaching a partitioned table.
+ALTER TABLE part_5 DROP CONSTRAINT check_a;
+CREATE TABLE part5_def PARTITION OF part_5 DEFAULT PARTITION BY LIST(a);
+CREATE TABLE part5_def_p1 PARTITION OF part5_def FOR VALUES IN (5);
+INSERT INTO part5_def_p1 VALUES (5, 'y');
+CREATE TABLE part5_p1 (LIKE part_5);
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+ERROR:  updated partition constraint for default partition would be violated by some row
+-- should be ok after deleting the bad row
+DELETE FROM part5_def_p1 WHERE b = 'y';
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
 -- check that the table being attached is not already a partition
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 ERROR:  "part_2" is already a partition
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 4f79612..58c755b 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -470,10 +470,7 @@ LINE 1: ...BLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) T...
 -- check default partition cannot be created more than once
 CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
 CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
-ERROR:  cannot add a new partition to table "list_parted" having a default partition
--- check that no partition can be created after default partition
-CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (3);
-ERROR:  cannot add a new partition to table "list_parted" having a default partition
+ERROR:  partition "fail_default_part" conflicts with existing default partition "part_default"
 -- specified literal can't be cast to the partition column data type
 CREATE TABLE bools (
 	a bool
@@ -565,10 +562,15 @@ CREATE TABLE list_parted2 (
 ) PARTITION BY LIST (a);
 CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
 CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
 ERROR:  partition "fail_part" would overlap partition "part_null_z"
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
 ERROR:  partition "fail_part" would overlap partition "part_ab"
+-- check default partition overlap
+INSERT INTO list_parted2 VALUES('X');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y');
+ERROR:  updated partition constraint for default partition "list_parted2_def" would be violated by some row
 CREATE TABLE range_parted2 (
 	a int
 ) PARTITION BY RANGE (a);
@@ -594,9 +596,14 @@ 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;
--- New partition cannot be created if a default partition exists
+-- 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:  cannot add a new partition to table "range_parted2" having a default partition
+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,
@@ -610,13 +617,12 @@ 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
 CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, minvalue) TO (1, maxvalue);
 ERROR:  partition "fail_part" would overlap partition "part10"
--- check default partition can be added to multi-column range partitioned table
-CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
 -- check schema propagation from parent
 CREATE TABLE parted (
 	a text,
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index a0ef3ff..2022584 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -276,9 +276,6 @@ select tableoid::regclass, * from list_parted;
  part_default_p2    | de | 35
 (9 rows)
 
--- drop default partition, as we need to add some more partitions to test tuple
--- routing
-drop table part_default;
 -- Check tuple routing for partitioned tables
 -- fail
 insert into range_parted values ('a', 0);
@@ -352,7 +349,10 @@ select tableoid::regclass, * from list_parted;
  part_ee_ff2        | EE | 10
  part_xx_yy_p1      | xx |  1
  part_xx_yy_defpart | yy |  2
-(10 rows)
+ part_default_p1    | cd | 25
+ part_default_p1    | ab | 21
+ part_default_p2    | de | 35
+(13 rows)
 
 -- some more tests to exercise tuple-routing with multi-level partitioning
 create table part_gg partition of list_parted for values in ('gg') partition by range (b);
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
index fb90357..c2eeff1 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -266,6 +266,10 @@ DETAIL:  Failing row contains (null).
 execute pstmt_def_insert(1);
 ERROR:  new row for relation "list_part_def" violates partition constraint
 DETAIL:  Failing row contains (1).
+create table list_part_2 partition of list_parted for values in (2);
+execute pstmt_def_insert(2);
+ERROR:  new row for relation "list_part_def" violates partition constraint
+DETAIL:  Failing row contains (2).
 alter table list_parted detach partition list_part_null;
 -- should be ok
 execute pstmt_def_insert(null);
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index b6aa9e3..ab5d23c 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2118,13 +2118,13 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 
--- check partition cannot be attached if default exists
+-- check partition cannot be attached if default has some row for its values
 CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 INSERT INTO list_parted2_def VALUES (11, 'z');
 CREATE TABLE part_3 (LIKE list_parted2);
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
--- should be ok after detaching a default partition
-ALTER TABLE list_parted2 DETACH PARTITION list_parted2_def;
+-- should be ok after deleting the bad row
+DELETE FROM list_parted2_def WHERE a = 11;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
 
 -- adding constraints that describe the desired partition constraint
@@ -2144,6 +2144,9 @@ ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
 ALTER TABLE part_3_4 ALTER a SET NOT NULL;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
 
+-- check if default partition scan skipped
+ALTER TABLE list_parted2_def ADD CONSTRAINT check_a CHECK (a IN (5, 6));
+CREATE TABLE part_55_66 PARTITION OF list_parted2 FOR VALUES IN (55, 66);
 
 -- check validation when attaching range partitions
 CREATE TABLE range_parted (
@@ -2175,10 +2178,18 @@ ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 2
 -- Create default partition
 CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
 
--- New partition cannot be attached if a default partition exists
+-- 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 (
@@ -2239,6 +2250,18 @@ INSERT INTO part_7 (a, b) VALUES (8, null), (9, 'a');
 SELECT tableoid::regclass, a, b FROM part_7 order by a;
 ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
 
+-- check that leaf partitions of default partition are scanned when
+-- attaching a partitioned table.
+ALTER TABLE part_5 DROP CONSTRAINT check_a;
+CREATE TABLE part5_def PARTITION OF part_5 DEFAULT PARTITION BY LIST(a);
+CREATE TABLE part5_def_p1 PARTITION OF part5_def FOR VALUES IN (5);
+INSERT INTO part5_def_p1 VALUES (5, 'y');
+CREATE TABLE part5_p1 (LIKE part_5);
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+-- should be ok after deleting the bad row
+DELETE FROM part5_def_p1 WHERE b = 'y';
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+
 -- check that the table being attached is not already a partition
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 24f005e..eeab5d9 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -450,8 +450,6 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
 -- check default partition cannot be created more than once
 CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
 CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
--- check that no partition can be created after default partition
-CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (3);
 
 -- specified literal can't be cast to the partition column data type
 CREATE TABLE bools (
@@ -530,9 +528,13 @@ CREATE TABLE list_parted2 (
 ) PARTITION BY LIST (a);
 CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
 CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+-- check default partition overlap
+INSERT INTO list_parted2 VALUES('X');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y');
 
 CREATE TABLE range_parted2 (
 	a int
@@ -555,8 +557,13 @@ 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;
 
--- New partition cannot be created if a default partition exists
+-- 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 (
@@ -571,15 +578,13 @@ 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
 -- more specific ranges
 CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, minvalue) TO (1, maxvalue);
 
--- check default partition can be added to multi-column range partitioned table
-CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
-
 -- check schema propagation from parent
 
 CREATE TABLE parted (
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index b7d11d5..adb31d4 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -165,9 +165,6 @@ insert into list_parted values ('ab', 21);
 insert into list_parted values ('xx', 1);
 insert into list_parted values ('yy', 2);
 select tableoid::regclass, * from list_parted;
--- drop default partition, as we need to add some more partitions to test tuple
--- routing
-drop table part_default;
 
 -- Check tuple routing for partitioned tables
 
diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql
index a8e357c..cb2a551 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -167,6 +167,8 @@ prepare pstmt_def_insert (int) as insert into list_part_def values($1);
 -- should fail
 execute pstmt_def_insert(null);
 execute pstmt_def_insert(1);
+create table list_part_2 partition of list_parted for values in (2);
+execute pstmt_def_insert(2);
 alter table list_parted detach partition list_part_null;
 -- should be ok
 execute pstmt_def_insert(null);
-- 
2.7.4

0005-Check-default-partitition-child-validation-scan-is.patch0000664000175000017500000001277413152002660023542 0ustar  jeevanjeevanFrom 10be01e36c125f3a8b8aa96a4b7110530328ce3f Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Thu, 31 Aug 2017 17:42:24 +0530
Subject: [PATCH 5/6] Check default partitition child validation scan is
 skippable

Add code to check_default_allows_bound() such that the default
partition children constraints are checked against new partition
constraints for implication and avoid scan of the child of which
existing constraints are implied by new default partition
constraints. Also, added testcase for the same.

Jeevan Ladhe.
---
 src/backend/catalog/partition.c           | 18 ++++++++++++++++++
 src/test/regress/expected/alter_table.out | 14 ++++++++++++--
 src/test/regress/sql/alter_table.sql      |  9 +++++++++
 3 files changed, 39 insertions(+), 2 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 58bb509..2298003 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -948,7 +948,25 @@ check_default_allows_bound(Relation parent, Relation default_rel,
 
 		/* Lock already taken above. */
 		if (part_relid != RelationGetRelid(default_rel))
+		{
 			part_rel = heap_open(part_relid, NoLock);
+
+			/*
+			 * If the partition constraints on default partition child imply
+			 * that it will not contain any row that would belong to the new
+			 * partition, we can avoid scanning the child table.
+			 */
+			if (PartConstraintImpliedByRelConstraint(part_rel,
+													 def_part_constraints))
+			{
+				ereport(INFO,
+						(errmsg("partition constraint for table \"%s\" is implied by existing constraints",
+								RelationGetRelationName(part_rel))));
+
+				heap_close(part_rel, NoLock);
+				continue;
+			}
+		}
 		else
 			part_rel = default_rel;
 
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index b9d843b..3cecf82 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3322,6 +3322,18 @@ INFO:  partition constraint for table "part_3_4" is implied by existing constrai
 ALTER TABLE list_parted2_def ADD CONSTRAINT check_a CHECK (a IN (5, 6));
 CREATE TABLE part_55_66 PARTITION OF list_parted2 FOR VALUES IN (55, 66);
 INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
+-- check if default partition child relation scan skipped
+DROP TABLE list_parted2_def;
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT PARTITION BY RANGE (a);
+CREATE TABLE list_parted2_def_p1 PARTITION OF list_parted2_def FOR VALUES FROM (21) TO (30);
+CREATE TABLE list_parted2_def_p2 PARTITION OF list_parted2_def FOR VALUES FROM (31) TO (40);
+ALTER TABLE list_parted2_def_p1 ADD CONSTRAINT check_a CHECK (a IN (21, 22));
+ALTER TABLE list_parted2_def_p2 ADD CONSTRAINT check_a CHECK (a IN (31, 32));
+CREATE TABLE part_9 PARTITION OF list_parted2 FOR VALUES IN (9);
+INFO:  partition constraint for table "list_parted2_def_p1" is implied by existing constraints
+INFO:  partition constraint for table "list_parted2_def_p2" is implied by existing constraints
+CREATE TABLE part_31 PARTITION OF list_parted2 FOR VALUES IN (31);
+INFO:  partition constraint for table "list_parted2_def_p1" is implied by existing constraints
 -- check validation when attaching range partitions
 CREATE TABLE range_parted (
 	a int,
@@ -3412,7 +3424,6 @@ ALTER TABLE part_7 ATTACH PARTITION part_7_a_null FOR VALUES IN ('a', null);
 INFO:  partition constraint for table "part_7_a_null" is implied by existing constraints
 ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
 INFO:  partition constraint for table "part_7" is implied by existing constraints
-INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
 -- Same example, but check this time that the constraint correctly detects
 -- violating rows
 ALTER TABLE list_parted2 DETACH PARTITION part_7;
@@ -3426,7 +3437,6 @@ SELECT tableoid::regclass, a, b FROM part_7 order by a;
 (2 rows)
 
 ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
-INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
 ERROR:  partition constraint is violated by some row
 -- check that leaf partitions of default partition are scanned when
 -- attaching a partitioned table.
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index ab5d23c..ccb2f26 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2147,6 +2147,15 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
 -- check if default partition scan skipped
 ALTER TABLE list_parted2_def ADD CONSTRAINT check_a CHECK (a IN (5, 6));
 CREATE TABLE part_55_66 PARTITION OF list_parted2 FOR VALUES IN (55, 66);
+-- check if default partition child relation scan skipped
+DROP TABLE list_parted2_def;
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT PARTITION BY RANGE (a);
+CREATE TABLE list_parted2_def_p1 PARTITION OF list_parted2_def FOR VALUES FROM (21) TO (30);
+CREATE TABLE list_parted2_def_p2 PARTITION OF list_parted2_def FOR VALUES FROM (31) TO (40);
+ALTER TABLE list_parted2_def_p1 ADD CONSTRAINT check_a CHECK (a IN (21, 22));
+ALTER TABLE list_parted2_def_p2 ADD CONSTRAINT check_a CHECK (a IN (31, 32));
+CREATE TABLE part_9 PARTITION OF list_parted2 FOR VALUES IN (9);
+CREATE TABLE part_31 PARTITION OF list_parted2 FOR VALUES IN (31);
 
 -- check validation when attaching range partitions
 CREATE TABLE range_parted (
-- 
2.7.4

0006-Documentation-for-default-partition.patch0000664000175000017500000002245413152002660021016 0ustar  jeevanjeevanFrom b8aba410210ded9a6f7443a9c0cfcf64d17debd4 Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Thu, 31 Aug 2017 17:43:37 +0530
Subject: [PATCH 6/6] Documentation for default partition.

This patch adds documentation for default partition for
CREATE TABLE and ALTER TABLE commands with example.
Also, added documentation for pg_partitioned_table new
field partdefid.

Jeevan Ladhe.
---
 doc/src/sgml/catalogs.sgml         | 11 +++++++++++
 doc/src/sgml/ref/alter_table.sgml  | 35 ++++++++++++++++++++++++++++++++---
 doc/src/sgml/ref/create_table.sgml | 32 +++++++++++++++++++++++++++++---
 3 files changed, 72 insertions(+), 6 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index ef7054c..b381e7e 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -4739,6 +4739,17 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</>:<replaceable>&lt;salt&gt;<
      </row>
 
      <row>
+      <entry><structfield>partdefid</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+      <entry>
+       The OID of the <structname>pg_class</> entry for the default partition
+       of this partitioned table, or zero if this partitioned table does not
+       have a default partition.
+     </entry>
+     </row>
+
+     <row>
       <entry><structfield>partattrs</structfield></entry>
       <entry><type>int2vector</type></entry>
       <entry><literal><link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.attnum</literal></entry>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 6960032..8dd0f14 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -34,7 +34,7 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
 ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable> [ OWNED BY <replaceable class="PARAMETER">role_name</replaceable> [, ... ] ]
     SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable> [ NOWAIT ]
 ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
-    ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+    ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> { FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> | DEFAULT }
 ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
     DETACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable>
 
@@ -765,11 +765,18 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
    </varlistentry>
 
    <varlistentry>
-    <term><literal>ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable></literal></term>
+    <term><literal>ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> { FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> | DEFAULT }</literal></term>
     <listitem>
      <para>
       This form attaches an existing table (which might itself be partitioned)
-      as a partition of the target table using the same syntax for
+      as a partition of the target table. The table can be attached
+      as a partition for specific values using <literal>FOR VALUES
+      </literal> or as a default partition by using <literal>DEFAULT
+      </literal>.
+     </para>
+
+     <para>
+      A partition using <literal>FOR VALUES</literal> uses same syntax for
       <replaceable class="PARAMETER">partition_bound_spec</replaceable> as
       <xref linkend="sql-createtable">.  The partition bound specification
       must correspond to the partitioning strategy and partition key of the
@@ -798,6 +805,10 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       a list partition that will not accept <literal>NULL</literal> values,
       also add <literal>NOT NULL</literal> constraint to the partition key
       column, unless it's an expression.
+      Also, if there exists a default partition table for the parent table,
+      then the default partition (if it is a regular table) is scanned to
+      check that no existing row in default partition would fit in the
+      partition that is being attached.
      </para>
 
      <para>
@@ -806,6 +817,17 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       (See the discussion in <xref linkend="SQL-CREATEFOREIGNTABLE"> about
       constraints on the foreign table.)
      </para>
+
+     <para>
+      When a table has a default partition, defining a new partition changes
+      the partition constraint for the default partition. The default
+      partition can't contain any rows that would need to be moved to the new
+      partition, and will be scanned to verify that none are present. This
+      scan, like the scan of the new partition, can be avoided if an
+      appropriate <literal>CHECK</literal> constraint is present. Also like
+      the scan of the new partition, it is always skipped when the default
+      partition is a foreign table.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -1396,6 +1418,13 @@ ALTER TABLE cities
 </programlisting></para>
 
   <para>
+   Attach a default partition to a partitioned table:
+<programlisting>
+ALTER TABLE cities
+    ATTACH PARTITION cities_partdef DEFAULT;
+</programlisting></para>
+
+  <para>
    Detach a partition from partitioned table:
 <programlisting>
 ALTER TABLE measurement
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index e9c2c49..c08abac 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -49,7 +49,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
   { <replaceable class="PARAMETER">column_name</replaceable> [ WITH OPTIONS ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
     | <replaceable>table_constraint</replaceable> }
     [, ... ]
-) ] FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+) ] { FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> | DEFAULT }
 [ PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
 [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
 [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
@@ -250,11 +250,13 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
    </varlistentry>
 
    <varlistentry id="SQL-CREATETABLE-PARTITION">
-    <term><literal>PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable> FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable></literal></term>
+    <term><literal>PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable> { FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> | DEFAULT }</literal></term>
     <listitem>
      <para>
       Creates the table as a <firstterm>partition</firstterm> of the specified
-      parent table.
+      parent table. The table can be created either as a partition for specific
+      values using <literal>FOR VALUES</literal> or as a default partition
+      using <literal>DEFAULT</literal>.
      </para>
 
      <para>
@@ -343,6 +345,23 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
      </para>
 
      <para>
+     If <literal>DEFAULT</literal> is specified, the table will be
+     created as a default partition of the parent table. The parent can
+     either be a list or range partitioned table. A partition key value
+     not fitting into any other partition of the given parent will be
+     routed to the default partition. There can be only one default
+     partition for a given parent table.
+     </para>
+
+     <para>
+     If the given parent is already having a default partition then
+     adding a new partition would result in an error if the default
+     partition contains a record that would fit in the new partition
+     being added. This check is not performed if the default partition
+     is a foreign table.
+     </para>
+
+     <para>
       A partition must have the same column names and types as the partitioned
       table to which it belongs.  If the parent is specified <literal>WITH
       OIDS</literal> then all partitions must have OIDs; the parent's OID
@@ -1679,6 +1698,13 @@ CREATE TABLE cities_ab
 CREATE TABLE cities_ab_10000_to_100000
     PARTITION OF cities_ab FOR VALUES FROM (10000) TO (100000);
 </programlisting></para>
+
+  <para>
+   Create a default partition of partitioned table:
+<programlisting>
+CREATE TABLE cities_partdef
+    PARTITION OF cities DEFAULT;
+</programlisting></para>
  </refsect1>
 
  <refsect1 id="SQL-CREATETABLE-compatibility">
-- 
2.7.4

#173Robert Haas
robertmhaas@gmail.com
In reply to: Jeevan Ladhe (#172)
Re: Adding support for Default partition in partitioning

On Thu, Aug 31, 2017 at 8:53 AM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

0001:
This patch refactors RelationBuildPartitionDesc(), basically this is patch
0001 of default range partition[1].

I spent a while studying this; it seems to be simpler and there's no
real downside. So, committed.

--
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

#174Robert Haas
robertmhaas@gmail.com
In reply to: Robert Haas (#173)
Re: Adding support for Default partition in partitioning

On Fri, Sep 1, 2017 at 3:19 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Thu, Aug 31, 2017 at 8:53 AM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

0001:
This patch refactors RelationBuildPartitionDesc(), basically this is patch
0001 of default range partition[1].

I spent a while studying this; it seems to be simpler and there's no
real downside. So, committed.

BTW, the rest of this series seems to need a rebase. The changes to
insert.sql conflicted with 30833ba154e0c1106d61e3270242dc5999a3e4f3.

--
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

#175Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Robert Haas (#174)
Re: Adding support for Default partition in partitioning

On Sat, Sep 2, 2017 at 7:03 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Fri, Sep 1, 2017 at 3:19 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Thu, Aug 31, 2017 at 8:53 AM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

0001:
This patch refactors RelationBuildPartitionDesc(), basically this is

patch

0001 of default range partition[1].

I spent a while studying this; it seems to be simpler and there's no
real downside. So, committed.

Thanks Robert for taking care of this.

BTW, the rest of this series seems to need a rebase. The changes to
insert.sql conflicted with 30833ba154e0c1106d61e3270242dc5999a3e4f3.

Will rebase the patches.

Regards,
Jeevan Ladhe

#176Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Jeevan Ladhe (#175)
1 attachment(s)
Re: Adding support for Default partition in partitioning

Hi,

Attached is the rebased set of patches.
Robert has committed[1]/messages/by-id/CA+Tgmoa5oKefW27VXD=mLRo0_SbbFj=Scybc-MZist7X3EkNmA@mail.gmail.com patch 0001 in V26 series, hence the patch numbering
in V27 is now decreased by 1 for each patch as compared to V26.

This set of patches also addresses comments[2]/messages/by-id/CAFjFpReO7VQtES6k+zNXF_cttNZU+cZ8U4HwN++WVSTuFG6PyQ@mail.gmail.com given by Ashutosh.

Here is the description of the patches:

0001:
This patch teaches the partitioning code to handle the NIL returned by
get_qual_for_list(). This is needed because a default partition will not
have
any constraints in case it is the only partition of its parent.

0002:
Support for default partition with the restriction of preventing addition
of any
new partition after default partition. This patch has support for default
partition for both list and range.
Addition to V26 patch 0003 following are the additional changes here:;
1. Some changes in range default partition comments given by Beena offline.
2. I have shifted definition of macro partition_bound_has_default to next
patch
as it wasn't used in this patch at all.

0003:
Extend default partitioning support to allow addition of new partitions
after
default partition is created/attached.

0004:
This patch introduces code to check if the scanning of default partition
child
can be skipped if it's constraints are proven.

0005:
Documentation.

[1]: /messages/by-id/CA+Tgmoa5oKefW27VXD=mLRo0_SbbFj=Scybc-MZist7X3EkNmA@mail.gmail.com
/messages/by-id/CA+Tgmoa5oKefW27VXD=mLRo0_SbbFj=Scybc-MZist7X3EkNmA@mail.gmail.com

[2]: /messages/by-id/CAFjFpReO7VQtES6k+zNXF_cttNZU+cZ8U4HwN++WVSTuFG6PyQ@mail.gmail.com
/messages/by-id/CAFjFpReO7VQtES6k+zNXF_cttNZU+cZ8U4HwN++WVSTuFG6PyQ@mail.gmail.com

Regards,
Jeevan Ladhe

Attachments:

default_partition_V27.tarapplication/x-tar; name=default_partition_V27.tarDownload
0001-Fix-assumptions-that-get_qual_from_partbound-cannot.patch0000664000175000017500000001146013153753554024225 0ustar  jeevanjeevanFrom 002499ad7e59166c1d6f3cc1a8c48fcb1f01005b Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Wed, 6 Sep 2017 16:22:37 +0530
Subject: [PATCH 1/5] Fix assumptions that get_qual_from_partbound() cannot
 return NIL list.

Current partitioning code assumes that there cannot be any partition
without partition constraints, but in future this assumption might
not hold true. This patch makes sure that the callers of
generate_partition_qual() can handle NIL partition constraint.
e.g. if we introduce support for default partition, then default
partition will not have any constraints in case it is the only
partition of its parent.

Jeevan Ladhe.
---
 src/backend/catalog/partition.c   | 11 ++++++++---
 src/backend/commands/tablecmds.c  | 37 +++++++++++++++++++++----------------
 src/backend/utils/adt/ruleutils.c |  2 +-
 3 files changed, 30 insertions(+), 20 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 5016263..807ceee 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -935,7 +935,8 @@ RelationGetPartitionQual(Relation rel)
  * get_partition_qual_relid
  *
  * Returns an expression tree describing the passed-in relation's partition
- * constraint.
+ * constraint. If there is no partition constraint returns NULL e.g. in case
+ * default partition is the only partition.
  */
 Expr *
 get_partition_qual_relid(Oid relid)
@@ -948,7 +949,10 @@ get_partition_qual_relid(Oid relid)
 	if (rel->rd_rel->relispartition)
 	{
 		and_args = generate_partition_qual(rel);
-		if (list_length(and_args) > 1)
+
+		if (and_args == NIL)
+			result = NULL;
+		else if (list_length(and_args) > 1)
 			result = makeBoolExpr(AND_EXPR, and_args, -1);
 		else
 			result = linitial(and_args);
@@ -1756,7 +1760,8 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 /*
  * generate_partition_qual
  *
- * Generate partition predicate from rel's partition bound expression
+ * Generate partition predicate from rel's partition bound expression. The
+ * function returns a NIL list if there is no predicate.
  *
  * Result expression tree is stored CacheMemoryContext to ensure it survives
  * as long as the relcache entry. But we should be running in a less long-lived
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 0f08245..0fb8238 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13802,24 +13802,29 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	partConstraint = list_concat(get_qual_from_partbound(attachrel, rel,
 														 cmd->bound),
 								 RelationGetPartitionQual(rel));
-	partConstraint = (List *) eval_const_expressions(NULL,
-													 (Node *) partConstraint);
-	partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
-	partConstraint = list_make1(make_ands_explicit(partConstraint));
 
-	/*
-	 * Adjust the generated constraint to match this partition's attribute
-	 * numbers.
-	 */
-	partConstraint = map_partition_varattnos(partConstraint, 1, attachrel,
-											 rel, &found_whole_row);
-	/* There can never be a whole-row reference here */
-	if (found_whole_row)
-		elog(ERROR, "unexpected whole-row reference found in partition key");
+	/* Skip validation if there are no constraints to validate. */
+	if (partConstraint)
+	{
+		partConstraint = (List *) eval_const_expressions(NULL,
+														 (Node *) partConstraint);
+		partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
+		partConstraint = list_make1(make_ands_explicit(partConstraint));
 
-	/* Validate partition constraints against the table being attached. */
-	ValidatePartitionConstraints(wqueue, attachrel, attachrel_children,
-								 partConstraint);
+		/*
+		 * Adjust the generated constraint to match this partition's attribute
+		 * numbers.
+		 */
+		partConstraint = map_partition_varattnos(partConstraint, 1, attachrel,
+												 rel, &found_whole_row);
+		/* There can never be a whole-row reference here */
+		if (found_whole_row)
+			elog(ERROR, "unexpected whole-row reference found in partition key");
+
+		/* Validate partition constraints against the table being attached. */
+		ValidatePartitionConstraints(wqueue, attachrel, attachrel_children,
+									 partConstraint);
+	}
 
 	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachrel));
 
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 43646d2..87aba5f 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -1750,7 +1750,7 @@ pg_get_partition_constraintdef(PG_FUNCTION_ARGS)
 
 	constr_expr = get_partition_qual_relid(relationId);
 
-	/* Quick exit if not a partition */
+	/* Quick exit if no partition constraint */
 	if (constr_expr == NULL)
 		PG_RETURN_NULL();
 
-- 
2.7.4

0002-Implement-default-partition-support.patch0000664000175000017500000021163513153753555021103 0ustar  jeevanjeevanFrom 0e4e67af00eb023e5ef059634d55becfbf103aba Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Wed, 6 Sep 2017 16:34:36 +0530
Subject: [PATCH 2/5] Implement default partition support

This patch introduces default partition for a list or range
partitioned table. However, in this version of patch no other
partition can be attached to a partitioned table if it has a
default partition. To ease the retrieval of default partition
OID, this patch adds a new column 'partdefid' to catalog
pg_partitioned_table.

Jeevan Ladhe, range partitioning related changes by Beena Emerson.
---
 src/backend/catalog/heap.c                 |  38 ++-
 src/backend/catalog/partition.c            | 427 ++++++++++++++++++++++++-----
 src/backend/commands/tablecmds.c           |  69 ++++-
 src/backend/nodes/copyfuncs.c              |   1 +
 src/backend/nodes/equalfuncs.c             |   1 +
 src/backend/nodes/outfuncs.c               |   1 +
 src/backend/nodes/readfuncs.c              |   1 +
 src/backend/parser/gram.y                  |  27 +-
 src/backend/parser/parse_utilcmd.c         |  14 +
 src/backend/utils/adt/ruleutils.c          |  11 +-
 src/bin/psql/describe.c                    |  10 +-
 src/bin/psql/tab-complete.c                |   4 +-
 src/include/catalog/partition.h            |   3 +
 src/include/catalog/pg_partitioned_table.h |  13 +-
 src/include/nodes/parsenodes.h             |   1 +
 src/test/regress/expected/alter_table.out  |  24 ++
 src/test/regress/expected/create_table.out |  14 +
 src/test/regress/expected/insert.out       | 139 +++++++++-
 src/test/regress/expected/plancache.out    |  22 ++
 src/test/regress/expected/sanity_check.out |   4 +
 src/test/regress/expected/update.out       |  24 ++
 src/test/regress/sql/alter_table.sql       |  24 ++
 src/test/regress/sql/create_table.sql      |  15 +
 src/test/regress/sql/insert.sql            |  71 ++++-
 src/test/regress/sql/plancache.sql         |  19 ++
 src/test/regress/sql/update.sql            |  23 ++
 26 files changed, 891 insertions(+), 109 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 45ee9ac..2952d40 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1759,7 +1759,8 @@ heap_drop_with_catalog(Oid relid)
 {
 	Relation	rel;
 	HeapTuple	tuple;
-	Oid			parentOid = InvalidOid;
+	Oid			parentOid = InvalidOid,
+				defaultPartOid = InvalidOid;
 
 	/*
 	 * To drop a partition safely, we must grab exclusive lock on its parent,
@@ -1775,6 +1776,23 @@ heap_drop_with_catalog(Oid relid)
 	{
 		parentOid = get_partition_parent(relid);
 		LockRelationOid(parentOid, AccessExclusiveLock);
+
+		/*
+		 * The partition constraint of the default partition depends on the
+		 * partition bounds of every other partition. It is possible that
+		 * other backend might be about to execute a query on the default
+		 * partition table and the query relies on previously cached default
+		 * partition constraints, which won't be correct after removal of a
+		 * partition. We must therefore take a table lock strong enough to
+		 * prevent all queries on the default partition from proceeding until
+		 * we commit and send out a shared-cache-inval notice that will make
+		 * them update their index lists. Note that we need not lock the
+		 * default partition if the table being dropped itself is the default
+		 * partition, because it is going to be locked further.
+		 */
+		defaultPartOid = get_default_partition_oid(parentOid);
+		if (OidIsValid(defaultPartOid) && relid != defaultPartOid)
+			LockRelationOid(defaultPartOid, AccessExclusiveLock);
 	}
 
 	ReleaseSysCache(tuple);
@@ -1826,6 +1844,13 @@ heap_drop_with_catalog(Oid relid)
 		RemovePartitionKeyByRelId(relid);
 
 	/*
+	 * If the relation being dropped is the default partition itself,
+	 * invalidate its entry in pg_partitioned_table.
+	 */
+	if (relid == defaultPartOid)
+		update_default_partition_oid(parentOid, InvalidOid);
+
+	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -1885,6 +1910,16 @@ heap_drop_with_catalog(Oid relid)
 	if (OidIsValid(parentOid))
 	{
 		/*
+		 * The partition constraint for the default partition depends on the
+		 * partition bounds of every other partition, so we must invalidate
+		 * the relcache entry for that partition every time a partition is
+		 * added or removed. If the default partition itself is dropped no
+		 * need to invalidate its relcache.
+		 */
+		if (OidIsValid(defaultPartOid) && relid != defaultPartOid)
+			CacheInvalidateRelcacheByRelid(defaultPartOid);
+
+		/*
 		 * Invalidate the parent's relcache so that the partition is no longer
 		 * included in its partition descriptor.
 		 */
@@ -3138,6 +3173,7 @@ StorePartitionKey(Relation rel,
 	values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
 	values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
 	values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
+	values[Anum_pg_partitioned_table_partdefid - 1] = ObjectIdGetDatum(InvalidOid);
 	values[Anum_pg_partitioned_table_partattrs - 1] = PointerGetDatum(partattrs_vec);
 	values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
 	values[Anum_pg_partitioned_table_partcollation - 1] = PointerGetDatum(partcollation_vec);
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 807ceee..ac51bca 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -27,6 +27,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_inherits_fn.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_type.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
@@ -80,6 +81,8 @@ typedef struct PartitionBoundInfoData
 								 * partitioned table) */
 	int			null_index;		/* Index of the null-accepting partition; -1
 								 * if there isn't one */
+	int			default_index;	/* Index of the default partition; -1 if there
+								 * isn't one */
 } PartitionBoundInfoData;
 
 #define partition_bound_accepts_nulls(bi) ((bi)->null_index != -1)
@@ -120,8 +123,10 @@ static void get_range_key_properties(PartitionKey key, int keynum,
 						 ListCell **partexprs_item,
 						 Expr **keyCol,
 						 Const **lower_val, Const **upper_val);
-static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
-static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
+static List *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 *generate_partition_qual(Relation rel);
 
 static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index,
@@ -162,6 +167,7 @@ RelationBuildPartitionDesc(Relation rel)
 	MemoryContext oldcxt;
 
 	int			ndatums = 0;
+	int			default_index = -1;
 
 	/* List partitioning specific */
 	PartitionListValue **all_values = NULL;
@@ -213,6 +219,21 @@ RelationBuildPartitionDesc(Relation rel)
 								&isnull);
 		Assert(!isnull);
 		boundspec = (Node *) stringToNode(TextDatumGetCString(datum));
+
+		/*
+		 * If this is a default partition, pg_partitioned_table must have it's
+		 * OID as value of 'partdefid' for it's parent (i.e. rel) entry.
+		 */
+		if (castNode(PartitionBoundSpec, boundspec)->is_default)
+		{
+			Oid			partdefid;
+
+			partdefid = get_default_partition_oid(RelationGetRelid(rel));
+			if (partdefid != inhrelid)
+				elog(WARNING, "unexpected partdefid %u for pg_partition_table record of relation %s",
+					 partdefid, RelationGetRelationName(rel));
+		}
+
 		boundspecs = lappend(boundspecs, boundspec);
 		partoids = lappend_oid(partoids, inhrelid);
 		ReleaseSysCache(tuple);
@@ -246,6 +267,18 @@ RelationBuildPartitionDesc(Relation rel)
 				if (spec->strategy != PARTITION_STRATEGY_LIST)
 					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 list of non-null
+				 * datums for this partition.
+				 */
+				if (spec->is_default)
+				{
+					default_index = i;
+					i++;
+					continue;
+				}
+
 				foreach(c, spec->listdatums)
 				{
 					Const	   *val = castNode(Const, lfirst(c));
@@ -325,6 +358,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,
@@ -334,10 +378,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);
@@ -421,6 +466,7 @@ RelationBuildPartitionDesc(Relation rel)
 		boundinfo = (PartitionBoundInfoData *)
 			palloc0(sizeof(PartitionBoundInfoData));
 		boundinfo->strategy = key->strategy;
+		boundinfo->default_index = -1;
 		boundinfo->ndatums = ndatums;
 		boundinfo->null_index = -1;
 		boundinfo->datums = (Datum **) palloc0(ndatums * sizeof(Datum *));
@@ -473,6 +519,21 @@ RelationBuildPartitionDesc(Relation rel)
 						boundinfo->null_index = mapping[null_index];
 					}
 
+					/* Assign mapped index for the default partition. */
+					if (default_index != -1)
+					{
+						/*
+						 * The default partition accepts any value not
+						 * specified in the lists of other partitions, hence
+						 * it should not get mapped index while assigning
+						 * those for non-null datums.
+						 */
+						Assert(default_index >= 0 &&
+							   mapping[default_index] == -1);
+						mapping[default_index] = next_index++;
+						boundinfo->default_index = mapping[default_index];
+					}
+
 					/* All partition must now have a valid mapping */
 					Assert(next_index == nparts);
 					break;
@@ -527,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;
 				}
@@ -577,6 +646,9 @@ partition_bounds_equal(int partnatts, int16 *parttyplen, bool *parttypbyval,
 	if (b1->null_index != b2->null_index)
 		return false;
 
+	if (b1->default_index != b2->default_index)
+		return false;
+
 	for (i = 0; i < b1->ndatums; i++)
 	{
 		int			j;
@@ -639,6 +711,9 @@ check_new_partition_bound(char *relname, Relation parent,
 	int			with = -1;
 	bool		overlap = false;
 
+	if (spec->is_default)
+		return;
+
 	switch (key->strategy)
 	{
 		case PARTITION_STRATEGY_LIST:
@@ -860,12 +935,12 @@ get_qual_from_partbound(Relation rel, Relation parent,
 	{
 		case PARTITION_STRATEGY_LIST:
 			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
-			my_qual = get_qual_for_list(key, spec);
+			my_qual = get_qual_for_list(parent, spec);
 			break;
 
 		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:
@@ -1267,10 +1342,14 @@ make_partition_op_expr(PartitionKey key, int keynum,
  *
  * Returns an implicit-AND list of expressions to use as a list partition's
  * constraint, given the partition key and bound structures.
+ *
+ * The function returns NIL for a default partition when it's the only
+ * partition since it can not have any partition constraint.
  */
 static List *
-get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
+get_qual_for_list(Relation parent, PartitionBoundSpec *spec)
 {
+	PartitionKey key = RelationGetPartitionKey(parent);
 	List	   *result;
 	Expr	   *keyCol;
 	ArrayExpr  *arr;
@@ -1297,15 +1376,63 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 	else
 		keyCol = (Expr *) copyObject(linitial(key->partexprs));
 
-	/* Create list of Consts for the allowed values, excluding any nulls */
-	foreach(cell, spec->listdatums)
+	/*
+	 * For default list partition, collect datums for all the partitions. The
+	 * default partition constraint should check that the partition key is
+	 * equal to none of those.
+	 */
+	if (spec->is_default)
 	{
-		Const	   *val = castNode(Const, lfirst(cell));
+		int			i;
+		int			ndatums = 0;
+		PartitionDesc pdesc = RelationGetPartitionDesc(parent);
+		PartitionBoundInfo boundinfo = pdesc->boundinfo;
 
-		if (val->constisnull)
-			list_has_null = true;
-		else
-			arrelems = lappend(arrelems, copyObject(val));
+		if (boundinfo)
+		{
+			ndatums = boundinfo->ndatums;
+
+			if (partition_bound_accepts_nulls(boundinfo))
+				list_has_null = true;
+		}
+
+		/*
+		 * If default is the only partition, there need not be any partition
+		 * constraint on it.
+		 */
+		if (ndatums == 0 && !list_has_null)
+			return NIL;
+
+		for (i = 0; i < ndatums; i++)
+		{
+			Const	   *val;
+
+			/* Construct const from datum */
+			val = makeConst(key->parttypid[0],
+							key->parttypmod[0],
+							key->parttypcoll[0],
+							key->parttyplen[0],
+							*boundinfo->datums[i],
+							false,	/* isnull */
+							key->parttypbyval[0]);
+
+			arrelems = lappend(arrelems, val);
+		}
+	}
+	else
+	{
+		/*
+		 * Create list of Consts for the allowed values, excluding any nulls.
+		 */
+		foreach(cell, spec->listdatums)
+		{
+			Const	   *val = castNode(Const, lfirst(cell));
+
+			if (val->constisnull)
+				list_has_null = true;
+			else
+				arrelems = lappend(arrelems, copyObject(val));
+		}
 	}
 
 	if (arrelems)
@@ -1369,6 +1496,25 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 			result = list_make1(nulltest);
 	}
 
+	/*
+	 * In case of the default partition, the constraint is of the form
+	 * "!(result)" i.e. one of the following two forms:
+	 *
+	 * 1. NOT ((keycol IS NULL) OR (keycol = ANY (arr)))
+	 *
+	 * 2. NOT ((keycol IS NOT NULL) AND (keycol = ANY (arr)))
+	 *
+	 * Note that, in general, applying NOT to a constraint expression doesn't
+	 * necessarily invert the set of rows it accepts, because NOT (NULL) is
+	 * NULL.  However, the partition constraints we construct here never
+	 * evaluate to NULL, so applying NOT works as intended.
+	 */
+	if (spec->is_default)
+	{
+		result = list_make1(make_ands_explicit(result));
+		result = list_make1(makeBoolExpr(NOT_EXPR, result, -1));
+	}
+
 	return result;
 }
 
@@ -1425,6 +1571,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
  *
@@ -1463,11 +1656,17 @@ 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 default is the only partition, then this function returns NIL. In case of
+ * non-default default partition, we return a single-member list with the
+ * constatnt TRUE when the result is empty.
+ *
  */
 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,
@@ -1478,10 +1677,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,
@@ -1491,44 +1690,72 @@ 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 it is the recursive call for default, we skip the get_range_nulltest
+	 * to avoid accumulating the NullTest on the same keys for each partition.
+	 */
+	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
@@ -1750,9 +1977,16 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 						 ? makeBoolExpr(OR_EXPR, upper_or_arms, -1)
 						 : linitial(upper_or_arms));
 
-	/* As noted above, caller expects the list to be non-empty. */
+	/*
+	 * As noted above, for non-default, we return list with constant TRUE. If
+	 * the result is NIL during the recursive call for default, it implies
+	 * this is the only other partition which can hold every value of the key
+	 * except NULL. Hence we return the NullTest result skipped earlier.
+	 */
 	if (result == NIL)
-		result = list_make1(makeBoolConst(true, false));
+		result = for_default
+			? get_range_nulltest(key)
+			: list_make1(makeBoolConst(true, false));
 
 	return result;
 }
@@ -1926,8 +2160,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;
@@ -1971,27 +2204,10 @@ 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;
-				}
-			}
-		}
-
 		/*
-		 * A null partition key is only acceptable if null-accepting list
-		 * partition exists.
+		 * If this is a null partition key, route it to the null-accepting
+		 * partition. Otherwise, route by searching the array of partition
+		 * bounds.
 		 */
 		cur_index = -1;
 		if (isnull[0] && partition_bound_accepts_nulls(partdesc->boundinfo))
@@ -2029,11 +2245,17 @@ get_partition_for_tuple(PartitionDispatch *pd,
 		}
 
 		/*
-		 * cur_index < 0 means we failed to find a partition of this parent.
-		 * cur_index >= 0 means we either found the leaf partition, or the
-		 * next parent to find a partition of.
+		 * cur_index < 0 means we could not find a non-default partition of
+		 * this parent. cur_index >= 0 means we either found the leaf
+		 * partition, or the next parent to find a partition of.
+		 *
+		 * If we couldn't find a non-default partition check if the default
+		 * partition exists, if it does, get its index.
 		 */
 		if (cur_index < 0)
+			cur_index = partdesc->boundinfo->default_index;
+
+		if (cur_index < 0)
 		{
 			result = -1;
 			*failed_at = parent;
@@ -2085,6 +2307,8 @@ make_one_range_bound(PartitionKey key, int index, List *datums, bool lower)
 	ListCell   *lc;
 	int			i;
 
+	Assert(datums != NIL);
+
 	bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
 	bound->index = index;
 	bound->datums = (Datum *) palloc0(key->partnatts * sizeof(Datum));
@@ -2321,3 +2545,58 @@ partition_bound_bsearch(PartitionKey key, PartitionBoundInfo boundinfo,
 
 	return lo;
 }
+
+/*
+ * get_default_partition_oid
+ *
+ * If the given relation has a default partition return the OID of the default
+ * partition, otherwise return InvalidOid.
+ */
+Oid
+get_default_partition_oid(Oid parentId)
+{
+	HeapTuple	tuple;
+	Oid			defaultPartId = InvalidOid;
+
+	tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(parentId));
+
+	if (HeapTupleIsValid(tuple))
+	{
+		Form_pg_partitioned_table part_table_form;
+
+		part_table_form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+		defaultPartId = part_table_form->partdefid;
+	}
+
+	ReleaseSysCache(tuple);
+	return defaultPartId;
+}
+
+/*
+ * update_default_partition_oid
+ *
+ * Updates the pg_partition_table catalog partdefid field for the given parent
+ * with the given default partition oid.
+ */
+void
+update_default_partition_oid(Oid parentId, Oid defaultPartId)
+{
+	HeapTuple	tuple;
+	Relation	pg_partitioned_table;
+	Form_pg_partitioned_table part_table_form;
+
+	pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCacheCopy1(PARTRELID, ObjectIdGetDatum(parentId));
+
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of relation %u",
+			 parentId);
+
+	part_table_form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+	part_table_form->partdefid = defaultPartId;
+	CatalogTupleUpdate(pg_partitioned_table, &tuple->t_self, tuple);
+
+	heap_freetuple(tuple);
+	heap_close(pg_partitioned_table, RowExclusiveLock);
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 0fb8238..9829616 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -774,7 +774,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	{
 		PartitionBoundSpec *bound;
 		ParseState *pstate;
-		Oid			parentId = linitial_oid(inheritOids);
+		Oid			parentId = linitial_oid(inheritOids),
+					defaultPartOid;
 		Relation	parent;
 
 		/* Already have strong enough lock on the parent */
@@ -790,6 +791,17 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 					 errmsg("\"%s\" is not partitioned",
 							RelationGetRelationName(parent))));
 
+		/*
+		 * A table cannot be created as a partition of a parent already having
+		 * a default partition.
+		 */
+		defaultPartOid = get_default_partition_oid(RelationGetRelid(parent));
+		if (OidIsValid(defaultPartOid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("cannot add a new partition to table \"%s\" having a default partition",
+							RelationGetRelationName(parent))));
+
 		/* Tranform the bound values */
 		pstate = make_parsestate(NULL);
 		pstate->p_sourcetext = queryString;
@@ -806,6 +818,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		/* Update the pg_class entry. */
 		StorePartitionBound(rel, parent, bound);
 
+		/* Update the default partition oid */
+		if (bound->is_default)
+			update_default_partition_oid(RelationGetRelid(parent), relationId);
+
 		heap_close(parent, NoLock);
 
 		/*
@@ -13627,6 +13643,15 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	ObjectAddress address;
 	const char *trigger_name;
 	bool		found_whole_row;
+	Oid			defaultPartOid;
+
+	/* A partition cannot be attached if there exists a default partition */
+	defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
+	if (OidIsValid(defaultPartOid))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot attach a new partition to table \"%s\" having a default partition",
+						RelationGetRelationName(rel))));
 
 	attachrel = heap_openrv(cmd->name, AccessExclusiveLock);
 
@@ -13783,6 +13808,11 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	/* OK to create inheritance.  Rest of the checks performed there */
 	CreateInheritance(attachrel, rel);
 
+	/* Update the default partition oid */
+	if (cmd->bound->is_default)
+		update_default_partition_oid(RelationGetRelid(rel),
+									 RelationGetRelid(attachrel));
+
 	/*
 	 * Check that the new partition's bound is valid and does not overlap any
 	 * of existing partitions of the parent - note that it does not return on
@@ -13851,8 +13881,25 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 				new_null[Natts_pg_class],
 				new_repl[Natts_pg_class];
 	ObjectAddress address;
+	Oid			defaultPartOid;
 
-	partRel = heap_openrv(name, AccessShareLock);
+	/*
+	 * We must lock the default partition, for the same reasons explained in
+	 * heap_drop_with_catalog().
+	 */
+	defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
+	if (OidIsValid(defaultPartOid))
+		LockRelationOid(defaultPartOid, AccessExclusiveLock);
+
+	/*
+	 * We already hold an exclusive lock on the default partition that is
+	 * going to be held until the transaction is commited, hence need to
+	 * acquire a shared lock only if the partition being detached is not the
+	 * default partition.
+	 */
+	partRel = heap_openrv(name, NoLock);
+	if (RelationGetRelid(partRel) != defaultPartOid)
+		LockRelationOid(RelationGetRelid(partRel), AccessShareLock);
 
 	/* All inheritance related checks are performed within the function */
 	RemoveInheritance(partRel, rel);
@@ -13882,6 +13929,24 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 	heap_freetuple(newtuple);
 	heap_close(classRel, RowExclusiveLock);
 
+	if (OidIsValid(defaultPartOid))
+	{
+		/*
+		 * If the detach relation is the default partition itself, invalidate
+		 * its entry in pg_partitioned_table.
+		 */
+		if (RelationGetRelid(partRel) == defaultPartOid)
+			update_default_partition_oid(RelationGetRelid(rel), InvalidOid);
+		else
+		{
+			/*
+			 * We must invalidate default partition's relcache, for the same
+			 * reasons explained in heap_drop_with_catalog().
+			 */
+			CacheInvalidateRelcacheByRelid(defaultPartOid);
+		}
+	}
+
 	/*
 	 * Invalidate the parent's relcache so that the partition is no longer
 	 * included in its partition descriptor.
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index f9ddf4e..822e7f8 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4449,6 +4449,7 @@ _copyPartitionBoundSpec(const PartitionBoundSpec *from)
 	PartitionBoundSpec *newnode = makeNode(PartitionBoundSpec);
 
 	COPY_SCALAR_FIELD(strategy);
+	COPY_SCALAR_FIELD(is_default);
 	COPY_NODE_FIELD(listdatums);
 	COPY_NODE_FIELD(lowerdatums);
 	COPY_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8d92c03..64ecfff 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2838,6 +2838,7 @@ static bool
 _equalPartitionBoundSpec(const PartitionBoundSpec *a, const PartitionBoundSpec *b)
 {
 	COMPARE_SCALAR_FIELD(strategy);
+	COMPARE_SCALAR_FIELD(is_default);
 	COMPARE_NODE_FIELD(listdatums);
 	COMPARE_NODE_FIELD(lowerdatums);
 	COMPARE_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 9ee3e23..b83d919 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3573,6 +3573,7 @@ _outPartitionBoundSpec(StringInfo str, const PartitionBoundSpec *node)
 	WRITE_NODE_TYPE("PARTITIONBOUNDSPEC");
 
 	WRITE_CHAR_FIELD(strategy);
+	WRITE_BOOL_FIELD(is_default);
 	WRITE_NODE_FIELD(listdatums);
 	WRITE_NODE_FIELD(lowerdatums);
 	WRITE_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 67b9e19..fbf8330 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2390,6 +2390,7 @@ _readPartitionBoundSpec(void)
 	READ_LOCALS(PartitionBoundSpec);
 
 	READ_CHAR_FIELD(strategy);
+	READ_BOOL_FIELD(is_default);
 	READ_NODE_FIELD(listdatums);
 	READ_NODE_FIELD(lowerdatums);
 	READ_NODE_FIELD(upperdatums);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7d0de99..1db35e2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -575,7 +575,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>			part_strategy
 %type <partelem>	part_elem
 %type <list>		part_params
-%type <partboundspec> ForValues
+%type <partboundspec> PartitionBoundSpec
 %type <node>		partbound_datum PartitionRangeDatum
 %type <list>		partbound_datum_list range_datum_list
 
@@ -1980,7 +1980,7 @@ alter_table_cmds:
 
 partition_cmd:
 			/* ALTER TABLE <name> ATTACH PARTITION <table_name> FOR VALUES */
-			ATTACH PARTITION qualified_name ForValues
+			ATTACH PARTITION qualified_name PartitionBoundSpec
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
 					PartitionCmd *cmd = makeNode(PartitionCmd);
@@ -2619,13 +2619,14 @@ alter_identity_column_option:
 				}
 		;
 
-ForValues:
+PartitionBoundSpec:
 			/* a LIST partition */
 			FOR VALUES IN_P '(' partbound_datum_list ')'
 				{
 					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
 
 					n->strategy = PARTITION_STRATEGY_LIST;
+					n->is_default = false;
 					n->listdatums = $5;
 					n->location = @3;
 
@@ -2638,12 +2639,24 @@ ForValues:
 					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
 
 					n->strategy = PARTITION_STRATEGY_RANGE;
+					n->is_default = false;
 					n->lowerdatums = $5;
 					n->upperdatums = $9;
 					n->location = @3;
 
 					$$ = n;
 				}
+
+			/* a DEFAULT partition */
+			| DEFAULT
+				{
+					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+					n->is_default = true;
+					n->location = @1;
+
+					$$ = n;
+				}
 		;
 
 partbound_datum:
@@ -3114,7 +3127,7 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
-			OptTypedTableElementList ForValues OptPartitionSpec OptWith
+			OptTypedTableElementList PartitionBoundSpec OptPartitionSpec OptWith
 			OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
@@ -3133,7 +3146,7 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
-			qualified_name OptTypedTableElementList ForValues OptPartitionSpec
+			qualified_name OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
 			OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
@@ -4848,7 +4861,7 @@ CreateForeignTableStmt:
 					$$ = (Node *) n;
 				}
 		| CREATE FOREIGN TABLE qualified_name
-			PARTITION OF qualified_name OptTypedTableElementList ForValues
+			PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
 			SERVER name create_generic_options
 				{
 					CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
@@ -4869,7 +4882,7 @@ CreateForeignTableStmt:
 					$$ = (Node *) n;
 				}
 		| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
-			PARTITION OF qualified_name OptTypedTableElementList ForValues
+			PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
 			SERVER name create_generic_options
 				{
 					CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 2058679..e285964 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -60,6 +61,7 @@
 #include "parser/parse_utilcmd.h"
 #include "parser/parser.h"
 #include "rewrite/rewriteManip.h"
+#include "storage/lmgr.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
@@ -3307,6 +3309,18 @@ transformPartitionBound(ParseState *pstate, Relation parent,
 	/* Avoid scribbling on input */
 	result_spec = copyObject(spec);
 
+	if (spec->is_default)
+	{
+		/*
+		 * In case of the default partition, parser had no way to identify the
+		 * partition strategy. Assign the parent's strategy to the default
+		 * partition bound spec.
+		 */
+		result_spec->strategy = strategy;
+
+		return result_spec;
+	}
+
 	if (strategy == PARTITION_STRATEGY_LIST)
 	{
 		ListCell   *cell;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 87aba5f..21ef0eb 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8696,10 +8696,17 @@ get_rule_expr(Node *node, deparse_context *context,
 		case T_PartitionBoundSpec:
 			{
 				PartitionBoundSpec *spec = (PartitionBoundSpec *) node;
+				char		strategy = spec->strategy;
 				ListCell   *cell;
 				char	   *sep;
 
-				switch (spec->strategy)
+				if (spec->is_default)
+				{
+					appendStringInfoString(buf, "DEFAULT");
+					break;
+				}
+
+				switch (strategy)
 				{
 					case PARTITION_STRATEGY_LIST:
 						Assert(spec->listdatums != NIL);
@@ -8731,7 +8738,7 @@ get_rule_expr(Node *node, deparse_context *context,
 
 					default:
 						elog(ERROR, "unrecognized partition strategy: %d",
-							 (int) spec->strategy);
+							 (int) strategy);
 						break;
 				}
 			}
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index f6049cc..ce1bd3b 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1891,7 +1891,7 @@ describeOneTableDetails(const char *schemaname,
 			parent_name = PQgetvalue(result, 0, 0);
 			partdef = PQgetvalue(result, 0, 1);
 
-			if (PQnfields(result) == 3)
+			if (PQnfields(result) == 3 && !PQgetisnull(result, 0, 2))
 				partconstraintdef = PQgetvalue(result, 0, 2);
 
 			printfPQExpBuffer(&tmpbuf, _("Partition of: %s %s"), parent_name,
@@ -1900,8 +1900,12 @@ describeOneTableDetails(const char *schemaname,
 
 			if (partconstraintdef)
 			{
-				printfPQExpBuffer(&tmpbuf, _("Partition constraint: %s"),
-								  partconstraintdef);
+				/* If there isn't any constraint, show that explicitly */
+				if (partconstraintdef[0] == '\0')
+					printfPQExpBuffer(&tmpbuf, _("No partition constraint"));
+				else
+					printfPQExpBuffer(&tmpbuf, _("Partition constraint: %s"),
+									  partconstraintdef);
 				printTableAddFooter(&cont, tmpbuf.data);
 			}
 
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 7959f9a..df147d0 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2050,7 +2050,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("ATTACH", "PARTITION", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 	else if (TailMatches2("FOR", "VALUES"))
 		COMPLETE_WITH_LIST2("FROM (", "IN (");
 
@@ -2489,7 +2489,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("PARTITION", "OF", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 
 /* CREATE TABLESPACE */
 	else if (Matches3("CREATE", "TABLESPACE", MatchAny))
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 2283c67..1dd70f2 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -99,4 +99,7 @@ extern int get_partition_for_tuple(PartitionDispatch *pd,
 						EState *estate,
 						PartitionDispatchData **failed_at,
 						TupleTableSlot **failed_slot);
+extern Oid	get_default_partition_oid(Oid parentId);
+extern void update_default_partition_oid(Oid parentId, Oid defaultPartId);
+
 #endif							/* PARTITION_H */
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
index 38d64d6..525e541 100644
--- a/src/include/catalog/pg_partitioned_table.h
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -32,6 +32,8 @@ CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
 	Oid			partrelid;		/* partitioned table oid */
 	char		partstrat;		/* partitioning strategy */
 	int16		partnatts;		/* number of partition key columns */
+	Oid			partdefid;		/* default partition oid; InvalidOid if there
+								 * isn't one */
 
 	/*
 	 * variable-length fields start here, but we allow direct access to
@@ -62,13 +64,14 @@ typedef FormData_pg_partitioned_table *Form_pg_partitioned_table;
  *		compiler constants for pg_partitioned_table
  * ----------------
  */
-#define Natts_pg_partitioned_table				7
+#define Natts_pg_partitioned_table				8
 #define Anum_pg_partitioned_table_partrelid		1
 #define Anum_pg_partitioned_table_partstrat		2
 #define Anum_pg_partitioned_table_partnatts		3
-#define Anum_pg_partitioned_table_partattrs		4
-#define Anum_pg_partitioned_table_partclass		5
-#define Anum_pg_partitioned_table_partcollation 6
-#define Anum_pg_partitioned_table_partexprs		7
+#define Anum_pg_partitioned_table_partdefid		4
+#define Anum_pg_partitioned_table_partattrs		5
+#define Anum_pg_partitioned_table_partclass		6
+#define Anum_pg_partitioned_table_partcollation 7
+#define Anum_pg_partitioned_table_partexprs		8
 
 #endif							/* PG_PARTITIONED_TABLE_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 5f2a4a7..6e05b79 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -797,6 +797,7 @@ typedef struct PartitionBoundSpec
 	NodeTag		type;
 
 	char		strategy;		/* see PARTITION_STRATEGY codes above */
+	bool		is_default;		/* is it a default partition bound? */
 
 	/* Partitioning info for LIST strategy: */
 	List	   *listdatums;		/* List of Consts (or A_Consts in raw tree) */
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index ed03cb9..053e472 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3273,6 +3273,14 @@ SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::reg
 CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
 ERROR:  partition "fail_part" would overlap partition "part_1"
+-- check that an existing table can be attached as a default partition
+CREATE TABLE def_part (LIKE list_parted INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION def_part DEFAULT;
+-- check attaching default partition fails if a default partition already
+-- exists
+CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT;
+ERROR:  cannot attach a new partition to table "list_parted" having a default partition
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
 	a int,
@@ -3286,6 +3294,15 @@ ERROR:  partition constraint is violated by some row
 -- should be ok after deleting the bad row
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+-- check partition cannot be attached if default exists
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
+INSERT INTO list_parted2_def VALUES (11, 'z');
+CREATE TABLE part_3 (LIKE list_parted2);
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+ERROR:  cannot attach a new partition to table "list_parted2" having a default partition
+-- should be ok after detaching a default partition
+ALTER TABLE list_parted2 DETACH PARTITION list_parted2_def;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
 CREATE TABLE part_3_4 (
@@ -3326,6 +3343,12 @@ 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;
+-- New partition cannot be attached if a default partition exists
+CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS);
+ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT;
+ERROR:  cannot attach a new partition to table "range_parted" having a default partition
 -- check that leaf partitions are scanned when attaching a partitioned
 -- table
 CREATE TABLE part_5 (
@@ -3514,6 +3537,7 @@ ALTER TABLE list_parted2 ALTER COLUMN b TYPE text;
 ERROR:  cannot alter type of column named in partition key
 -- cleanup
 DROP TABLE list_parted, list_parted2, range_parted;
+DROP TABLE fail_def_part;
 -- more tests for certain multi-level partitioning scenarios
 create table p (a int, b int) partition by range (a, b);
 create table p1 (b int, a int not null) partition by range (b);
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index babda89..4f79612 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -467,6 +467,13 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
 ERROR:  invalid bound specification for a list partition
 LINE 1: ...BLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) T...
                                                              ^
+-- check default partition cannot be created more than once
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
+ERROR:  cannot add a new partition to table "list_parted" having a default partition
+-- check that no partition can be created after default partition
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (3);
+ERROR:  cannot add a new partition to table "list_parted" having a default partition
 -- specified literal can't be cast to the partition column data type
 CREATE TABLE bools (
 	a bool
@@ -585,6 +592,11 @@ 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;
+-- New partition cannot be created if a default partition exists
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90);
+ERROR:  cannot add a new partition to table "range_parted2" having a default partition
 -- now check for multi-column range partition key
 CREATE TABLE range_parted3 (
 	a int,
@@ -603,6 +615,8 @@ ERROR:  partition "fail_part" would overlap partition "part12"
 -- more specific ranges
 CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, minvalue) TO (1, maxvalue);
 ERROR:  partition "fail_part" would overlap partition "part10"
+-- check default partition can be added to multi-column range partitioned table
+CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
 -- check schema propagation from parent
 CREATE TABLE parted (
 	a text,
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index e159d62..48f5292 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -219,17 +219,66 @@ insert into part_null values (null, 0);
 create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b);
 create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
 create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
+-- test default partition
+create table part_default partition of list_parted default;
+-- Negative test: a row, which would fit in other partition, does not fit
+-- default partition, even when inserted directly
+insert into part_default values ('aa', 2);
+ERROR:  new row for relation "part_default" violates partition constraint
+DETAIL:  Failing row contains (aa, 2).
+insert into part_default values (null, 2);
+ERROR:  new row for relation "part_default" violates partition constraint
+DETAIL:  Failing row contains (null, 2).
+-- ok
+insert into part_default values ('Zz', 2);
+-- test if default partition works as expected for multi-level partitioned
+-- table as well as when default partition itself is further partitioned
+drop table part_default;
+create table part_xx_yy partition of list_parted for values in ('xx', 'yy') partition by list (a);
+create table part_xx_yy_p1 partition of part_xx_yy for values in ('xx');
+create table part_xx_yy_defpart partition of part_xx_yy default;
+create table part_default partition of list_parted default partition by range(b);
+create table part_default_p1 partition of part_default for values from (20) to (30);
+create table part_default_p2 partition of part_default for values from (30) to (40);
 -- fail
 insert into part_ee_ff1 values ('EE', 11);
 ERROR:  new row for relation "part_ee_ff1" violates partition constraint
 DETAIL:  Failing row contains (EE, 11).
+insert into part_default_p2 values ('gg', 43);
+ERROR:  new row for relation "part_default_p2" violates partition constraint
+DETAIL:  Failing row contains (gg, 43).
 -- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
 insert into part_ee_ff1 values ('cc', 1);
 ERROR:  new row for relation "part_ee_ff1" violates partition constraint
 DETAIL:  Failing row contains (cc, 1).
+insert into part_default values ('gg', 43);
+ERROR:  no partition of relation "part_default" found for row
+DETAIL:  Partition key of the failing row contains (b) = (43).
 -- ok
 insert into part_ee_ff1 values ('ff', 1);
 insert into part_ee_ff2 values ('ff', 11);
+insert into part_default_p1 values ('cd', 25);
+insert into part_default_p2 values ('de', 35);
+insert into list_parted values ('ab', 21);
+insert into list_parted values ('xx', 1);
+insert into list_parted values ('yy', 2);
+select tableoid::regclass, * from list_parted;
+      tableoid      | a  | b  
+--------------------+----+----
+ part_cc_dd         | cC |  1
+ part_ee_ff1        | ff |  1
+ part_ee_ff2        | ff | 11
+ part_xx_yy_p1      | xx |  1
+ part_xx_yy_defpart | yy |  2
+ part_null          |    |  0
+ part_default_p1    | cd | 25
+ part_default_p1    | ab | 21
+ part_default_p2    | de | 35
+(9 rows)
+
+-- drop default partition, as we need to add some more partitions to test tuple
+-- routing
+drop table part_default;
 -- Check tuple routing for partitioned tables
 -- fail
 insert into range_parted values ('a', 0);
@@ -249,6 +298,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  
 ----------+---+----
@@ -258,7 +319,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);
@@ -274,17 +340,19 @@ DETAIL:  Partition key of the failing row contains (b) = (0).
 insert into list_parted values ('EE', 1);
 insert into part_ee_ff values ('EE', 10);
 select tableoid::regclass, * from list_parted;
-  tableoid   | a  | b  
--------------+----+----
- part_aa_bb  | aA |   
- part_cc_dd  | cC |  1
- part_ee_ff1 | ff |  1
- part_ee_ff1 | EE |  1
- part_ee_ff2 | ff | 11
- part_ee_ff2 | EE | 10
- part_null   |    |  0
- part_null   |    |  1
-(8 rows)
+      tableoid      | a  | b  
+--------------------+----+----
+ part_aa_bb         | aA |   
+ part_cc_dd         | cC |  1
+ part_ee_ff1        | ff |  1
+ part_ee_ff1        | EE |  1
+ part_ee_ff2        | ff | 11
+ part_ee_ff2        | EE | 10
+ part_xx_yy_p1      | xx |  1
+ part_xx_yy_defpart | yy |  2
+ part_null          |    |  0
+ part_null          |    |  1
+(10 rows)
 
 -- some more tests to exercise tuple-routing with multi-level partitioning
 create table part_gg partition of list_parted for values in ('gg') partition by range (b);
@@ -316,6 +384,23 @@ select tableoid::regclass::text, a, min(b) as min_b, max(b) as max_b from list_p
 
 -- cleanup
 drop table range_parted, list_parted;
+-- test that a default partition added as the first partition accepts any value
+-- including null
+create table list_parted (a int) partition by list (a);
+create table part_default partition of list_parted default;
+insert into part_default values (null);
+insert into part_default values (1);
+insert into part_default values (-1);
+select tableoid::regclass, a from list_parted;
+   tableoid   | a  
+--------------+----
+ part_default |   
+ part_default |  1
+ part_default | -1
+(3 rows)
+
+-- cleanup
+drop table list_parted;
 -- more tests for certain multi-level partitioning scenarios
 create table mlparted (a int, b int) partition by range (a, b);
 create table mlparted1 (b int not null, a int not null) partition by range ((b+0));
@@ -425,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/plancache.out b/src/test/regress/expected/plancache.out
index 3f3db33..fb90357 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -252,3 +252,25 @@ NOTICE:  3
  
 (1 row)
 
+-- Check that addition or removal of any partition is correctly dealt with by
+-- default partition table when it is being used in prepared statement.
+create table list_parted (a int) partition by list(a);
+create table list_part_null partition of list_parted for values in (null);
+create table list_part_1 partition of list_parted for values in (1);
+create table list_part_def partition of list_parted default;
+prepare pstmt_def_insert (int) as insert into list_part_def values($1);
+-- should fail
+execute pstmt_def_insert(null);
+ERROR:  new row for relation "list_part_def" violates partition constraint
+DETAIL:  Failing row contains (null).
+execute pstmt_def_insert(1);
+ERROR:  new row for relation "list_part_def" violates partition constraint
+DETAIL:  Failing row contains (1).
+alter table list_parted detach partition list_part_null;
+-- should be ok
+execute pstmt_def_insert(null);
+drop table list_part_1;
+-- should be ok
+execute pstmt_def_insert(1);
+drop table list_parted, list_part_null;
+deallocate pstmt_def_insert;
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 9366f04..cfb6aa8 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -218,5 +218,29 @@ 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
+) partition by list (a);
+create table list_part1  partition of list_parted for values in ('a', 'b');
+create table list_default partition of list_parted default;
+insert into list_part1 values ('a', 1);
+insert into list_default values ('d', 10);
+-- fail
+update list_default set a = 'a' where a = 'd';
+ERROR:  new row for relation "list_default" violates partition constraint
+DETAIL:  Failing row contains (a, 10).
+-- ok
+update list_default set a = 'x' where a = 'd';
 -- cleanup
 drop table range_parted;
+drop table list_parted;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 9a20dd1..b6aa9e3 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2095,6 +2095,13 @@ SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::reg
 -- check that the new partition won't overlap with an existing partition
 CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+-- check that an existing table can be attached as a default partition
+CREATE TABLE def_part (LIKE list_parted INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION def_part DEFAULT;
+-- check attaching default partition fails if a default partition already
+-- exists
+CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT;
 
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
@@ -2111,6 +2118,15 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 
+-- check partition cannot be attached if default exists
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
+INSERT INTO list_parted2_def VALUES (11, 'z');
+CREATE TABLE part_3 (LIKE list_parted2);
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+-- should be ok after detaching a default partition
+ALTER TABLE list_parted2 DETACH PARTITION list_parted2_def;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
 CREATE TABLE part_3_4 (
@@ -2156,6 +2172,13 @@ 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;
+
+-- New partition cannot be attached if a default partition exists
+CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS);
+ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT;
+
 -- check that leaf partitions are scanned when attaching a partitioned
 -- table
 CREATE TABLE part_5 (
@@ -2311,6 +2334,7 @@ ALTER TABLE list_parted2 ALTER COLUMN b TYPE text;
 
 -- cleanup
 DROP TABLE list_parted, list_parted2, range_parted;
+DROP TABLE fail_def_part;
 
 -- more tests for certain multi-level partitioning scenarios
 create table p (a int, b int) partition by range (a, b);
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 1c0ce927..24f005e 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -447,6 +447,12 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
 -- trying to specify range for list partitioned table
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
 
+-- check default partition cannot be created more than once
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
+-- check that no partition can be created after default partition
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (3);
+
 -- specified literal can't be cast to the partition column data type
 CREATE TABLE bools (
 	a bool
@@ -546,6 +552,12 @@ 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;
+
+-- New partition cannot be created if a default partition exists
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90);
+
 -- now check for multi-column range partition key
 CREATE TABLE range_parted3 (
 	a int,
@@ -565,6 +577,9 @@ CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1,
 -- more specific ranges
 CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, minvalue) TO (1, maxvalue);
 
+-- check default partition can be added to multi-column range partitioned table
+CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
+
 -- check schema propagation from parent
 
 CREATE TABLE parted (
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 6f17872..7fc5688 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -132,13 +132,42 @@ create table part_ee_ff partition of list_parted for values in ('ee', 'ff') part
 create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
 create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
 
+-- test default partition
+create table part_default partition of list_parted default;
+-- Negative test: a row, which would fit in other partition, does not fit
+-- default partition, even when inserted directly
+insert into part_default values ('aa', 2);
+insert into part_default values (null, 2);
+-- ok
+insert into part_default values ('Zz', 2);
+-- test if default partition works as expected for multi-level partitioned
+-- table as well as when default partition itself is further partitioned
+drop table part_default;
+create table part_xx_yy partition of list_parted for values in ('xx', 'yy') partition by list (a);
+create table part_xx_yy_p1 partition of part_xx_yy for values in ('xx');
+create table part_xx_yy_defpart partition of part_xx_yy default;
+create table part_default partition of list_parted default partition by range(b);
+create table part_default_p1 partition of part_default for values from (20) to (30);
+create table part_default_p2 partition of part_default for values from (30) to (40);
+
 -- fail
 insert into part_ee_ff1 values ('EE', 11);
+insert into part_default_p2 values ('gg', 43);
 -- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
 insert into part_ee_ff1 values ('cc', 1);
+insert into part_default values ('gg', 43);
 -- ok
 insert into part_ee_ff1 values ('ff', 1);
 insert into part_ee_ff2 values ('ff', 11);
+insert into part_default_p1 values ('cd', 25);
+insert into part_default_p2 values ('de', 35);
+insert into list_parted values ('ab', 21);
+insert into list_parted values ('xx', 1);
+insert into list_parted values ('yy', 2);
+select tableoid::regclass, * from list_parted;
+-- drop default partition, as we need to add some more partitions to test tuple
+-- routing
+drop table part_default;
 
 -- Check tuple routing for partitioned tables
 
@@ -154,8 +183,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');
@@ -188,6 +228,17 @@ select tableoid::regclass::text, a, min(b) as min_b, max(b) as max_b from list_p
 -- cleanup
 drop table range_parted, list_parted;
 
+-- test that a default partition added as the first partition accepts any value
+-- including null
+create table list_parted (a int) partition by list (a);
+create table part_default partition of list_parted default;
+insert into part_default values (null);
+insert into part_default values (1);
+insert into part_default values (-1);
+select tableoid::regclass, a from list_parted;
+-- cleanup
+drop table list_parted;
+
 -- more tests for certain multi-level partitioning scenarios
 create table mlparted (a int, b int) partition by range (a, b);
 create table mlparted1 (b int not null, a int not null) partition by range ((b+0));
@@ -274,6 +325,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/plancache.sql b/src/test/regress/sql/plancache.sql
index bc20861..a8e357c 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -156,3 +156,22 @@ end$$ language plpgsql;
 
 select cachebug();
 select cachebug();
+
+-- Check that addition or removal of any partition is correctly dealt with by
+-- default partition table when it is being used in prepared statement.
+create table list_parted (a int) partition by list(a);
+create table list_part_null partition of list_parted for values in (null);
+create table list_part_1 partition of list_parted for values in (1);
+create table list_part_def partition of list_parted default;
+prepare pstmt_def_insert (int) as insert into list_part_def values($1);
+-- should fail
+execute pstmt_def_insert(null);
+execute pstmt_def_insert(1);
+alter table list_parted detach partition list_part_null;
+-- should be ok
+execute pstmt_def_insert(null);
+drop table list_part_1;
+-- should be ok
+execute pstmt_def_insert(1);
+drop table list_parted, list_part_null;
+deallocate pstmt_def_insert;
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 6637119..1ce7a94 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -125,5 +125,28 @@ 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
+) partition by list (a);
+create table list_part1  partition of list_parted for values in ('a', 'b');
+create table list_default partition of list_parted default;
+insert into list_part1 values ('a', 1);
+insert into list_default values ('d', 10);
+
+-- fail
+update list_default set a = 'a' where a = 'd';
+-- ok
+update list_default set a = 'x' where a = 'd';
+
 -- cleanup
 drop table range_parted;
+drop table list_parted;
-- 
2.7.4

0003-Default-partition-extended-for-create-and-attach.patch0000664000175000017500000012510113153753555023217 0ustar  jeevanjeevanFrom 175a36785ea6fe40d8e2f0d53eae6eb94023cd18 Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Wed, 6 Sep 2017 16:35:31 +0530
Subject: [PATCH 3/5] Default partition extended for create and attach

1. This patch extends the previous patch in this series to allow a
new partition to be created or attached even when a default
partition exists.
2. Also, extends the regression tests to test this functionality.
in this series.
3. While adding a new partition we need to make sure that default
partition does not contain any rows that would fit in newly added
partition. So, in this patch adds a code to negate the partition
constraints of new partition so as these constraints form the part
of would be default partition constraints. Then the default
partition is scanned to check if there exist a row that does not
hold these negated partition constraints, if there exists such a
row error out.
4. While doing 3, to optimize the validation scan a check is done,
if the existing constraints on the default partition imply that it
will not contain any row that would belong to the new partition,
then the validation scan is skipped.

Jeevan Ladhe, some refactoring by Ashutosh bapat.
---
 src/backend/catalog/heap.c                 |  33 ++---
 src/backend/catalog/partition.c            | 188 ++++++++++++++++++++++++++++-
 src/backend/commands/tablecmds.c           | 112 +++++++++++++----
 src/include/catalog/partition.h            |   3 +
 src/include/commands/tablecmds.h           |   4 +
 src/test/regress/expected/alter_table.out  |  39 ++++--
 src/test/regress/expected/create_table.out |  22 ++--
 src/test/regress/expected/insert.out       |   8 +-
 src/test/regress/expected/plancache.out    |   4 +
 src/test/regress/sql/alter_table.sql       |  31 ++++-
 src/test/regress/sql/create_table.sql      |  17 ++-
 src/test/regress/sql/insert.sql            |   3 -
 src/test/regress/sql/plancache.sql         |   2 +
 13 files changed, 389 insertions(+), 77 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 2952d40..ef213b6 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1778,15 +1778,8 @@ heap_drop_with_catalog(Oid relid)
 		LockRelationOid(parentOid, AccessExclusiveLock);
 
 		/*
-		 * The partition constraint of the default partition depends on the
-		 * partition bounds of every other partition. It is possible that
-		 * other backend might be about to execute a query on the default
-		 * partition table and the query relies on previously cached default
-		 * partition constraints, which won't be correct after removal of a
-		 * partition. We must therefore take a table lock strong enough to
-		 * prevent all queries on the default partition from proceeding until
-		 * we commit and send out a shared-cache-inval notice that will make
-		 * them update their index lists. Note that we need not lock the
+		 * We must also lock the default partition, for the same reasons
+		 * explained in DefineRelation(). Note that we need not lock the
 		 * default partition if the table being dropped itself is the default
 		 * partition, because it is going to be locked further.
 		 */
@@ -1910,11 +1903,9 @@ heap_drop_with_catalog(Oid relid)
 	if (OidIsValid(parentOid))
 	{
 		/*
-		 * The partition constraint for the default partition depends on the
-		 * partition bounds of every other partition, so we must invalidate
-		 * the relcache entry for that partition every time a partition is
-		 * added or removed. If the default partition itself is dropped no
-		 * need to invalidate its relcache.
+		 * We must invalidate default partition's relcache, for the same
+		 * reasons explained in StorePartitionBound(). If the default
+		 * partition itself is dropped no need to invalidate its relcache.
 		 */
 		if (OidIsValid(defaultPartOid) && relid != defaultPartOid)
 			CacheInvalidateRelcacheByRelid(defaultPartOid);
@@ -3259,7 +3250,8 @@ RemovePartitionKeyByRelId(Oid relid)
  *		relispartition to true
  *
  * Also, invalidate the parent's relcache, so that the next rebuild will load
- * the new partition's info into its partition descriptor.
+ * the new partition's info into its partition descriptor.  If there is a
+ * default partition, we must invalidate its relcache entry as well.
  */
 void
 StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
@@ -3270,6 +3262,7 @@ StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
 	Datum		new_val[Natts_pg_class];
 	bool		new_null[Natts_pg_class],
 				new_repl[Natts_pg_class];
+	Oid			defaultPartOid;
 
 	/* Update pg_class tuple */
 	classRel = heap_open(RelationRelationId, RowExclusiveLock);
@@ -3307,5 +3300,15 @@ StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
 	heap_freetuple(newtuple);
 	heap_close(classRel, RowExclusiveLock);
 
+	/*
+	 * The partition constraint for the default partition depends on the
+	 * partition bounds of every other partition, so we must invalidate the
+	 * relcache entry for that partition every time a partition is added or
+	 * removed.
+	 */
+	defaultPartOid = get_default_partition_oid(RelationGetRelid(parent));
+	if (OidIsValid(defaultPartOid))
+		CacheInvalidateRelcacheByRelid(defaultPartOid);
+
 	CacheInvalidateRelcache(parent);
 }
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index ac51bca..782d5a4 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -29,6 +29,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_type.h"
+#include "commands/tablecmds.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -36,6 +37,7 @@
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planmain.h"
+#include "optimizer/prep.h"
 #include "optimizer/var.h"
 #include "rewrite/rewriteManip.h"
 #include "storage/lmgr.h"
@@ -86,6 +88,7 @@ typedef struct PartitionBoundInfoData
 } 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
@@ -707,12 +710,23 @@ check_new_partition_bound(char *relname, Relation parent,
 {
 	PartitionKey key = RelationGetPartitionKey(parent);
 	PartitionDesc partdesc = RelationGetPartitionDesc(parent);
+	PartitionBoundInfo boundinfo = partdesc->boundinfo;
 	ParseState *pstate = make_parsestate(NULL);
 	int			with = -1;
 	bool		overlap = false;
 
 	if (spec->is_default)
-		return;
+	{
+		if (boundinfo == NULL || !partition_bound_has_default(boundinfo))
+			return;
+
+		/* Default partition already exists, error out. */
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("partition \"%s\" conflicts with existing default partition \"%s\"",
+						relname, get_rel_name(partdesc->oids[boundinfo->default_index])),
+				 parser_errposition(pstate, spec->location)));
+	}
 
 	switch (key->strategy)
 	{
@@ -722,13 +736,13 @@ check_new_partition_bound(char *relname, Relation parent,
 
 				if (partdesc->nparts > 0)
 				{
-					PartitionBoundInfo boundinfo = partdesc->boundinfo;
 					ListCell   *cell;
 
 					Assert(boundinfo &&
 						   boundinfo->strategy == PARTITION_STRATEGY_LIST &&
 						   (boundinfo->ndatums > 0 ||
-							partition_bound_accepts_nulls(boundinfo)));
+							partition_bound_accepts_nulls(boundinfo) ||
+							partition_bound_has_default(boundinfo)));
 
 					foreach(cell, spec->listdatums)
 					{
@@ -793,8 +807,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
@@ -872,6 +888,139 @@ check_new_partition_bound(char *relname, Relation parent,
 }
 
 /*
+ * check_default_allows_bound
+ *
+ * This function checks if there exists a row in the default partition that
+ * fits in the new partition being added and throws an error if it finds one.
+ */
+void
+check_default_allows_bound(Relation parent, Relation default_rel,
+						   PartitionBoundSpec *new_spec)
+{
+	List	   *new_part_constraints;
+	List	   *def_part_constraints;
+	List	   *all_parts;
+	ListCell   *lc;
+
+	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);
+
+	/*
+	 * If the existing constraints on the default partition imply that it will
+	 * not contain any row that would belong to the new partition, we can
+	 * avoid scanning the default partition.
+	 */
+	if (PartConstraintImpliedByRelConstraint(default_rel, def_part_constraints))
+	{
+		ereport(INFO,
+				(errmsg("partition constraint for table \"%s\" is implied by existing constraints",
+						RelationGetRelationName(default_rel))));
+		return;
+	}
+
+	/*
+	 * Bad luck, scan the default partition and its subpartitions, and check
+	 * if any of the row does not satisfy the partition constraints that are
+	 * going to be imposed as additional constraints on default partition by
+	 * addition of the new partition.
+	 */
+	if (default_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		all_parts = find_all_inheritors(RelationGetRelid(default_rel),
+										AccessExclusiveLock, NULL);
+	else
+		all_parts = list_make1_oid(RelationGetRelid(default_rel));
+
+	foreach(lc, all_parts)
+	{
+		Oid			part_relid = lfirst_oid(lc);
+		Relation	part_rel;
+		Expr	   *constr;
+		Expr	   *partition_constraint;
+		EState	   *estate;
+		HeapTuple	tuple;
+		ExprState  *partqualstate = NULL;
+		Snapshot	snapshot;
+		TupleDesc	tupdesc;
+		ExprContext *econtext;
+		HeapScanDesc scan;
+		MemoryContext oldCxt;
+		TupleTableSlot *tupslot;
+
+		/* Lock already taken above. */
+		if (part_relid != RelationGetRelid(default_rel))
+			part_rel = heap_open(part_relid, NoLock);
+		else
+			part_rel = default_rel;
+
+		/*
+		 * Only RELKIND_RELATION relations (i.e. leaf partitions) need to be
+		 * scanned.
+		 */
+		if (part_rel->rd_rel->relkind != RELKIND_RELATION)
+		{
+			if (part_rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+				ereport(WARNING,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("skipped scanning foreign table \"%s\" which is a partition of default partition \"%s\"",
+								RelationGetRelationName(part_rel),
+								RelationGetRelationName(default_rel))));
+
+			if (RelationGetRelid(default_rel) != RelationGetRelid(part_rel))
+				heap_close(part_rel, NoLock);
+
+			continue;
+		}
+
+		tupdesc = CreateTupleDescCopy(RelationGetDescr(part_rel));
+		constr = linitial(def_part_constraints);
+		partition_constraint = (Expr *) map_partition_varattnos((List *) constr,
+																1, part_rel, parent, NULL);
+		estate = CreateExecutorState();
+
+		/* Build expression execution states for partition check quals */
+		partqualstate = ExecPrepareExpr(partition_constraint, estate);
+
+		econtext = GetPerTupleExprContext(estate);
+		snapshot = RegisterSnapshot(GetLatestSnapshot());
+		scan = heap_beginscan(part_rel, snapshot, 0, NULL);
+		tupslot = MakeSingleTupleTableSlot(tupdesc);
+
+		/*
+		 * Switch to per-tuple memory context and reset it for each tuple
+		 * produced, so we don't leak memory.
+		 */
+		oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		{
+			ExecStoreTuple(tuple, tupslot, InvalidBuffer, false);
+			econtext->ecxt_scantuple = tupslot;
+
+			if (!ExecCheck(partqualstate, econtext))
+				ereport(ERROR,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("updated partition constraint for default partition \"%s\" would be violated by some row",
+								RelationGetRelationName(default_rel))));
+
+			ResetExprContext(econtext);
+			CHECK_FOR_INTERRUPTS();
+		}
+
+		MemoryContextSwitchTo(oldCxt);
+		heap_endscan(scan);
+		UnregisterSnapshot(snapshot);
+		ExecDropSingleTupleTableSlot(tupslot);
+		FreeExecutorState(estate);
+
+		if (RelationGetRelid(default_rel) != RelationGetRelid(part_rel))
+			heap_close(part_rel, NoLock);	/* keep the lock until commit */
+	}
+}
+
+/*
  * get_partition_parent
  *
  * Returns inheritance parent of a partition by scanning pg_inherits
@@ -2600,3 +2749,32 @@ update_default_partition_oid(Oid parentId, Oid defaultPartId)
 	heap_freetuple(tuple);
 	heap_close(pg_partitioned_table, RowExclusiveLock);
 }
+
+/*
+ * get_default_part_validation_constraint
+ *
+ * This function returns negated constraint of new_part_constraints which
+ * would be an integral part of the default partition constraints after
+ * addition of the partition to which the new_part_constraints belongs.
+ */
+List *
+get_default_part_validation_constraint(List *new_part_constraints)
+{
+	Expr	   *defPartConstraint;
+
+	defPartConstraint = make_ands_explicit(new_part_constraints);
+
+	/*
+	 * Derieve the partition constraints of default partition by negating the
+	 * given partition constraints. The partition constraint never evaluates
+	 * to NULL, so negating it like this is safe.
+	 */
+	defPartConstraint = makeBoolExpr(NOT_EXPR,
+									 list_make1(defPartConstraint),
+									 -1);
+	defPartConstraint = (Expr *) eval_const_expressions(NULL,
+														(Node *) defPartConstraint);
+	defPartConstraint = canonicalize_qual(defPartConstraint);
+
+	return list_make1(defPartConstraint);
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 9829616..bd25070 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -168,6 +168,8 @@ typedef struct AlteredTableInfo
 	bool		chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
 	char		newrelpersistence;	/* if above is true */
 	Expr	   *partition_constraint;	/* for attach partition validation */
+	/* true, if is a default partition or a child of default partition */
+	bool		is_default_partition;
 	/* Objects to rebuild after completing ALTER TYPE operations */
 	List	   *changedConstraintOids;	/* OIDs of constraints to rebuild */
 	List	   *changedConstraintDefs;	/* string definitions of same */
@@ -473,11 +475,10 @@ static void CreateInheritance(Relation child_rel, Relation parent_rel);
 static void RemoveInheritance(Relation child_rel, Relation parent_rel);
 static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
 					  PartitionCmd *cmd);
-static bool PartConstraintImpliedByRelConstraint(Relation scanrel,
-									 List *partConstraint);
 static void ValidatePartitionConstraints(List **wqueue, Relation scanrel,
 							 List *scanrel_children,
-							 List *partConstraint);
+							 List *partConstraint,
+							 bool scanrel_is_default);
 static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
 
 
@@ -776,7 +777,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		ParseState *pstate;
 		Oid			parentId = linitial_oid(inheritOids),
 					defaultPartOid;
-		Relation	parent;
+		Relation	parent,
+					defaultRel;
 
 		/* Already have strong enough lock on the parent */
 		parent = heap_open(parentId, NoLock);
@@ -792,15 +794,28 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 							RelationGetRelationName(parent))));
 
 		/*
-		 * A table cannot be created as a partition of a parent already having
-		 * a default partition.
+		 * The partition constraint of the default partition depends on the
+		 * partition bounds of every other partition. It is possible that
+		 * other backend might be about to execute a query on the default
+		 * partition table and the query relies on previously cached default
+		 * partition constraints, which won't be correct after removal of a
+		 * partition. We must therefore take a table lock strong enough to
+		 * prevent all queries on the default partition from proceeding until
+		 * we commit and send out a shared-cache-inval notice that will make
+		 * them update their index lists.
+		 *
+		 * Order of locking: The relation being added won't be visible to
+		 * other backends until it is committed, hence here in
+		 * DefineRelation() the order of locking the default partition and the
+		 * relation being added does not matter. But at all other places we
+		 * need to lock the default relation before we lock the relation being
+		 * added or removed i.e. we should take the lock in same order at all
+		 * the places such that lock parent, lock default partition and then
+		 * lock the partition so as to avoid a deadlock.
 		 */
 		defaultPartOid = get_default_partition_oid(RelationGetRelid(parent));
 		if (OidIsValid(defaultPartOid))
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-					 errmsg("cannot add a new partition to table \"%s\" having a default partition",
-							RelationGetRelationName(parent))));
+			defaultRel = heap_open(defaultPartOid, AccessExclusiveLock);
 
 		/* Tranform the bound values */
 		pstate = make_parsestate(NULL);
@@ -815,6 +830,20 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		 */
 		check_new_partition_bound(relname, parent, bound);
 
+		/*
+		 * If the default partition exists, its partition constraints will
+		 * change after the addition of this new partition such that it won't
+		 * allow any row that qualifies for this new partition. So, check that
+		 * the existing data in the default partition satisfies the constraint
+		 * as it will exist after adding this partition.
+		 */
+		if (OidIsValid(defaultPartOid))
+		{
+			check_default_allows_bound(parent, defaultRel, bound);
+			/* Keep the lock until commit. */
+			heap_close(defaultRel, NoLock);
+		}
+
 		/* Update the pg_class entry. */
 		StorePartitionBound(rel, parent, bound);
 
@@ -4611,9 +4640,16 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 			}
 
 			if (partqualstate && !ExecCheck(partqualstate, econtext))
-				ereport(ERROR,
-						(errcode(ERRCODE_CHECK_VIOLATION),
-						 errmsg("partition constraint is violated by some row")));
+			{
+				if (tab->is_default_partition)
+					ereport(ERROR,
+							(errcode(ERRCODE_CHECK_VIOLATION),
+							 errmsg("updated partition constraint for default partition would be violated by some row")));
+				else
+					ereport(ERROR,
+							(errcode(ERRCODE_CHECK_VIOLATION),
+							 errmsg("partition constraint is violated by some row")));
+			}
 
 			/* Write the tuple out to the new relation */
 			if (newrel)
@@ -13467,7 +13503,7 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
  * Existing constraints includes its check constraints and column-level
  * NOT NULL constraints and partConstraint describes the partition constraint.
  */
-static bool
+bool
 PartConstraintImpliedByRelConstraint(Relation scanrel,
 									 List *partConstraint)
 {
@@ -13554,7 +13590,8 @@ PartConstraintImpliedByRelConstraint(Relation scanrel,
 static void
 ValidatePartitionConstraints(List **wqueue, Relation scanrel,
 							 List *scanrel_children,
-							 List *partConstraint)
+							 List *partConstraint,
+							 bool scanrel_is_default)
 {
 	bool		found_whole_row;
 	ListCell   *lc;
@@ -13616,6 +13653,7 @@ ValidatePartitionConstraints(List **wqueue, Relation scanrel,
 		/* Grab a work queue entry. */
 		tab = ATGetQueueEntry(wqueue, part_rel);
 		tab->partition_constraint = (Expr *) linitial(my_partconstr);
+		tab->is_default_partition = scanrel_is_default;
 
 		/* keep our lock until commit */
 		if (part_rel != scanrel)
@@ -13644,14 +13682,15 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	const char *trigger_name;
 	bool		found_whole_row;
 	Oid			defaultPartOid;
+	List	   *partBoundConstraint;
 
-	/* A partition cannot be attached if there exists a default partition */
+	/*
+	 * We must lock the default partition, for the same reasons explained in
+	 * DefineRelation().
+	 */
 	defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
 	if (OidIsValid(defaultPartOid))
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-				 errmsg("cannot attach a new partition to table \"%s\" having a default partition",
-						RelationGetRelationName(rel))));
+		LockRelationOid(defaultPartOid, AccessExclusiveLock);
 
 	attachrel = heap_openrv(cmd->name, AccessExclusiveLock);
 
@@ -13829,8 +13868,8 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	 * If the parent itself is a partition, make sure to include its
 	 * constraint as well.
 	 */
-	partConstraint = list_concat(get_qual_from_partbound(attachrel, rel,
-														 cmd->bound),
+	partBoundConstraint = get_qual_from_partbound(attachrel, rel, cmd->bound);
+	partConstraint = list_concat(partBoundConstraint,
 								 RelationGetPartitionQual(rel));
 
 	/* Skip validation if there are no constraints to validate. */
@@ -13853,7 +13892,30 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 
 		/* Validate partition constraints against the table being attached. */
 		ValidatePartitionConstraints(wqueue, attachrel, attachrel_children,
-									 partConstraint);
+									 partConstraint, false);
+
+		/*
+		 * Check whether default partition has a row that would fit the
+		 * partition being attached.
+		 */
+		defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
+		if (OidIsValid(defaultPartOid))
+		{
+			Relation	defaultrel;
+			List	   *defaultrel_children;
+			List	   *defPartConstraint;
+
+			/* We already have taken a lock on default partition. */
+			defaultrel = heap_open(defaultPartOid, NoLock);
+			defPartConstraint = get_default_part_validation_constraint(partBoundConstraint);
+			defaultrel_children = find_all_inheritors(defaultPartOid,
+													  AccessExclusiveLock, NULL);
+			ValidatePartitionConstraints(wqueue, defaultrel, defaultrel_children,
+										 defPartConstraint, true);
+
+			/* keep our lock until commit. */
+			heap_close(defaultrel, NoLock);
+		}
 	}
 
 	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachrel));
@@ -13885,7 +13947,7 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 
 	/*
 	 * We must lock the default partition, for the same reasons explained in
-	 * heap_drop_with_catalog().
+	 * DefineRelation().
 	 */
 	defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
 	if (OidIsValid(defaultPartOid))
@@ -13941,7 +14003,7 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 		{
 			/*
 			 * We must invalidate default partition's relcache, for the same
-			 * reasons explained in heap_drop_with_catalog().
+			 * reasons explained in StorePartitionBound().
 			 */
 			CacheInvalidateRelcacheByRelid(defaultPartOid);
 		}
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 1dd70f2..ee0cc15 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -101,5 +101,8 @@ extern int get_partition_for_tuple(PartitionDispatch *pd,
 						TupleTableSlot **failed_slot);
 extern Oid	get_default_partition_oid(Oid parentId);
 extern void update_default_partition_oid(Oid parentId, Oid defaultPartId);
+extern void check_default_allows_bound(Relation parent, Relation defaultRel,
+						   PartitionBoundSpec *new_spec);
+extern List *get_default_part_validation_constraint(List *new_part_constaints);
 
 #endif							/* PARTITION_H */
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index abd31b6..da3ff5d 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -18,6 +18,7 @@
 #include "catalog/dependency.h"
 #include "catalog/objectaddress.h"
 #include "nodes/parsenodes.h"
+#include "catalog/partition.h"
 #include "storage/lock.h"
 #include "utils/relcache.h"
 
@@ -87,4 +88,7 @@ extern void RangeVarCallbackOwnsTable(const RangeVar *relation,
 
 extern void RangeVarCallbackOwnsRelation(const RangeVar *relation,
 							 Oid relId, Oid oldRelId, void *noCatalogs);
+extern bool PartConstraintImpliedByRelConstraint(Relation scanrel,
+									 List *partConstraint);
+
 #endif							/* TABLECMDS_H */
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 053e472..b9d843b 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3280,7 +3280,7 @@ ALTER TABLE list_parted ATTACH PARTITION def_part DEFAULT;
 -- exists
 CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT;
-ERROR:  cannot attach a new partition to table "list_parted" having a default partition
+ERROR:  partition "fail_def_part" conflicts with existing default partition "def_part"
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
 	a int,
@@ -3294,14 +3294,14 @@ ERROR:  partition constraint is violated by some row
 -- should be ok after deleting the bad row
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
--- check partition cannot be attached if default exists
+-- check partition cannot be attached if default has some row for its values
 CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 INSERT INTO list_parted2_def VALUES (11, 'z');
 CREATE TABLE part_3 (LIKE list_parted2);
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
-ERROR:  cannot attach a new partition to table "list_parted2" having a default partition
--- should be ok after detaching a default partition
-ALTER TABLE list_parted2 DETACH PARTITION list_parted2_def;
+ERROR:  updated partition constraint for default partition would be violated by some row
+-- should be ok after deleting the bad row
+DELETE FROM list_parted2_def WHERE a = 11;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
@@ -3318,6 +3318,10 @@ ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
 ALTER TABLE part_3_4 ALTER a SET NOT NULL;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
 INFO:  partition constraint for table "part_3_4" is implied by existing constraints
+-- check if default partition scan skipped
+ALTER TABLE list_parted2_def ADD CONSTRAINT check_a CHECK (a IN (5, 6));
+CREATE TABLE part_55_66 PARTITION OF list_parted2 FOR VALUES IN (55, 66);
+INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
 -- check validation when attaching range partitions
 CREATE TABLE range_parted (
 	a int,
@@ -3345,10 +3349,17 @@ ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 2
 INFO:  partition constraint for table "part2" is implied by existing constraints
 -- Create default partition
 CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
--- New partition cannot be attached if a default partition exists
+-- 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:  cannot attach a new partition to table "range_parted" having a default partition
+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 (
@@ -3401,6 +3412,7 @@ ALTER TABLE part_7 ATTACH PARTITION part_7_a_null FOR VALUES IN ('a', null);
 INFO:  partition constraint for table "part_7_a_null" is implied by existing constraints
 ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
 INFO:  partition constraint for table "part_7" is implied by existing constraints
+INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
 -- Same example, but check this time that the constraint correctly detects
 -- violating rows
 ALTER TABLE list_parted2 DETACH PARTITION part_7;
@@ -3414,7 +3426,20 @@ SELECT tableoid::regclass, a, b FROM part_7 order by a;
 (2 rows)
 
 ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
+INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
 ERROR:  partition constraint is violated by some row
+-- check that leaf partitions of default partition are scanned when
+-- attaching a partitioned table.
+ALTER TABLE part_5 DROP CONSTRAINT check_a;
+CREATE TABLE part5_def PARTITION OF part_5 DEFAULT PARTITION BY LIST(a);
+CREATE TABLE part5_def_p1 PARTITION OF part5_def FOR VALUES IN (5);
+INSERT INTO part5_def_p1 VALUES (5, 'y');
+CREATE TABLE part5_p1 (LIKE part_5);
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+ERROR:  updated partition constraint for default partition would be violated by some row
+-- should be ok after deleting the bad row
+DELETE FROM part5_def_p1 WHERE b = 'y';
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
 -- check that the table being attached is not already a partition
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 ERROR:  "part_2" is already a partition
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 4f79612..58c755b 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -470,10 +470,7 @@ LINE 1: ...BLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) T...
 -- check default partition cannot be created more than once
 CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
 CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
-ERROR:  cannot add a new partition to table "list_parted" having a default partition
--- check that no partition can be created after default partition
-CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (3);
-ERROR:  cannot add a new partition to table "list_parted" having a default partition
+ERROR:  partition "fail_default_part" conflicts with existing default partition "part_default"
 -- specified literal can't be cast to the partition column data type
 CREATE TABLE bools (
 	a bool
@@ -565,10 +562,15 @@ CREATE TABLE list_parted2 (
 ) PARTITION BY LIST (a);
 CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
 CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
 ERROR:  partition "fail_part" would overlap partition "part_null_z"
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
 ERROR:  partition "fail_part" would overlap partition "part_ab"
+-- check default partition overlap
+INSERT INTO list_parted2 VALUES('X');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y');
+ERROR:  updated partition constraint for default partition "list_parted2_def" would be violated by some row
 CREATE TABLE range_parted2 (
 	a int
 ) PARTITION BY RANGE (a);
@@ -594,9 +596,14 @@ 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;
--- New partition cannot be created if a default partition exists
+-- 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:  cannot add a new partition to table "range_parted2" having a default partition
+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,
@@ -610,13 +617,12 @@ 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
 CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, minvalue) TO (1, maxvalue);
 ERROR:  partition "fail_part" would overlap partition "part10"
--- check default partition can be added to multi-column range partitioned table
-CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
 -- check schema propagation from parent
 CREATE TABLE parted (
 	a text,
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 48f5292..9872643 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -276,9 +276,6 @@ select tableoid::regclass, * from list_parted;
  part_default_p2    | de | 35
 (9 rows)
 
--- drop default partition, as we need to add some more partitions to test tuple
--- routing
-drop table part_default;
 -- Check tuple routing for partitioned tables
 -- fail
 insert into range_parted values ('a', 0);
@@ -352,7 +349,10 @@ select tableoid::regclass, * from list_parted;
  part_xx_yy_defpart | yy |  2
  part_null          |    |  0
  part_null          |    |  1
-(10 rows)
+ part_default_p1    | cd | 25
+ part_default_p1    | ab | 21
+ part_default_p2    | de | 35
+(13 rows)
 
 -- some more tests to exercise tuple-routing with multi-level partitioning
 create table part_gg partition of list_parted for values in ('gg') partition by range (b);
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
index fb90357..c2eeff1 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -266,6 +266,10 @@ DETAIL:  Failing row contains (null).
 execute pstmt_def_insert(1);
 ERROR:  new row for relation "list_part_def" violates partition constraint
 DETAIL:  Failing row contains (1).
+create table list_part_2 partition of list_parted for values in (2);
+execute pstmt_def_insert(2);
+ERROR:  new row for relation "list_part_def" violates partition constraint
+DETAIL:  Failing row contains (2).
 alter table list_parted detach partition list_part_null;
 -- should be ok
 execute pstmt_def_insert(null);
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index b6aa9e3..ab5d23c 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2118,13 +2118,13 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 
--- check partition cannot be attached if default exists
+-- check partition cannot be attached if default has some row for its values
 CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 INSERT INTO list_parted2_def VALUES (11, 'z');
 CREATE TABLE part_3 (LIKE list_parted2);
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
--- should be ok after detaching a default partition
-ALTER TABLE list_parted2 DETACH PARTITION list_parted2_def;
+-- should be ok after deleting the bad row
+DELETE FROM list_parted2_def WHERE a = 11;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
 
 -- adding constraints that describe the desired partition constraint
@@ -2144,6 +2144,9 @@ ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
 ALTER TABLE part_3_4 ALTER a SET NOT NULL;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
 
+-- check if default partition scan skipped
+ALTER TABLE list_parted2_def ADD CONSTRAINT check_a CHECK (a IN (5, 6));
+CREATE TABLE part_55_66 PARTITION OF list_parted2 FOR VALUES IN (55, 66);
 
 -- check validation when attaching range partitions
 CREATE TABLE range_parted (
@@ -2175,10 +2178,18 @@ ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 2
 -- Create default partition
 CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
 
--- New partition cannot be attached if a default partition exists
+-- 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 (
@@ -2239,6 +2250,18 @@ INSERT INTO part_7 (a, b) VALUES (8, null), (9, 'a');
 SELECT tableoid::regclass, a, b FROM part_7 order by a;
 ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
 
+-- check that leaf partitions of default partition are scanned when
+-- attaching a partitioned table.
+ALTER TABLE part_5 DROP CONSTRAINT check_a;
+CREATE TABLE part5_def PARTITION OF part_5 DEFAULT PARTITION BY LIST(a);
+CREATE TABLE part5_def_p1 PARTITION OF part5_def FOR VALUES IN (5);
+INSERT INTO part5_def_p1 VALUES (5, 'y');
+CREATE TABLE part5_p1 (LIKE part_5);
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+-- should be ok after deleting the bad row
+DELETE FROM part5_def_p1 WHERE b = 'y';
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+
 -- check that the table being attached is not already a partition
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 24f005e..eeab5d9 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -450,8 +450,6 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
 -- check default partition cannot be created more than once
 CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
 CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
--- check that no partition can be created after default partition
-CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (3);
 
 -- specified literal can't be cast to the partition column data type
 CREATE TABLE bools (
@@ -530,9 +528,13 @@ CREATE TABLE list_parted2 (
 ) PARTITION BY LIST (a);
 CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
 CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+-- check default partition overlap
+INSERT INTO list_parted2 VALUES('X');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y');
 
 CREATE TABLE range_parted2 (
 	a int
@@ -555,8 +557,13 @@ 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;
 
--- New partition cannot be created if a default partition exists
+-- 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 (
@@ -571,15 +578,13 @@ 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
 -- more specific ranges
 CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, minvalue) TO (1, maxvalue);
 
--- check default partition can be added to multi-column range partitioned table
-CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
-
 -- check schema propagation from parent
 
 CREATE TABLE parted (
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 7fc5688..0e68480 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -165,9 +165,6 @@ insert into list_parted values ('ab', 21);
 insert into list_parted values ('xx', 1);
 insert into list_parted values ('yy', 2);
 select tableoid::regclass, * from list_parted;
--- drop default partition, as we need to add some more partitions to test tuple
--- routing
-drop table part_default;
 
 -- Check tuple routing for partitioned tables
 
diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql
index a8e357c..cb2a551 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -167,6 +167,8 @@ prepare pstmt_def_insert (int) as insert into list_part_def values($1);
 -- should fail
 execute pstmt_def_insert(null);
 execute pstmt_def_insert(1);
+create table list_part_2 partition of list_parted for values in (2);
+execute pstmt_def_insert(2);
 alter table list_parted detach partition list_part_null;
 -- should be ok
 execute pstmt_def_insert(null);
-- 
2.7.4

0004-Check-default-partitition-child-validation-scan-is.patch0000664000175000017500000001277313153753555023561 0ustar  jeevanjeevanFrom ab86fd099427e73b3c848d68498dc7a5d4f4ce58 Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Wed, 6 Sep 2017 16:38:01 +0530
Subject: [PATCH 4/5] Check default partitition child validation scan is
 skippable

Add code to check_default_allows_bound() such that the default
partition children constraints are checked against new partition
constraints for implication and avoid scan of the child of which
existing constraints are implied by new default partition
constraints. Also, added testcase for the same.

Jeevan Ladhe.
---
 src/backend/catalog/partition.c           | 18 ++++++++++++++++++
 src/test/regress/expected/alter_table.out | 14 ++++++++++++--
 src/test/regress/sql/alter_table.sql      |  9 +++++++++
 3 files changed, 39 insertions(+), 2 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 782d5a4..6952494 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -951,7 +951,25 @@ check_default_allows_bound(Relation parent, Relation default_rel,
 
 		/* Lock already taken above. */
 		if (part_relid != RelationGetRelid(default_rel))
+		{
 			part_rel = heap_open(part_relid, NoLock);
+
+			/*
+			 * If the partition constraints on default partition child imply
+			 * that it will not contain any row that would belong to the new
+			 * partition, we can avoid scanning the child table.
+			 */
+			if (PartConstraintImpliedByRelConstraint(part_rel,
+													 def_part_constraints))
+			{
+				ereport(INFO,
+						(errmsg("partition constraint for table \"%s\" is implied by existing constraints",
+								RelationGetRelationName(part_rel))));
+
+				heap_close(part_rel, NoLock);
+				continue;
+			}
+		}
 		else
 			part_rel = default_rel;
 
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index b9d843b..3cecf82 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3322,6 +3322,18 @@ INFO:  partition constraint for table "part_3_4" is implied by existing constrai
 ALTER TABLE list_parted2_def ADD CONSTRAINT check_a CHECK (a IN (5, 6));
 CREATE TABLE part_55_66 PARTITION OF list_parted2 FOR VALUES IN (55, 66);
 INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
+-- check if default partition child relation scan skipped
+DROP TABLE list_parted2_def;
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT PARTITION BY RANGE (a);
+CREATE TABLE list_parted2_def_p1 PARTITION OF list_parted2_def FOR VALUES FROM (21) TO (30);
+CREATE TABLE list_parted2_def_p2 PARTITION OF list_parted2_def FOR VALUES FROM (31) TO (40);
+ALTER TABLE list_parted2_def_p1 ADD CONSTRAINT check_a CHECK (a IN (21, 22));
+ALTER TABLE list_parted2_def_p2 ADD CONSTRAINT check_a CHECK (a IN (31, 32));
+CREATE TABLE part_9 PARTITION OF list_parted2 FOR VALUES IN (9);
+INFO:  partition constraint for table "list_parted2_def_p1" is implied by existing constraints
+INFO:  partition constraint for table "list_parted2_def_p2" is implied by existing constraints
+CREATE TABLE part_31 PARTITION OF list_parted2 FOR VALUES IN (31);
+INFO:  partition constraint for table "list_parted2_def_p1" is implied by existing constraints
 -- check validation when attaching range partitions
 CREATE TABLE range_parted (
 	a int,
@@ -3412,7 +3424,6 @@ ALTER TABLE part_7 ATTACH PARTITION part_7_a_null FOR VALUES IN ('a', null);
 INFO:  partition constraint for table "part_7_a_null" is implied by existing constraints
 ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
 INFO:  partition constraint for table "part_7" is implied by existing constraints
-INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
 -- Same example, but check this time that the constraint correctly detects
 -- violating rows
 ALTER TABLE list_parted2 DETACH PARTITION part_7;
@@ -3426,7 +3437,6 @@ SELECT tableoid::regclass, a, b FROM part_7 order by a;
 (2 rows)
 
 ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
-INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
 ERROR:  partition constraint is violated by some row
 -- check that leaf partitions of default partition are scanned when
 -- attaching a partitioned table.
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index ab5d23c..ccb2f26 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2147,6 +2147,15 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
 -- check if default partition scan skipped
 ALTER TABLE list_parted2_def ADD CONSTRAINT check_a CHECK (a IN (5, 6));
 CREATE TABLE part_55_66 PARTITION OF list_parted2 FOR VALUES IN (55, 66);
+-- check if default partition child relation scan skipped
+DROP TABLE list_parted2_def;
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT PARTITION BY RANGE (a);
+CREATE TABLE list_parted2_def_p1 PARTITION OF list_parted2_def FOR VALUES FROM (21) TO (30);
+CREATE TABLE list_parted2_def_p2 PARTITION OF list_parted2_def FOR VALUES FROM (31) TO (40);
+ALTER TABLE list_parted2_def_p1 ADD CONSTRAINT check_a CHECK (a IN (21, 22));
+ALTER TABLE list_parted2_def_p2 ADD CONSTRAINT check_a CHECK (a IN (31, 32));
+CREATE TABLE part_9 PARTITION OF list_parted2 FOR VALUES IN (9);
+CREATE TABLE part_31 PARTITION OF list_parted2 FOR VALUES IN (31);
 
 -- check validation when attaching range partitions
 CREATE TABLE range_parted (
-- 
2.7.4

0005-Documentation-for-default-partition.patch0000664000175000017500000002245213153753555021034 0ustar  jeevanjeevanFrom 8eba3403f9e4a7a46bf11ee7ef2cd4df1a711182 Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Wed, 6 Sep 2017 16:38:54 +0530
Subject: [PATCH 5/5] Documentation for default partition.

This patch adds documentation for default partition for
CREATE TABLE and ALTER TABLE commands with example.
Also, added documentation for pg_partitioned_table new
field partdefid.

Jeevan Ladhe
---
 doc/src/sgml/catalogs.sgml         | 11 +++++++++++
 doc/src/sgml/ref/alter_table.sgml  | 35 ++++++++++++++++++++++++++++++++---
 doc/src/sgml/ref/create_table.sgml | 32 +++++++++++++++++++++++++++++---
 3 files changed, 72 insertions(+), 6 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 4f56188..4978b47 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -4739,6 +4739,17 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</>:<replaceable>&lt;salt&gt;<
      </row>
 
      <row>
+      <entry><structfield>partdefid</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+      <entry>
+       The OID of the <structname>pg_class</> entry for the default partition
+       of this partitioned table, or zero if this partitioned table does not
+       have a default partition.
+     </entry>
+     </row>
+
+     <row>
       <entry><structfield>partattrs</structfield></entry>
       <entry><type>int2vector</type></entry>
       <entry><literal><link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.attnum</literal></entry>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index dae6307..f27d487 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -34,7 +34,7 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
 ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable> [ OWNED BY <replaceable class="PARAMETER">role_name</replaceable> [, ... ] ]
     SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable> [ NOWAIT ]
 ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
-    ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+    ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> { FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> | DEFAULT }
 ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
     DETACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable>
 
@@ -765,11 +765,18 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
    </varlistentry>
 
    <varlistentry>
-    <term><literal>ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable></literal></term>
+    <term><literal>ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> { FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> | DEFAULT }</literal></term>
     <listitem>
      <para>
       This form attaches an existing table (which might itself be partitioned)
-      as a partition of the target table using the same syntax for
+      as a partition of the target table. The table can be attached
+      as a partition for specific values using <literal>FOR VALUES
+      </literal> or as a default partition by using <literal>DEFAULT
+      </literal>.
+     </para>
+
+     <para>
+      A partition using <literal>FOR VALUES</literal> uses same syntax for
       <replaceable class="PARAMETER">partition_bound_spec</replaceable> as
       <xref linkend="sql-createtable">.  The partition bound specification
       must correspond to the partitioning strategy and partition key of the
@@ -798,6 +805,10 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       a list partition that will not accept <literal>NULL</literal> values,
       also add <literal>NOT NULL</literal> constraint to the partition key
       column, unless it's an expression.
+      Also, if there exists a default partition table for the parent table,
+      then the default partition (if it is a regular table) is scanned to
+      check that no existing row in default partition would fit in the
+      partition that is being attached.
      </para>
 
      <para>
@@ -806,6 +817,17 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       (See the discussion in <xref linkend="SQL-CREATEFOREIGNTABLE"> about
       constraints on the foreign table.)
      </para>
+
+     <para>
+      When a table has a default partition, defining a new partition changes
+      the partition constraint for the default partition. The default
+      partition can't contain any rows that would need to be moved to the new
+      partition, and will be scanned to verify that none are present. This
+      scan, like the scan of the new partition, can be avoided if an
+      appropriate <literal>CHECK</literal> constraint is present. Also like
+      the scan of the new partition, it is always skipped when the default
+      partition is a foreign table.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -1396,6 +1418,13 @@ ALTER TABLE cities
 </programlisting></para>
 
   <para>
+   Attach a default partition to a partitioned table:
+<programlisting>
+ALTER TABLE cities
+    ATTACH PARTITION cities_partdef DEFAULT;
+</programlisting></para>
+
+  <para>
    Detach a partition from partitioned table:
 <programlisting>
 ALTER TABLE measurement
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index a6ca590..84d43c6 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -49,7 +49,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
   { <replaceable class="PARAMETER">column_name</replaceable> [ WITH OPTIONS ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
     | <replaceable>table_constraint</replaceable> }
     [, ... ]
-) ] FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+) ] { FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> | DEFAULT }
 [ PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
 [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
 [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
@@ -250,11 +250,13 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
    </varlistentry>
 
    <varlistentry id="SQL-CREATETABLE-PARTITION">
-    <term><literal>PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable> FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable></literal></term>
+    <term><literal>PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable> { FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> | DEFAULT }</literal></term>
     <listitem>
      <para>
       Creates the table as a <firstterm>partition</firstterm> of the specified
-      parent table.
+      parent table. The table can be created either as a partition for specific
+      values using <literal>FOR VALUES</literal> or as a default partition
+      using <literal>DEFAULT</literal>.
      </para>
 
      <para>
@@ -343,6 +345,23 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
      </para>
 
      <para>
+     If <literal>DEFAULT</literal> is specified, the table will be
+     created as a default partition of the parent table. The parent can
+     either be a list or range partitioned table. A partition key value
+     not fitting into any other partition of the given parent will be
+     routed to the default partition. There can be only one default
+     partition for a given parent table.
+     </para>
+
+     <para>
+     If the given parent is already having a default partition then
+     adding a new partition would result in an error if the default
+     partition contains a record that would fit in the new partition
+     being added. This check is not performed if the default partition
+     is a foreign table.
+     </para>
+
+     <para>
       A partition must have the same column names and types as the partitioned
       table to which it belongs.  If the parent is specified <literal>WITH
       OIDS</literal> then all partitions must have OIDs; the parent's OID
@@ -1679,6 +1698,13 @@ CREATE TABLE cities_ab
 CREATE TABLE cities_ab_10000_to_100000
     PARTITION OF cities_ab FOR VALUES FROM (10000) TO (100000);
 </programlisting></para>
+
+  <para>
+   Create a default partition of partitioned table:
+<programlisting>
+CREATE TABLE cities_partdef
+    PARTITION OF cities DEFAULT;
+</programlisting></para>
  </refsect1>
 
  <refsect1 id="SQL-CREATETABLE-compatibility">
-- 
2.7.4

#177Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Jeevan Ladhe (#176)
Re: Adding support for Default partition in partitioning

Hi Ashutosh,

I have tried to address your comments in V27 patch series[1]/messages/by-id/CAOgcT0OM_Px6BXp1uDhhArw0bm-q4zCD5YwhDywR3K9ziBNL6A@mail.gmail.com.
Please find my comments inlined.

The current set of patches contains 6 patches as below:

0001:
Refactoring existing ATExecAttachPartition code so that it can be used
for
default partitioning as well

If I understand correctly these comments refer to patch 0001 of V25_rebase
series, which is related to "Fix assumptions that get_qual_from_partbound()"
and not refactoring. This is patch 0001 in V27 now.

* Returns an expression tree describing the passed-in relation's partition

- * constraint.
+ * constraint. If there are no partition constraints returns NULL e.g. in
case
+ * default partition is the only partition.
The first sentence uses singular constraint. The second uses plural. Given
that
partition bounds together form a single constraint we should use singular
constraint in the second sentence as well.

I have changed the wording now.

Do we want to add a similar comment in the prologue of
generate_partition_qual(). The current wording there seems to cover this
case,
but do we want to explicitly mention this case?

I have added a comment there.

+        if (!and_args)
+            result = NULL;
While this is correct, I am increasingly seeing (and_args != NIL) usage.
Changed this to:
+       if (and_args == NIL)
+           result = NULL;

get_partition_qual_relid() is called from pg_get_partition_
constraintdef(),
constr_expr = get_partition_qual_relid(relationId);

/* Quick exit if not a partition */
if (constr_expr == NULL)
PG_RETURN_NULL();
The comment is now wrong since a default partition may have no
constraints. May
be rewrite it as simply, "Quick exit if no partition constraint."

Fixed.

generate_partition_qual() has three callers and all of them are capable of
handling NIL partition constraint for default partition. May be it's
better to
mention in the commit message that we have checked that the callers of
this function
can handle NIL partition constraint.

Added in commit message.

0002:
This patch teaches the partitioning code to handle the NIL returned by
get_qual_for_list().
This is needed because a default partition will not have any constraints
in case
it is the only partition of its parent.

Comments below refer to patch 0002 in V25_rebase(0003 in V25), which
adds basic support for default partition, which is now 0002 in V27.

If the partition being dropped is the default partition,
heap_drop_with_catalog() locks default partition twice, once as the default
partition and the second time as the partition being dropped. So, it will
be
counted as locked twice. There doesn't seem to be any harm in this, since
the
lock will be help till the transaction ends, by when all the locks will be
released.

Fixed.

Same is the case with cache invalidation message. If we are dropping
default
partition, the cache invalidation message on "default partition" is not
required. Again this might be harmless, but better to avoid it.

Fixed.

Similar problems exists with ATExecDetachPartition(), default partition
will be
locked twice if it's being detached.

In ATExecDetachPartition() we do not have OID of the relation being
detached
available at the time we lock default partition. Moreover, here we are
taking an
exclusive lock on default OID and an share lock on partition being detached.
As you correctly said in your earlier comment that it will be counted as
locked
twice, which to me also seems harmless. As these locks are to be held till
commit of the transaction nobody else is supposed to be releasing these
locks in
between. I am not able to visualize a problem here, but still I have tried
to
avoid locking the default partition table twice, please review the changes
and
let me know your thoughts.

+        /*
+         * If this is a default partition, pg_partitioned_table must have
it's
+         * OID as value of 'partdefid' for it's parent (i.e. rel) entry.
+         */
+        if (castNode(PartitionBoundSpec, boundspec)->is_default)
+        {
+            Oid            partdefid;
+
+            partdefid = get_default_partition_oid(RelationGetRelid(rel));
+            Assert(partdefid == inhrelid);
+        }
Since an accidental change or database corruption may change the default
partition OID in pg_partition_table. An Assert won't help in such a case.
May
be we should throw an error or at least report a warning. If we throw an
error,
the table will become useless (or even the database will become useless
RelationBuildPartitionDesc is called from RelationCacheInitializePhase3()
on
such a corrupted table). To avoid that we may raise a warning.

I have added a warning here instead of Assert.

I am wondering whether we could avoid call to get_default_partition_oid()
in
the above block, thus avoiding a sys cache lookup. The sys cache search
shouldn't be expensive since the cache should already have that entry, but
still if we can avoid it, we save some CPU cycles. The default partition
OID is
stored in pg_partition_table catalog, which is looked up in
RelationGetPartitionKey(), a function which precedes
RelationGetPartitionDesc()
everywhere. What if that RelationGetPartitionKey() also returns the default
partition OID and the common caller passes it to
RelationGetPartitionDesc()?.

The purpose here is to cross check the relid with partdefid stored in
catalog
pg_partitioned_table, though its going to be the same in the parents cache,
I
think its better that we retrieve it from the catalog syscache.
Further, RelationGetPartitionKey() is a macro and not a function, so
modifying
the existing simple macro for this reason does not sound a good idea to me.
Having said this I am open to ideas here.

+    /* A partition cannot be attached if there exists a default partition
*/
+    defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
+    if (OidIsValid(defaultPartOid))
+        ereport(ERROR,
+                (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                 errmsg("cannot attach a new partition to table
\"%s\" having a default partition",
+                        RelationGetRelationName(rel))));
get_default_partition_oid() searches the catalogs, which is not needed
when we
have relation descriptor of the partitioned table (to which a new
partition is
being attached). You should get the default partition OID from partition
descriptor. That will be cheaper.

Something like following can be done here:
/* A partition cannot be attached if there exists a default partition */
if (partition_bound_has_default(rel->partdesc->boundinfo))
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("cannot attach a new partition to table \"%s\"
having a default partition",
RelationGetRelationName(rel))));

But, partition_bound_has_default() is defined in partition.c and not in
partition.h. This is done that way because boundinfo is not available in
partition.h. Further, this piece of code is removed in next patch where we
extend default partition support to add/attach partition even when default
partition exists. So, to me I don’t see much of the correction issue here.

Another way to get around this is, we can define another version of
get_default_partition_oid() something like
get_default_partition_oid_from_parent_rel()
in partition.c which looks around in relcache instead of catalog and
returns the
oid of default partition, or get_default_partition_oid() accepts both
parent OID,
and parent ‘Relation’ rel, if rel is not null look into relcahce and return,
else search from catalog using OID.

+                /* If there isn't any constraint, show that explicitly */
+                if (partconstraintdef[0] == '\0')
+                    printfPQExpBuffer(&tmpbuf, _("No partition
constraint"));
I think we need to change the way we set partconstraintdef at
if (PQnfields(result) == 3)
partconstraintdef = PQgetvalue(result, 0, 2);
Before this commit, constraints will never be NULL so this code works, but
now
that the cosntraints could be NULL, we need to check whether 3rd value is
NULL
or not using PQgetisnull() and assigning a value only when it's not NULL.
I have changed this to:
-                       if (PQnfields(result) == 3)
+                       if (PQnfields(result) == 3 && !PQgetisnull(result,
0, 2))
                                partconstraintdef = PQgetvalue(result, 0,
2);

Please let me know if the change looks good to you.

+-- test adding default partition as first partition accepts any value
including
grammar, reword as "test that a default partition added as the first
partition accepts any
value including".

changed the wording in the comment as suggested.

0003:
Support for default partition with the restriction of preventing

addition

of any
new partition after default partition. This is a merge of 0003 and 0004

in

V24 series.

Comments below rather seem to be for the patch that extends default
partition
such that new partition can be added even when default partition exists.
This
is 0003 patch in V27.

The commit message of this patch has following line, which no more applies
to
patch 0001. May be you want to remove this line or update the patch number.
3. This patch uses the refactored functions created in patch 0001
in this series.
Similarly the credit line refers to patch 0001. That too needs correction.

Fixed commit message.

- * Also, invalidate the parent's relcache, so that the next rebuild will
load
- * the new partition's info into its partition descriptor.
+ * Also, invalidate the parent's relcache entry, so that the next rebuild
will
+ * load he new partition's info into its partition descriptor.  If there
is a
+ * default partition, we must invalidate its relcache entry as well.
Replacing "relcache" with "relcache entry" in the first sentence  may be a
good
idea, but is unrelated to this patch. I would leave that change aside and
just
add comment about default partition.

Agree. Fixed.

+    /*
+     * The partition constraint for the default partition depends on the
+     * partition bounds of every other partition, so we must invalidate
the
+     * relcache entry for that partition every time a partition is added
or
+     * removed.
+     */
+    defaultPartOid = get_default_partition_oid(RelationGetRelid(parent));
+    if (OidIsValid(defaultPartOid))
+        CacheInvalidateRelcacheByRelid(defaultPartOid);
Again, since we have access to the parent's relcache, we should get the
default
partition OID from relcache rather than catalogs.

This change is in heap.c, as I said above we would need to have a
different version of get_default_partition_oid() to address this.
Your thoughts?

I haven't gone through the full patch yet, so there may be more

comments here. There is some duplication of code in
check_default_allows_bound() and ValidatePartitionConstraints() to
scan the children of partition being validated. The difference is that
the first one scans the relations in the same function and the second
adds them to work queue. May be we could use
ValidatePartitionConstraints() to scan the relation or add to the
queue based on some input flag may be wqueue argument itself. But I
haven't thought through this completely. Any thoughts?

check_default_allows_bound() is called only from DefineRelation(),
and not for alter command. I am not really sure how can we use
work queue for create command.

[1]: /messages/by-id/CAOgcT0OM_Px6BXp1uDhhArw0bm-q4zCD5YwhDywR3K9ziBNL6A@mail.gmail.com
/messages/by-id/CAOgcT0OM_Px6BXp1uDhhArw0bm-q4zCD5YwhDywR3K9ziBNL6A@mail.gmail.com

Regards,
Jeevan Ladhe

#178Rajkumar Raghuwanshi
rajkumar.raghuwanshi@enterprisedb.com
In reply to: Jeevan Ladhe (#176)
Re: Adding support for Default partition in partitioning

On Wed, Sep 6, 2017 at 5:25 PM, Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
wrote:

Hi,

Attached is the rebased set of patches.
Robert has committed[1] patch 0001 in V26 series, hence the patch numbering
in V27 is now decreased by 1 for each patch as compared to V26.

Hi,

I have applied v27 patches and while testing got below observation.

Observation: in below partition table, d1 constraints not allowing NULL to
be inserted in b column
but I am able to insert it.

steps to reproduce:
create table d0 (a int, b int) partition by range(a,b);
create table d1 partition of d0 for values from (0,0) to
(maxvalue,maxvalue);

postgres=# insert into d0 values (0,null);
INSERT 0 1
postgres=# \d+ d1
Table "public.d1"
Column | Type | Collation | Nullable | Default | Storage | Stats target
| Description
--------+---------+-----------+----------+---------+---------+--------------+-------------
a | integer | | | | plain |
|
b | integer | | | | plain |
|
Partition of: d0 FOR VALUES FROM (0, 0) TO (MAXVALUE, MAXVALUE)
Partition constraint: ((a IS NOT NULL) AND *(b IS NOT NULL) *AND ((a > 0)
OR ((a = 0) AND (b >= 0))))

postgres=# select tableoid::regclass,* from d0;
tableoid | a | b
----------+---+---
*d1 | 0 | *
(1 row)

Thanks & Regards,
Rajkumar Raghuwanshi
QMG, EnterpriseDB Corporation

#179Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Rajkumar Raghuwanshi (#178)
Re: Adding support for Default partition in partitioning

On Thu, Sep 7, 2017 at 3:15 PM, Rajkumar Raghuwanshi <
rajkumar.raghuwanshi@enterprisedb.com> wrote:

On Wed, Sep 6, 2017 at 5:25 PM, Jeevan Ladhe <
jeevan.ladhe@enterprisedb.com> wrote:

Hi,

Attached is the rebased set of patches.
Robert has committed[1] patch 0001 in V26 series, hence the patch
numbering
in V27 is now decreased by 1 for each patch as compared to V26.

Hi,

I have applied v27 patches and while testing got below observation.

Observation: in below partition table, d1 constraints not allowing NULL to
be inserted in b column
but I am able to insert it.

steps to reproduce:
create table d0 (a int, b int) partition by range(a,b);
create table d1 partition of d0 for values from (0,0) to
(maxvalue,maxvalue);

postgres=# insert into d0 values (0,null);
INSERT 0 1
postgres=# \d+ d1
Table "public.d1"
Column | Type | Collation | Nullable | Default | Storage | Stats
target | Description
--------+---------+-----------+----------+---------+--------
-+--------------+-------------
a | integer | | | | plain
| |
b | integer | | | | plain
| |
Partition of: d0 FOR VALUES FROM (0, 0) TO (MAXVALUE, MAXVALUE)
Partition constraint: ((a IS NOT NULL) AND *(b IS NOT NULL) *AND ((a > 0)
OR ((a = 0) AND (b >= 0))))

postgres=# select tableoid::regclass,* from d0;
tableoid | a | b
----------+---+---
*d1 | 0 | *
(1 row)

Good catch. Thanks Rajkumar.
This seems to be happening because of the following changes made in
get_partition_for_tuple() for default range partition support as part of
patch 0002.

@@ -1971,27 +2204,10 @@ 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;
- }
- }
- }

Instead of getting rid of this. If there isn't a default partition then
we still do not have any range partition to route a null partition
key and the routing should fail.

I will work on a fix and send a patch shortly.

Regards,
Jeevan Ladhe

#180Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Jeevan Ladhe (#179)
1 attachment(s)
Re: Adding support for Default partition in partitioning

I will work on a fix and send a patch shortly.

Attached is the V28 patch that fixes the issue reported by Rajkumar.
The patch series is exactly same as that of V27 series[1]/messages/by-id/CAAJ_b96jnnjsVCcMG5tpiDLLi8B76dK+R2wermmk6uKbOnwdFg@mail.gmail.com.
The fix is in patch 0002, and macro partition_bound_has_default() is
again moved in 0002 from 0003, as the fix needed to use it.

The fix is basically in get_partition_for_tuple() as below:

@@ -1973,30 +2209,46 @@ get_partition_for_tuple(PartitionDispatch *pd,

        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;
+                   /*
+                    * We cannot route tuples with NULL partition keys
through
+                    * a range-partitioned table if it does not have a
default
+                    * partition. In such case simply return that no
partition
+                    * exists for routing null partition key.
+                    */
+                   if (!partition_bound_has_default(partdesc->boundinfo))
+                   {
+                       *failed_at = parent;
+                       *failed_slot = slot;
+                       result = -1;
+                       goto error_exit;
+                   }
+                   else
+                   {
+                       /*
+                        * If there is any null partition key, it would be
+                        * routed to the default partition.
+                        */
+                       range_partkey_has_null = true;
+                       break;
+                   }
                }
            }
        }
        /*
-        * A null partition key is only acceptable if null-accepting list
-        * partition exists.
+        * If partition strategy is LIST and this is a null partition key,
+        * route it to the null-accepting partition. Otherwise, route by
+        * searching the array of partition bounds.
         */
        cur_index = -1;
-       if (isnull[0] && partition_bound_accepts_nulls(partdesc->boundinfo))
+       if (key->strategy == PARTITION_STRATEGY_LIST && isnull[0] &&
+           partition_bound_accepts_nulls(partdesc->boundinfo))
            cur_index = partdesc->boundinfo->null_index;
-       else if (!isnull[0])
+       else if (!range_partkey_has_null && !isnull[0])
        {

The fix would be much easier if the refactoring patch 0001 by Amul in hash
partitioning thread[2]/messages/by-id/CAAJ_b96jnnjsVCcMG5tpiDLLi8B76dK+R2wermmk6uKbOnwdFg@mail.gmail.com is committed.
The current code mixes the routing for list and range partitioning, and
makes
it difficult to understand and fix any issues coming forward. I believe it
will
be a good idea to keep the logic separate for both partitioning strategies.
Thoughts, view?

[1]: /messages/by-id/CAAJ_b96jnnjsVCcMG5tpiDLLi8B76dK+R2wermmk6uKbOnwdFg@mail.gmail.com
/messages/by-id/CAAJ_b96jnnjsVCcMG5tpiDLLi8B76dK+R2wermmk6uKbOnwdFg@mail.gmail.com
[2]: /messages/by-id/CAAJ_b96jnnjsVCcMG5tpiDLLi8B76dK+R2wermmk6uKbOnwdFg@mail.gmail.com
/messages/by-id/CAAJ_b96jnnjsVCcMG5tpiDLLi8B76dK+R2wermmk6uKbOnwdFg@mail.gmail.com

Regards,
Jeevan Ladhe

Attachments:

default_partition_V28.tarapplication/x-tar; name=default_partition_V28.tarDownload
0001-Fix-assumptions-that-get_qual_from_partbound-cannot-.patch0000664000175000017500000001146013154226110024262 0ustar  jeevanjeevanFrom 2657d7e82e7a82906939664676430eebe30d97ee Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Wed, 6 Sep 2017 16:22:37 +0530
Subject: [PATCH 1/2] Fix assumptions that get_qual_from_partbound() cannot
 return NIL list.

Current partitioning code assumes that there cannot be any partition
without partition constraints, but in future this assumption might
not hold true. This patch makes sure that the callers of
generate_partition_qual() can handle NIL partition constraint.
e.g. if we introduce support for default partition, then default
partition will not have any constraints in case it is the only
partition of its parent.

Jeevan Ladhe.
---
 src/backend/catalog/partition.c   | 11 ++++++++---
 src/backend/commands/tablecmds.c  | 37 +++++++++++++++++++++----------------
 src/backend/utils/adt/ruleutils.c |  2 +-
 3 files changed, 30 insertions(+), 20 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 5016263..807ceee 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -935,7 +935,8 @@ RelationGetPartitionQual(Relation rel)
  * get_partition_qual_relid
  *
  * Returns an expression tree describing the passed-in relation's partition
- * constraint.
+ * constraint. If there is no partition constraint returns NULL e.g. in case
+ * default partition is the only partition.
  */
 Expr *
 get_partition_qual_relid(Oid relid)
@@ -948,7 +949,10 @@ get_partition_qual_relid(Oid relid)
 	if (rel->rd_rel->relispartition)
 	{
 		and_args = generate_partition_qual(rel);
-		if (list_length(and_args) > 1)
+
+		if (and_args == NIL)
+			result = NULL;
+		else if (list_length(and_args) > 1)
 			result = makeBoolExpr(AND_EXPR, and_args, -1);
 		else
 			result = linitial(and_args);
@@ -1756,7 +1760,8 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 /*
  * generate_partition_qual
  *
- * Generate partition predicate from rel's partition bound expression
+ * Generate partition predicate from rel's partition bound expression. The
+ * function returns a NIL list if there is no predicate.
  *
  * Result expression tree is stored CacheMemoryContext to ensure it survives
  * as long as the relcache entry. But we should be running in a less long-lived
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index c8fc9cb..77f5761 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13833,24 +13833,29 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	partConstraint = list_concat(get_qual_from_partbound(attachrel, rel,
 														 cmd->bound),
 								 RelationGetPartitionQual(rel));
-	partConstraint = (List *) eval_const_expressions(NULL,
-													 (Node *) partConstraint);
-	partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
-	partConstraint = list_make1(make_ands_explicit(partConstraint));
 
-	/*
-	 * Adjust the generated constraint to match this partition's attribute
-	 * numbers.
-	 */
-	partConstraint = map_partition_varattnos(partConstraint, 1, attachrel,
-											 rel, &found_whole_row);
-	/* There can never be a whole-row reference here */
-	if (found_whole_row)
-		elog(ERROR, "unexpected whole-row reference found in partition key");
+	/* Skip validation if there are no constraints to validate. */
+	if (partConstraint)
+	{
+		partConstraint = (List *) eval_const_expressions(NULL,
+														 (Node *) partConstraint);
+		partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
+		partConstraint = list_make1(make_ands_explicit(partConstraint));
 
-	/* Validate partition constraints against the table being attached. */
-	ValidatePartitionConstraints(wqueue, attachrel, attachrel_children,
-								 partConstraint);
+		/*
+		 * Adjust the generated constraint to match this partition's attribute
+		 * numbers.
+		 */
+		partConstraint = map_partition_varattnos(partConstraint, 1, attachrel,
+												 rel, &found_whole_row);
+		/* There can never be a whole-row reference here */
+		if (found_whole_row)
+			elog(ERROR, "unexpected whole-row reference found in partition key");
+
+		/* Validate partition constraints against the table being attached. */
+		ValidatePartitionConstraints(wqueue, attachrel, attachrel_children,
+									 partConstraint);
+	}
 
 	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachrel));
 
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index f9ea7ed..12decb0 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -1750,7 +1750,7 @@ pg_get_partition_constraintdef(PG_FUNCTION_ARGS)
 
 	constr_expr = get_partition_qual_relid(relationId);
 
-	/* Quick exit if not a partition */
+	/* Quick exit if no partition constraint */
 	if (constr_expr == NULL)
 		PG_RETURN_NULL();
 
-- 
2.7.4

0002-Implement-default-partition-support.patch0000664000175000017500000021406713154226110021064 0ustar  jeevanjeevanFrom 684d0f965ed6c8fa56942fd4baf77293ce459405 Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Wed, 6 Sep 2017 16:34:36 +0530
Subject: [PATCH 2/2] Implement default partition support

This patch introduces default partition for a list or range
partitioned table. However, in this version of patch no other
partition can be attached to a partitioned table if it has a
default partition. To ease the retrieval of default partition
OID, this patch adds a new column 'partdefid' to catalog
pg_partitioned_table.

Jeevan Ladhe, range partitioning related changes by Beena Emerson.
---
 src/backend/catalog/heap.c                 |  38 ++-
 src/backend/catalog/partition.c            | 451 ++++++++++++++++++++++++-----
 src/backend/commands/tablecmds.c           |  69 ++++-
 src/backend/nodes/copyfuncs.c              |   1 +
 src/backend/nodes/equalfuncs.c             |   1 +
 src/backend/nodes/outfuncs.c               |   1 +
 src/backend/nodes/readfuncs.c              |   1 +
 src/backend/parser/gram.y                  |  27 +-
 src/backend/parser/parse_utilcmd.c         |  14 +
 src/backend/utils/adt/ruleutils.c          |  11 +-
 src/bin/psql/describe.c                    |  10 +-
 src/bin/psql/tab-complete.c                |   4 +-
 src/include/catalog/partition.h            |   3 +
 src/include/catalog/pg_partitioned_table.h |  13 +-
 src/include/nodes/parsenodes.h             |   1 +
 src/test/regress/expected/alter_table.out  |  24 ++
 src/test/regress/expected/create_table.out |  14 +
 src/test/regress/expected/insert.out       | 139 ++++++++-
 src/test/regress/expected/plancache.out    |  22 ++
 src/test/regress/expected/sanity_check.out |   4 +
 src/test/regress/expected/update.out       |  24 ++
 src/test/regress/sql/alter_table.sql       |  24 ++
 src/test/regress/sql/create_table.sql      |  15 +
 src/test/regress/sql/insert.sql            |  71 ++++-
 src/test/regress/sql/plancache.sql         |  19 ++
 src/test/regress/sql/update.sql            |  23 ++
 26 files changed, 921 insertions(+), 103 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 45ee9ac..2952d40 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1759,7 +1759,8 @@ heap_drop_with_catalog(Oid relid)
 {
 	Relation	rel;
 	HeapTuple	tuple;
-	Oid			parentOid = InvalidOid;
+	Oid			parentOid = InvalidOid,
+				defaultPartOid = InvalidOid;
 
 	/*
 	 * To drop a partition safely, we must grab exclusive lock on its parent,
@@ -1775,6 +1776,23 @@ heap_drop_with_catalog(Oid relid)
 	{
 		parentOid = get_partition_parent(relid);
 		LockRelationOid(parentOid, AccessExclusiveLock);
+
+		/*
+		 * The partition constraint of the default partition depends on the
+		 * partition bounds of every other partition. It is possible that
+		 * other backend might be about to execute a query on the default
+		 * partition table and the query relies on previously cached default
+		 * partition constraints, which won't be correct after removal of a
+		 * partition. We must therefore take a table lock strong enough to
+		 * prevent all queries on the default partition from proceeding until
+		 * we commit and send out a shared-cache-inval notice that will make
+		 * them update their index lists. Note that we need not lock the
+		 * default partition if the table being dropped itself is the default
+		 * partition, because it is going to be locked further.
+		 */
+		defaultPartOid = get_default_partition_oid(parentOid);
+		if (OidIsValid(defaultPartOid) && relid != defaultPartOid)
+			LockRelationOid(defaultPartOid, AccessExclusiveLock);
 	}
 
 	ReleaseSysCache(tuple);
@@ -1826,6 +1844,13 @@ heap_drop_with_catalog(Oid relid)
 		RemovePartitionKeyByRelId(relid);
 
 	/*
+	 * If the relation being dropped is the default partition itself,
+	 * invalidate its entry in pg_partitioned_table.
+	 */
+	if (relid == defaultPartOid)
+		update_default_partition_oid(parentOid, InvalidOid);
+
+	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -1885,6 +1910,16 @@ heap_drop_with_catalog(Oid relid)
 	if (OidIsValid(parentOid))
 	{
 		/*
+		 * The partition constraint for the default partition depends on the
+		 * partition bounds of every other partition, so we must invalidate
+		 * the relcache entry for that partition every time a partition is
+		 * added or removed. If the default partition itself is dropped no
+		 * need to invalidate its relcache.
+		 */
+		if (OidIsValid(defaultPartOid) && relid != defaultPartOid)
+			CacheInvalidateRelcacheByRelid(defaultPartOid);
+
+		/*
 		 * Invalidate the parent's relcache so that the partition is no longer
 		 * included in its partition descriptor.
 		 */
@@ -3138,6 +3173,7 @@ StorePartitionKey(Relation rel,
 	values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
 	values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
 	values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
+	values[Anum_pg_partitioned_table_partdefid - 1] = ObjectIdGetDatum(InvalidOid);
 	values[Anum_pg_partitioned_table_partattrs - 1] = PointerGetDatum(partattrs_vec);
 	values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
 	values[Anum_pg_partitioned_table_partcollation - 1] = PointerGetDatum(partcollation_vec);
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 807ceee..02431a9 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -27,6 +27,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_inherits_fn.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_type.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
@@ -80,9 +81,12 @@ typedef struct PartitionBoundInfoData
 								 * partitioned table) */
 	int			null_index;		/* Index of the null-accepting partition; -1
 								 * if there isn't one */
+	int			default_index;	/* Index of the default partition; -1 if there
+								 * isn't one */
 } 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
@@ -120,8 +124,10 @@ static void get_range_key_properties(PartitionKey key, int keynum,
 						 ListCell **partexprs_item,
 						 Expr **keyCol,
 						 Const **lower_val, Const **upper_val);
-static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
-static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
+static List *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 *generate_partition_qual(Relation rel);
 
 static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index,
@@ -162,6 +168,7 @@ RelationBuildPartitionDesc(Relation rel)
 	MemoryContext oldcxt;
 
 	int			ndatums = 0;
+	int			default_index = -1;
 
 	/* List partitioning specific */
 	PartitionListValue **all_values = NULL;
@@ -213,6 +220,21 @@ RelationBuildPartitionDesc(Relation rel)
 								&isnull);
 		Assert(!isnull);
 		boundspec = (Node *) stringToNode(TextDatumGetCString(datum));
+
+		/*
+		 * If this is a default partition, pg_partitioned_table must have it's
+		 * OID as value of 'partdefid' for it's parent (i.e. rel) entry.
+		 */
+		if (castNode(PartitionBoundSpec, boundspec)->is_default)
+		{
+			Oid			partdefid;
+
+			partdefid = get_default_partition_oid(RelationGetRelid(rel));
+			if (partdefid != inhrelid)
+				elog(WARNING, "unexpected partdefid %u for pg_partition_table record of relation %s",
+					 partdefid, RelationGetRelationName(rel));
+		}
+
 		boundspecs = lappend(boundspecs, boundspec);
 		partoids = lappend_oid(partoids, inhrelid);
 		ReleaseSysCache(tuple);
@@ -246,6 +268,18 @@ RelationBuildPartitionDesc(Relation rel)
 				if (spec->strategy != PARTITION_STRATEGY_LIST)
 					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 list of non-null
+				 * datums for this partition.
+				 */
+				if (spec->is_default)
+				{
+					default_index = i;
+					i++;
+					continue;
+				}
+
 				foreach(c, spec->listdatums)
 				{
 					Const	   *val = castNode(Const, lfirst(c));
@@ -325,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,
@@ -334,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);
@@ -421,6 +467,7 @@ RelationBuildPartitionDesc(Relation rel)
 		boundinfo = (PartitionBoundInfoData *)
 			palloc0(sizeof(PartitionBoundInfoData));
 		boundinfo->strategy = key->strategy;
+		boundinfo->default_index = -1;
 		boundinfo->ndatums = ndatums;
 		boundinfo->null_index = -1;
 		boundinfo->datums = (Datum **) palloc0(ndatums * sizeof(Datum *));
@@ -473,6 +520,21 @@ RelationBuildPartitionDesc(Relation rel)
 						boundinfo->null_index = mapping[null_index];
 					}
 
+					/* Assign mapped index for the default partition. */
+					if (default_index != -1)
+					{
+						/*
+						 * The default partition accepts any value not
+						 * specified in the lists of other partitions, hence
+						 * it should not get mapped index while assigning
+						 * those for non-null datums.
+						 */
+						Assert(default_index >= 0 &&
+							   mapping[default_index] == -1);
+						mapping[default_index] = next_index++;
+						boundinfo->default_index = mapping[default_index];
+					}
+
 					/* All partition must now have a valid mapping */
 					Assert(next_index == nparts);
 					break;
@@ -527,6 +589,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;
 				}
@@ -577,6 +647,9 @@ partition_bounds_equal(int partnatts, int16 *parttyplen, bool *parttypbyval,
 	if (b1->null_index != b2->null_index)
 		return false;
 
+	if (b1->default_index != b2->default_index)
+		return false;
+
 	for (i = 0; i < b1->ndatums; i++)
 	{
 		int			j;
@@ -639,6 +712,9 @@ check_new_partition_bound(char *relname, Relation parent,
 	int			with = -1;
 	bool		overlap = false;
 
+	if (spec->is_default)
+		return;
+
 	switch (key->strategy)
 	{
 		case PARTITION_STRATEGY_LIST:
@@ -860,12 +936,12 @@ get_qual_from_partbound(Relation rel, Relation parent,
 	{
 		case PARTITION_STRATEGY_LIST:
 			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
-			my_qual = get_qual_for_list(key, spec);
+			my_qual = get_qual_for_list(parent, spec);
 			break;
 
 		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:
@@ -1267,10 +1343,14 @@ make_partition_op_expr(PartitionKey key, int keynum,
  *
  * Returns an implicit-AND list of expressions to use as a list partition's
  * constraint, given the partition key and bound structures.
+ *
+ * The function returns NIL for a default partition when it's the only
+ * partition since it can not have any partition constraint.
  */
 static List *
-get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
+get_qual_for_list(Relation parent, PartitionBoundSpec *spec)
 {
+	PartitionKey key = RelationGetPartitionKey(parent);
 	List	   *result;
 	Expr	   *keyCol;
 	ArrayExpr  *arr;
@@ -1297,15 +1377,63 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 	else
 		keyCol = (Expr *) copyObject(linitial(key->partexprs));
 
-	/* Create list of Consts for the allowed values, excluding any nulls */
-	foreach(cell, spec->listdatums)
+	/*
+	 * For default list partition, collect datums for all the partitions. The
+	 * default partition constraint should check that the partition key is
+	 * equal to none of those.
+	 */
+	if (spec->is_default)
 	{
-		Const	   *val = castNode(Const, lfirst(cell));
+		int			i;
+		int			ndatums = 0;
+		PartitionDesc pdesc = RelationGetPartitionDesc(parent);
+		PartitionBoundInfo boundinfo = pdesc->boundinfo;
 
-		if (val->constisnull)
-			list_has_null = true;
-		else
-			arrelems = lappend(arrelems, copyObject(val));
+		if (boundinfo)
+		{
+			ndatums = boundinfo->ndatums;
+
+			if (partition_bound_accepts_nulls(boundinfo))
+				list_has_null = true;
+		}
+
+		/*
+		 * If default is the only partition, there need not be any partition
+		 * constraint on it.
+		 */
+		if (ndatums == 0 && !list_has_null)
+			return NIL;
+
+		for (i = 0; i < ndatums; i++)
+		{
+			Const	   *val;
+
+			/* Construct const from datum */
+			val = makeConst(key->parttypid[0],
+							key->parttypmod[0],
+							key->parttypcoll[0],
+							key->parttyplen[0],
+							*boundinfo->datums[i],
+							false,	/* isnull */
+							key->parttypbyval[0]);
+
+			arrelems = lappend(arrelems, val);
+		}
+	}
+	else
+	{
+		/*
+		 * Create list of Consts for the allowed values, excluding any nulls.
+		 */
+		foreach(cell, spec->listdatums)
+		{
+			Const	   *val = castNode(Const, lfirst(cell));
+
+			if (val->constisnull)
+				list_has_null = true;
+			else
+				arrelems = lappend(arrelems, copyObject(val));
+		}
 	}
 
 	if (arrelems)
@@ -1369,6 +1497,25 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 			result = list_make1(nulltest);
 	}
 
+	/*
+	 * In case of the default partition, the constraint is of the form
+	 * "!(result)" i.e. one of the following two forms:
+	 *
+	 * 1. NOT ((keycol IS NULL) OR (keycol = ANY (arr)))
+	 *
+	 * 2. NOT ((keycol IS NOT NULL) AND (keycol = ANY (arr)))
+	 *
+	 * Note that, in general, applying NOT to a constraint expression doesn't
+	 * necessarily invert the set of rows it accepts, because NOT (NULL) is
+	 * NULL.  However, the partition constraints we construct here never
+	 * evaluate to NULL, so applying NOT works as intended.
+	 */
+	if (spec->is_default)
+	{
+		result = list_make1(make_ands_explicit(result));
+		result = list_make1(makeBoolExpr(NOT_EXPR, result, -1));
+	}
+
 	return result;
 }
 
@@ -1425,6 +1572,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
  *
@@ -1463,11 +1657,17 @@ 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 default is the only partition, then this function returns NIL. In case of
+ * non-default default partition, we return a single-member list with the
+ * constatnt TRUE when the result is empty.
+ *
  */
 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,
@@ -1478,10 +1678,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,
@@ -1491,44 +1691,72 @@ 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 it is the recursive call for default, we skip the get_range_nulltest
+	 * to avoid accumulating the NullTest on the same keys for each partition.
+	 */
+	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
@@ -1750,9 +1978,16 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 						 ? makeBoolExpr(OR_EXPR, upper_or_arms, -1)
 						 : linitial(upper_or_arms));
 
-	/* As noted above, caller expects the list to be non-empty. */
+	/*
+	 * As noted above, for non-default, we return list with constant TRUE. If
+	 * the result is NIL during the recursive call for default, it implies
+	 * this is the only other partition which can hold every value of the key
+	 * except NULL. Hence we return the NullTest result skipped earlier.
+	 */
 	if (result == NIL)
-		result = list_make1(makeBoolConst(true, false));
+		result = for_default
+			? get_range_nulltest(key)
+			: list_make1(makeBoolConst(true, false));
 
 	return result;
 }
@@ -1924,11 +2159,12 @@ get_partition_for_tuple(PartitionDispatch *pd,
 {
 	PartitionDispatch parent;
 	Datum		values[PARTITION_MAX_KEYS];
-	bool		isnull[PARTITION_MAX_KEYS];
+	bool		isnull[PARTITION_MAX_KEYS],
+				range_partkey_has_null = false;
 	int			cur_offset,
-				cur_index;
-	int			i,
-				result;
+				cur_index = -1,
+				result,
+				i;
 	ExprContext *ecxt = GetPerTupleExprContext(estate);
 	TupleTableSlot *ecxt_scantuple_old = ecxt->ecxt_scantuple;
 
@@ -1973,30 +2209,46 @@ get_partition_for_tuple(PartitionDispatch *pd,
 
 		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;
+					/*
+					 * We cannot route tuples with NULL partition keys through
+					 * a range-partitioned table if it does not have a default
+					 * partition. In such case simply return that no partition
+					 * exists for routing null partition key.
+					 */
+					if (!partition_bound_has_default(partdesc->boundinfo))
+					{
+						*failed_at = parent;
+						*failed_slot = slot;
+						result = -1;
+						goto error_exit;
+					}
+					else
+					{
+						/*
+						 * If there is any null partition key, it would be
+						 * routed to the default partition.
+						 */
+						range_partkey_has_null = true;
+						break;
+					}
 				}
 			}
 		}
 
 		/*
-		 * A null partition key is only acceptable if null-accepting list
-		 * partition exists.
+		 * If partition strategy is LIST and this is a null partition key,
+		 * route it to the null-accepting partition. Otherwise, route by
+		 * searching the array of partition bounds.
 		 */
 		cur_index = -1;
-		if (isnull[0] && partition_bound_accepts_nulls(partdesc->boundinfo))
+		if (key->strategy == PARTITION_STRATEGY_LIST && isnull[0] &&
+			partition_bound_accepts_nulls(partdesc->boundinfo))
 			cur_index = partdesc->boundinfo->null_index;
-		else if (!isnull[0])
+		else if (!range_partkey_has_null && !isnull[0])
 		{
 			/* Else bsearch in partdesc->boundinfo */
 			bool		equal = false;
@@ -2029,11 +2281,17 @@ get_partition_for_tuple(PartitionDispatch *pd,
 		}
 
 		/*
-		 * cur_index < 0 means we failed to find a partition of this parent.
-		 * cur_index >= 0 means we either found the leaf partition, or the
-		 * next parent to find a partition of.
+		 * cur_index < 0 means we could not find a non-default partition of
+		 * this parent. cur_index >= 0 means we either found the leaf
+		 * partition, or the next parent to find a partition of.
+		 *
+		 * If we couldn't find a non-default partition check if the default
+		 * partition exists, if it does, get its index.
 		 */
 		if (cur_index < 0)
+			cur_index = partdesc->boundinfo->default_index;
+
+		if (cur_index < 0)
 		{
 			result = -1;
 			*failed_at = parent;
@@ -2085,6 +2343,8 @@ make_one_range_bound(PartitionKey key, int index, List *datums, bool lower)
 	ListCell   *lc;
 	int			i;
 
+	Assert(datums != NIL);
+
 	bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
 	bound->index = index;
 	bound->datums = (Datum *) palloc0(key->partnatts * sizeof(Datum));
@@ -2321,3 +2581,58 @@ partition_bound_bsearch(PartitionKey key, PartitionBoundInfo boundinfo,
 
 	return lo;
 }
+
+/*
+ * get_default_partition_oid
+ *
+ * If the given relation has a default partition return the OID of the default
+ * partition, otherwise return InvalidOid.
+ */
+Oid
+get_default_partition_oid(Oid parentId)
+{
+	HeapTuple	tuple;
+	Oid			defaultPartId = InvalidOid;
+
+	tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(parentId));
+
+	if (HeapTupleIsValid(tuple))
+	{
+		Form_pg_partitioned_table part_table_form;
+
+		part_table_form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+		defaultPartId = part_table_form->partdefid;
+	}
+
+	ReleaseSysCache(tuple);
+	return defaultPartId;
+}
+
+/*
+ * update_default_partition_oid
+ *
+ * Updates the pg_partition_table catalog partdefid field for the given parent
+ * with the given default partition oid.
+ */
+void
+update_default_partition_oid(Oid parentId, Oid defaultPartId)
+{
+	HeapTuple	tuple;
+	Relation	pg_partitioned_table;
+	Form_pg_partitioned_table part_table_form;
+
+	pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCacheCopy1(PARTRELID, ObjectIdGetDatum(parentId));
+
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of relation %u",
+			 parentId);
+
+	part_table_form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+	part_table_form->partdefid = defaultPartId;
+	CatalogTupleUpdate(pg_partitioned_table, &tuple->t_self, tuple);
+
+	heap_freetuple(tuple);
+	heap_close(pg_partitioned_table, RowExclusiveLock);
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 77f5761..d0a8223 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -774,7 +774,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	{
 		PartitionBoundSpec *bound;
 		ParseState *pstate;
-		Oid			parentId = linitial_oid(inheritOids);
+		Oid			parentId = linitial_oid(inheritOids),
+					defaultPartOid;
 		Relation	parent;
 
 		/* Already have strong enough lock on the parent */
@@ -790,6 +791,17 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 					 errmsg("\"%s\" is not partitioned",
 							RelationGetRelationName(parent))));
 
+		/*
+		 * A table cannot be created as a partition of a parent already having
+		 * a default partition.
+		 */
+		defaultPartOid = get_default_partition_oid(RelationGetRelid(parent));
+		if (OidIsValid(defaultPartOid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("cannot add a new partition to table \"%s\" having a default partition",
+							RelationGetRelationName(parent))));
+
 		/* Tranform the bound values */
 		pstate = make_parsestate(NULL);
 		pstate->p_sourcetext = queryString;
@@ -806,6 +818,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		/* Update the pg_class entry. */
 		StorePartitionBound(rel, parent, bound);
 
+		/* Update the default partition oid */
+		if (bound->is_default)
+			update_default_partition_oid(RelationGetRelid(parent), relationId);
+
 		heap_close(parent, NoLock);
 
 		/*
@@ -13658,6 +13674,15 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	ObjectAddress address;
 	const char *trigger_name;
 	bool		found_whole_row;
+	Oid			defaultPartOid;
+
+	/* A partition cannot be attached if there exists a default partition */
+	defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
+	if (OidIsValid(defaultPartOid))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot attach a new partition to table \"%s\" having a default partition",
+						RelationGetRelationName(rel))));
 
 	attachrel = heap_openrv(cmd->name, AccessExclusiveLock);
 
@@ -13814,6 +13839,11 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	/* OK to create inheritance.  Rest of the checks performed there */
 	CreateInheritance(attachrel, rel);
 
+	/* Update the default partition oid */
+	if (cmd->bound->is_default)
+		update_default_partition_oid(RelationGetRelid(rel),
+									 RelationGetRelid(attachrel));
+
 	/*
 	 * Check that the new partition's bound is valid and does not overlap any
 	 * of existing partitions of the parent - note that it does not return on
@@ -13882,8 +13912,25 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 				new_null[Natts_pg_class],
 				new_repl[Natts_pg_class];
 	ObjectAddress address;
+	Oid			defaultPartOid;
 
-	partRel = heap_openrv(name, AccessShareLock);
+	/*
+	 * We must lock the default partition, for the same reasons explained in
+	 * heap_drop_with_catalog().
+	 */
+	defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
+	if (OidIsValid(defaultPartOid))
+		LockRelationOid(defaultPartOid, AccessExclusiveLock);
+
+	/*
+	 * We already hold an exclusive lock on the default partition that is
+	 * going to be held until the transaction is commited, hence need to
+	 * acquire a shared lock only if the partition being detached is not the
+	 * default partition.
+	 */
+	partRel = heap_openrv(name, NoLock);
+	if (RelationGetRelid(partRel) != defaultPartOid)
+		LockRelationOid(RelationGetRelid(partRel), AccessShareLock);
 
 	/* All inheritance related checks are performed within the function */
 	RemoveInheritance(partRel, rel);
@@ -13913,6 +13960,24 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 	heap_freetuple(newtuple);
 	heap_close(classRel, RowExclusiveLock);
 
+	if (OidIsValid(defaultPartOid))
+	{
+		/*
+		 * If the detach relation is the default partition itself, invalidate
+		 * its entry in pg_partitioned_table.
+		 */
+		if (RelationGetRelid(partRel) == defaultPartOid)
+			update_default_partition_oid(RelationGetRelid(rel), InvalidOid);
+		else
+		{
+			/*
+			 * We must invalidate default partition's relcache, for the same
+			 * reasons explained in heap_drop_with_catalog().
+			 */
+			CacheInvalidateRelcacheByRelid(defaultPartOid);
+		}
+	}
+
 	/*
 	 * Invalidate the parent's relcache so that the partition is no longer
 	 * included in its partition descriptor.
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 9bae264..f1bed14 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4450,6 +4450,7 @@ _copyPartitionBoundSpec(const PartitionBoundSpec *from)
 	PartitionBoundSpec *newnode = makeNode(PartitionBoundSpec);
 
 	COPY_SCALAR_FIELD(strategy);
+	COPY_SCALAR_FIELD(is_default);
 	COPY_NODE_FIELD(listdatums);
 	COPY_NODE_FIELD(lowerdatums);
 	COPY_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 11731da..8b56b91 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2839,6 +2839,7 @@ static bool
 _equalPartitionBoundSpec(const PartitionBoundSpec *a, const PartitionBoundSpec *b)
 {
 	COMPARE_SCALAR_FIELD(strategy);
+	COMPARE_SCALAR_FIELD(is_default);
 	COMPARE_NODE_FIELD(listdatums);
 	COMPARE_NODE_FIELD(lowerdatums);
 	COMPARE_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 9ee3e23..b83d919 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3573,6 +3573,7 @@ _outPartitionBoundSpec(StringInfo str, const PartitionBoundSpec *node)
 	WRITE_NODE_TYPE("PARTITIONBOUNDSPEC");
 
 	WRITE_CHAR_FIELD(strategy);
+	WRITE_BOOL_FIELD(is_default);
 	WRITE_NODE_FIELD(listdatums);
 	WRITE_NODE_FIELD(lowerdatums);
 	WRITE_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 67b9e19..fbf8330 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2390,6 +2390,7 @@ _readPartitionBoundSpec(void)
 	READ_LOCALS(PartitionBoundSpec);
 
 	READ_CHAR_FIELD(strategy);
+	READ_BOOL_FIELD(is_default);
 	READ_NODE_FIELD(listdatums);
 	READ_NODE_FIELD(lowerdatums);
 	READ_NODE_FIELD(upperdatums);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 5eb3981..c303818 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -575,7 +575,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>			part_strategy
 %type <partelem>	part_elem
 %type <list>		part_params
-%type <partboundspec> ForValues
+%type <partboundspec> PartitionBoundSpec
 %type <node>		partbound_datum PartitionRangeDatum
 %type <list>		partbound_datum_list range_datum_list
 
@@ -1980,7 +1980,7 @@ alter_table_cmds:
 
 partition_cmd:
 			/* ALTER TABLE <name> ATTACH PARTITION <table_name> FOR VALUES */
-			ATTACH PARTITION qualified_name ForValues
+			ATTACH PARTITION qualified_name PartitionBoundSpec
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
 					PartitionCmd *cmd = makeNode(PartitionCmd);
@@ -2635,13 +2635,14 @@ alter_identity_column_option:
 				}
 		;
 
-ForValues:
+PartitionBoundSpec:
 			/* a LIST partition */
 			FOR VALUES IN_P '(' partbound_datum_list ')'
 				{
 					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
 
 					n->strategy = PARTITION_STRATEGY_LIST;
+					n->is_default = false;
 					n->listdatums = $5;
 					n->location = @3;
 
@@ -2654,12 +2655,24 @@ ForValues:
 					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
 
 					n->strategy = PARTITION_STRATEGY_RANGE;
+					n->is_default = false;
 					n->lowerdatums = $5;
 					n->upperdatums = $9;
 					n->location = @3;
 
 					$$ = n;
 				}
+
+			/* a DEFAULT partition */
+			| DEFAULT
+				{
+					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+					n->is_default = true;
+					n->location = @1;
+
+					$$ = n;
+				}
 		;
 
 partbound_datum:
@@ -3130,7 +3143,7 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
-			OptTypedTableElementList ForValues OptPartitionSpec OptWith
+			OptTypedTableElementList PartitionBoundSpec OptPartitionSpec OptWith
 			OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
@@ -3149,7 +3162,7 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
-			qualified_name OptTypedTableElementList ForValues OptPartitionSpec
+			qualified_name OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
 			OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
@@ -4864,7 +4877,7 @@ CreateForeignTableStmt:
 					$$ = (Node *) n;
 				}
 		| CREATE FOREIGN TABLE qualified_name
-			PARTITION OF qualified_name OptTypedTableElementList ForValues
+			PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
 			SERVER name create_generic_options
 				{
 					CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
@@ -4885,7 +4898,7 @@ CreateForeignTableStmt:
 					$$ = (Node *) n;
 				}
 		| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
-			PARTITION OF qualified_name OptTypedTableElementList ForValues
+			PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
 			SERVER name create_generic_options
 				{
 					CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 2058679..e285964 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -60,6 +61,7 @@
 #include "parser/parse_utilcmd.h"
 #include "parser/parser.h"
 #include "rewrite/rewriteManip.h"
+#include "storage/lmgr.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
@@ -3307,6 +3309,18 @@ transformPartitionBound(ParseState *pstate, Relation parent,
 	/* Avoid scribbling on input */
 	result_spec = copyObject(spec);
 
+	if (spec->is_default)
+	{
+		/*
+		 * In case of the default partition, parser had no way to identify the
+		 * partition strategy. Assign the parent's strategy to the default
+		 * partition bound spec.
+		 */
+		result_spec->strategy = strategy;
+
+		return result_spec;
+	}
+
 	if (strategy == PARTITION_STRATEGY_LIST)
 	{
 		ListCell   *cell;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 12decb0..71e8b1d 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8696,10 +8696,17 @@ get_rule_expr(Node *node, deparse_context *context,
 		case T_PartitionBoundSpec:
 			{
 				PartitionBoundSpec *spec = (PartitionBoundSpec *) node;
+				char		strategy = spec->strategy;
 				ListCell   *cell;
 				char	   *sep;
 
-				switch (spec->strategy)
+				if (spec->is_default)
+				{
+					appendStringInfoString(buf, "DEFAULT");
+					break;
+				}
+
+				switch (strategy)
 				{
 					case PARTITION_STRATEGY_LIST:
 						Assert(spec->listdatums != NIL);
@@ -8731,7 +8738,7 @@ get_rule_expr(Node *node, deparse_context *context,
 
 					default:
 						elog(ERROR, "unrecognized partition strategy: %d",
-							 (int) spec->strategy);
+							 (int) strategy);
 						break;
 				}
 			}
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6fb9bdd..579dd92 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1893,7 +1893,7 @@ describeOneTableDetails(const char *schemaname,
 			parent_name = PQgetvalue(result, 0, 0);
 			partdef = PQgetvalue(result, 0, 1);
 
-			if (PQnfields(result) == 3)
+			if (PQnfields(result) == 3 && !PQgetisnull(result, 0, 2))
 				partconstraintdef = PQgetvalue(result, 0, 2);
 
 			printfPQExpBuffer(&tmpbuf, _("Partition of: %s %s"), parent_name,
@@ -1902,8 +1902,12 @@ describeOneTableDetails(const char *schemaname,
 
 			if (partconstraintdef)
 			{
-				printfPQExpBuffer(&tmpbuf, _("Partition constraint: %s"),
-								  partconstraintdef);
+				/* If there isn't any constraint, show that explicitly */
+				if (partconstraintdef[0] == '\0')
+					printfPQExpBuffer(&tmpbuf, _("No partition constraint"));
+				else
+					printfPQExpBuffer(&tmpbuf, _("Partition constraint: %s"),
+									  partconstraintdef);
 				printTableAddFooter(&cont, tmpbuf.data);
 			}
 
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 2ab8809..a09c49d 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2053,7 +2053,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("ATTACH", "PARTITION", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 	else if (TailMatches2("FOR", "VALUES"))
 		COMPLETE_WITH_LIST2("FROM (", "IN (");
 
@@ -2492,7 +2492,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("PARTITION", "OF", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 
 /* CREATE TABLESPACE */
 	else if (Matches3("CREATE", "TABLESPACE", MatchAny))
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 2283c67..1dd70f2 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -99,4 +99,7 @@ extern int get_partition_for_tuple(PartitionDispatch *pd,
 						EState *estate,
 						PartitionDispatchData **failed_at,
 						TupleTableSlot **failed_slot);
+extern Oid	get_default_partition_oid(Oid parentId);
+extern void update_default_partition_oid(Oid parentId, Oid defaultPartId);
+
 #endif							/* PARTITION_H */
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
index 38d64d6..525e541 100644
--- a/src/include/catalog/pg_partitioned_table.h
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -32,6 +32,8 @@ CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
 	Oid			partrelid;		/* partitioned table oid */
 	char		partstrat;		/* partitioning strategy */
 	int16		partnatts;		/* number of partition key columns */
+	Oid			partdefid;		/* default partition oid; InvalidOid if there
+								 * isn't one */
 
 	/*
 	 * variable-length fields start here, but we allow direct access to
@@ -62,13 +64,14 @@ typedef FormData_pg_partitioned_table *Form_pg_partitioned_table;
  *		compiler constants for pg_partitioned_table
  * ----------------
  */
-#define Natts_pg_partitioned_table				7
+#define Natts_pg_partitioned_table				8
 #define Anum_pg_partitioned_table_partrelid		1
 #define Anum_pg_partitioned_table_partstrat		2
 #define Anum_pg_partitioned_table_partnatts		3
-#define Anum_pg_partitioned_table_partattrs		4
-#define Anum_pg_partitioned_table_partclass		5
-#define Anum_pg_partitioned_table_partcollation 6
-#define Anum_pg_partitioned_table_partexprs		7
+#define Anum_pg_partitioned_table_partdefid		4
+#define Anum_pg_partitioned_table_partattrs		5
+#define Anum_pg_partitioned_table_partclass		6
+#define Anum_pg_partitioned_table_partcollation 7
+#define Anum_pg_partitioned_table_partexprs		8
 
 #endif							/* PG_PARTITIONED_TABLE_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3171815..f3e4c69 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -797,6 +797,7 @@ typedef struct PartitionBoundSpec
 	NodeTag		type;
 
 	char		strategy;		/* see PARTITION_STRATEGY codes above */
+	bool		is_default;		/* is it a default partition bound? */
 
 	/* Partitioning info for LIST strategy: */
 	List	   *listdatums;		/* List of Consts (or A_Consts in raw tree) */
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 0f36423..c82c533 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3297,6 +3297,14 @@ SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::reg
 CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
 ERROR:  partition "fail_part" would overlap partition "part_1"
+-- check that an existing table can be attached as a default partition
+CREATE TABLE def_part (LIKE list_parted INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION def_part DEFAULT;
+-- check attaching default partition fails if a default partition already
+-- exists
+CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT;
+ERROR:  cannot attach a new partition to table "list_parted" having a default partition
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
 	a int,
@@ -3310,6 +3318,15 @@ ERROR:  partition constraint is violated by some row
 -- should be ok after deleting the bad row
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+-- check partition cannot be attached if default exists
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
+INSERT INTO list_parted2_def VALUES (11, 'z');
+CREATE TABLE part_3 (LIKE list_parted2);
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+ERROR:  cannot attach a new partition to table "list_parted2" having a default partition
+-- should be ok after detaching a default partition
+ALTER TABLE list_parted2 DETACH PARTITION list_parted2_def;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
 CREATE TABLE part_3_4 (
@@ -3350,6 +3367,12 @@ 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;
+-- New partition cannot be attached if a default partition exists
+CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS);
+ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT;
+ERROR:  cannot attach a new partition to table "range_parted" having a default partition
 -- check that leaf partitions are scanned when attaching a partitioned
 -- table
 CREATE TABLE part_5 (
@@ -3538,6 +3561,7 @@ ALTER TABLE list_parted2 ALTER COLUMN b TYPE text;
 ERROR:  cannot alter type of column named in partition key
 -- cleanup
 DROP TABLE list_parted, list_parted2, range_parted;
+DROP TABLE fail_def_part;
 -- more tests for certain multi-level partitioning scenarios
 create table p (a int, b int) partition by range (a, b);
 create table p1 (b int, a int not null) partition by range (b);
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index babda89..4f79612 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -467,6 +467,13 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
 ERROR:  invalid bound specification for a list partition
 LINE 1: ...BLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) T...
                                                              ^
+-- check default partition cannot be created more than once
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
+ERROR:  cannot add a new partition to table "list_parted" having a default partition
+-- check that no partition can be created after default partition
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (3);
+ERROR:  cannot add a new partition to table "list_parted" having a default partition
 -- specified literal can't be cast to the partition column data type
 CREATE TABLE bools (
 	a bool
@@ -585,6 +592,11 @@ 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;
+-- New partition cannot be created if a default partition exists
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90);
+ERROR:  cannot add a new partition to table "range_parted2" having a default partition
 -- now check for multi-column range partition key
 CREATE TABLE range_parted3 (
 	a int,
@@ -603,6 +615,8 @@ ERROR:  partition "fail_part" would overlap partition "part12"
 -- more specific ranges
 CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, minvalue) TO (1, maxvalue);
 ERROR:  partition "fail_part" would overlap partition "part10"
+-- check default partition can be added to multi-column range partitioned table
+CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
 -- check schema propagation from parent
 CREATE TABLE parted (
 	a text,
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index e159d62..48f5292 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -219,17 +219,66 @@ insert into part_null values (null, 0);
 create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b);
 create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
 create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
+-- test default partition
+create table part_default partition of list_parted default;
+-- Negative test: a row, which would fit in other partition, does not fit
+-- default partition, even when inserted directly
+insert into part_default values ('aa', 2);
+ERROR:  new row for relation "part_default" violates partition constraint
+DETAIL:  Failing row contains (aa, 2).
+insert into part_default values (null, 2);
+ERROR:  new row for relation "part_default" violates partition constraint
+DETAIL:  Failing row contains (null, 2).
+-- ok
+insert into part_default values ('Zz', 2);
+-- test if default partition works as expected for multi-level partitioned
+-- table as well as when default partition itself is further partitioned
+drop table part_default;
+create table part_xx_yy partition of list_parted for values in ('xx', 'yy') partition by list (a);
+create table part_xx_yy_p1 partition of part_xx_yy for values in ('xx');
+create table part_xx_yy_defpart partition of part_xx_yy default;
+create table part_default partition of list_parted default partition by range(b);
+create table part_default_p1 partition of part_default for values from (20) to (30);
+create table part_default_p2 partition of part_default for values from (30) to (40);
 -- fail
 insert into part_ee_ff1 values ('EE', 11);
 ERROR:  new row for relation "part_ee_ff1" violates partition constraint
 DETAIL:  Failing row contains (EE, 11).
+insert into part_default_p2 values ('gg', 43);
+ERROR:  new row for relation "part_default_p2" violates partition constraint
+DETAIL:  Failing row contains (gg, 43).
 -- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
 insert into part_ee_ff1 values ('cc', 1);
 ERROR:  new row for relation "part_ee_ff1" violates partition constraint
 DETAIL:  Failing row contains (cc, 1).
+insert into part_default values ('gg', 43);
+ERROR:  no partition of relation "part_default" found for row
+DETAIL:  Partition key of the failing row contains (b) = (43).
 -- ok
 insert into part_ee_ff1 values ('ff', 1);
 insert into part_ee_ff2 values ('ff', 11);
+insert into part_default_p1 values ('cd', 25);
+insert into part_default_p2 values ('de', 35);
+insert into list_parted values ('ab', 21);
+insert into list_parted values ('xx', 1);
+insert into list_parted values ('yy', 2);
+select tableoid::regclass, * from list_parted;
+      tableoid      | a  | b  
+--------------------+----+----
+ part_cc_dd         | cC |  1
+ part_ee_ff1        | ff |  1
+ part_ee_ff2        | ff | 11
+ part_xx_yy_p1      | xx |  1
+ part_xx_yy_defpart | yy |  2
+ part_null          |    |  0
+ part_default_p1    | cd | 25
+ part_default_p1    | ab | 21
+ part_default_p2    | de | 35
+(9 rows)
+
+-- drop default partition, as we need to add some more partitions to test tuple
+-- routing
+drop table part_default;
 -- Check tuple routing for partitioned tables
 -- fail
 insert into range_parted values ('a', 0);
@@ -249,6 +298,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  
 ----------+---+----
@@ -258,7 +319,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);
@@ -274,17 +340,19 @@ DETAIL:  Partition key of the failing row contains (b) = (0).
 insert into list_parted values ('EE', 1);
 insert into part_ee_ff values ('EE', 10);
 select tableoid::regclass, * from list_parted;
-  tableoid   | a  | b  
--------------+----+----
- part_aa_bb  | aA |   
- part_cc_dd  | cC |  1
- part_ee_ff1 | ff |  1
- part_ee_ff1 | EE |  1
- part_ee_ff2 | ff | 11
- part_ee_ff2 | EE | 10
- part_null   |    |  0
- part_null   |    |  1
-(8 rows)
+      tableoid      | a  | b  
+--------------------+----+----
+ part_aa_bb         | aA |   
+ part_cc_dd         | cC |  1
+ part_ee_ff1        | ff |  1
+ part_ee_ff1        | EE |  1
+ part_ee_ff2        | ff | 11
+ part_ee_ff2        | EE | 10
+ part_xx_yy_p1      | xx |  1
+ part_xx_yy_defpart | yy |  2
+ part_null          |    |  0
+ part_null          |    |  1
+(10 rows)
 
 -- some more tests to exercise tuple-routing with multi-level partitioning
 create table part_gg partition of list_parted for values in ('gg') partition by range (b);
@@ -316,6 +384,23 @@ select tableoid::regclass::text, a, min(b) as min_b, max(b) as max_b from list_p
 
 -- cleanup
 drop table range_parted, list_parted;
+-- test that a default partition added as the first partition accepts any value
+-- including null
+create table list_parted (a int) partition by list (a);
+create table part_default partition of list_parted default;
+insert into part_default values (null);
+insert into part_default values (1);
+insert into part_default values (-1);
+select tableoid::regclass, a from list_parted;
+   tableoid   | a  
+--------------+----
+ part_default |   
+ part_default |  1
+ part_default | -1
+(3 rows)
+
+-- cleanup
+drop table list_parted;
 -- more tests for certain multi-level partitioning scenarios
 create table mlparted (a int, b int) partition by range (a, b);
 create table mlparted1 (b int not null, a int not null) partition by range ((b+0));
@@ -425,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/plancache.out b/src/test/regress/expected/plancache.out
index 3f3db33..fb90357 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -252,3 +252,25 @@ NOTICE:  3
  
 (1 row)
 
+-- Check that addition or removal of any partition is correctly dealt with by
+-- default partition table when it is being used in prepared statement.
+create table list_parted (a int) partition by list(a);
+create table list_part_null partition of list_parted for values in (null);
+create table list_part_1 partition of list_parted for values in (1);
+create table list_part_def partition of list_parted default;
+prepare pstmt_def_insert (int) as insert into list_part_def values($1);
+-- should fail
+execute pstmt_def_insert(null);
+ERROR:  new row for relation "list_part_def" violates partition constraint
+DETAIL:  Failing row contains (null).
+execute pstmt_def_insert(1);
+ERROR:  new row for relation "list_part_def" violates partition constraint
+DETAIL:  Failing row contains (1).
+alter table list_parted detach partition list_part_null;
+-- should be ok
+execute pstmt_def_insert(null);
+drop table list_part_1;
+-- should be ok
+execute pstmt_def_insert(1);
+drop table list_parted, list_part_null;
+deallocate pstmt_def_insert;
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 9366f04..cfb6aa8 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -218,5 +218,29 @@ 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
+) partition by list (a);
+create table list_part1  partition of list_parted for values in ('a', 'b');
+create table list_default partition of list_parted default;
+insert into list_part1 values ('a', 1);
+insert into list_default values ('d', 10);
+-- fail
+update list_default set a = 'a' where a = 'd';
+ERROR:  new row for relation "list_default" violates partition constraint
+DETAIL:  Failing row contains (a, 10).
+-- ok
+update list_default set a = 'x' where a = 'd';
 -- cleanup
 drop table range_parted;
+drop table list_parted;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index e6f6669..ac6f9d3 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2111,6 +2111,13 @@ SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::reg
 -- check that the new partition won't overlap with an existing partition
 CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+-- check that an existing table can be attached as a default partition
+CREATE TABLE def_part (LIKE list_parted INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION def_part DEFAULT;
+-- check attaching default partition fails if a default partition already
+-- exists
+CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT;
 
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
@@ -2127,6 +2134,15 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 
+-- check partition cannot be attached if default exists
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
+INSERT INTO list_parted2_def VALUES (11, 'z');
+CREATE TABLE part_3 (LIKE list_parted2);
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+-- should be ok after detaching a default partition
+ALTER TABLE list_parted2 DETACH PARTITION list_parted2_def;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
 CREATE TABLE part_3_4 (
@@ -2172,6 +2188,13 @@ 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;
+
+-- New partition cannot be attached if a default partition exists
+CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS);
+ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT;
+
 -- check that leaf partitions are scanned when attaching a partitioned
 -- table
 CREATE TABLE part_5 (
@@ -2327,6 +2350,7 @@ ALTER TABLE list_parted2 ALTER COLUMN b TYPE text;
 
 -- cleanup
 DROP TABLE list_parted, list_parted2, range_parted;
+DROP TABLE fail_def_part;
 
 -- more tests for certain multi-level partitioning scenarios
 create table p (a int, b int) partition by range (a, b);
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 1c0ce927..24f005e 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -447,6 +447,12 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
 -- trying to specify range for list partitioned table
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
 
+-- check default partition cannot be created more than once
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
+-- check that no partition can be created after default partition
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (3);
+
 -- specified literal can't be cast to the partition column data type
 CREATE TABLE bools (
 	a bool
@@ -546,6 +552,12 @@ 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;
+
+-- New partition cannot be created if a default partition exists
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90);
+
 -- now check for multi-column range partition key
 CREATE TABLE range_parted3 (
 	a int,
@@ -565,6 +577,9 @@ CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1,
 -- more specific ranges
 CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, minvalue) TO (1, maxvalue);
 
+-- check default partition can be added to multi-column range partitioned table
+CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
+
 -- check schema propagation from parent
 
 CREATE TABLE parted (
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 6f17872..7fc5688 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -132,13 +132,42 @@ create table part_ee_ff partition of list_parted for values in ('ee', 'ff') part
 create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
 create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
 
+-- test default partition
+create table part_default partition of list_parted default;
+-- Negative test: a row, which would fit in other partition, does not fit
+-- default partition, even when inserted directly
+insert into part_default values ('aa', 2);
+insert into part_default values (null, 2);
+-- ok
+insert into part_default values ('Zz', 2);
+-- test if default partition works as expected for multi-level partitioned
+-- table as well as when default partition itself is further partitioned
+drop table part_default;
+create table part_xx_yy partition of list_parted for values in ('xx', 'yy') partition by list (a);
+create table part_xx_yy_p1 partition of part_xx_yy for values in ('xx');
+create table part_xx_yy_defpart partition of part_xx_yy default;
+create table part_default partition of list_parted default partition by range(b);
+create table part_default_p1 partition of part_default for values from (20) to (30);
+create table part_default_p2 partition of part_default for values from (30) to (40);
+
 -- fail
 insert into part_ee_ff1 values ('EE', 11);
+insert into part_default_p2 values ('gg', 43);
 -- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
 insert into part_ee_ff1 values ('cc', 1);
+insert into part_default values ('gg', 43);
 -- ok
 insert into part_ee_ff1 values ('ff', 1);
 insert into part_ee_ff2 values ('ff', 11);
+insert into part_default_p1 values ('cd', 25);
+insert into part_default_p2 values ('de', 35);
+insert into list_parted values ('ab', 21);
+insert into list_parted values ('xx', 1);
+insert into list_parted values ('yy', 2);
+select tableoid::regclass, * from list_parted;
+-- drop default partition, as we need to add some more partitions to test tuple
+-- routing
+drop table part_default;
 
 -- Check tuple routing for partitioned tables
 
@@ -154,8 +183,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');
@@ -188,6 +228,17 @@ select tableoid::regclass::text, a, min(b) as min_b, max(b) as max_b from list_p
 -- cleanup
 drop table range_parted, list_parted;
 
+-- test that a default partition added as the first partition accepts any value
+-- including null
+create table list_parted (a int) partition by list (a);
+create table part_default partition of list_parted default;
+insert into part_default values (null);
+insert into part_default values (1);
+insert into part_default values (-1);
+select tableoid::regclass, a from list_parted;
+-- cleanup
+drop table list_parted;
+
 -- more tests for certain multi-level partitioning scenarios
 create table mlparted (a int, b int) partition by range (a, b);
 create table mlparted1 (b int not null, a int not null) partition by range ((b+0));
@@ -274,6 +325,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/plancache.sql b/src/test/regress/sql/plancache.sql
index bc20861..a8e357c 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -156,3 +156,22 @@ end$$ language plpgsql;
 
 select cachebug();
 select cachebug();
+
+-- Check that addition or removal of any partition is correctly dealt with by
+-- default partition table when it is being used in prepared statement.
+create table list_parted (a int) partition by list(a);
+create table list_part_null partition of list_parted for values in (null);
+create table list_part_1 partition of list_parted for values in (1);
+create table list_part_def partition of list_parted default;
+prepare pstmt_def_insert (int) as insert into list_part_def values($1);
+-- should fail
+execute pstmt_def_insert(null);
+execute pstmt_def_insert(1);
+alter table list_parted detach partition list_part_null;
+-- should be ok
+execute pstmt_def_insert(null);
+drop table list_part_1;
+-- should be ok
+execute pstmt_def_insert(1);
+drop table list_parted, list_part_null;
+deallocate pstmt_def_insert;
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 6637119..1ce7a94 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -125,5 +125,28 @@ 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
+) partition by list (a);
+create table list_part1  partition of list_parted for values in ('a', 'b');
+create table list_default partition of list_parted default;
+insert into list_part1 values ('a', 1);
+insert into list_default values ('d', 10);
+
+-- fail
+update list_default set a = 'a' where a = 'd';
+-- ok
+update list_default set a = 'x' where a = 'd';
+
 -- cleanup
 drop table range_parted;
+drop table list_parted;
-- 
2.7.4

0003-Default-partition-extended-for-create-and-attach.patch0000664000175000017500000012441613154226226023216 0ustar  jeevanjeevanFrom 175a36785ea6fe40d8e2f0d53eae6eb94023cd18 Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Wed, 6 Sep 2017 16:35:31 +0530
Subject: [PATCH 3/6] Default partition extended for create and attach

1. This patch extends the previous patch in this series to allow a
new partition to be created or attached even when a default
partition exists.
2. Also, extends the regression tests to test this functionality.
in this series.
3. While adding a new partition we need to make sure that default
partition does not contain any rows that would fit in newly added
partition. So, in this patch adds a code to negate the partition
constraints of new partition so as these constraints form the part
of would be default partition constraints. Then the default
partition is scanned to check if there exist a row that does not
hold these negated partition constraints, if there exists such a
row error out.
4. While doing 3, to optimize the validation scan a check is done,
if the existing constraints on the default partition imply that it
will not contain any row that would belong to the new partition,
then the validation scan is skipped.

Jeevan Ladhe, some refactoring by Ashutosh bapat.
---
 src/backend/catalog/heap.c                 |  33 ++---
 src/backend/catalog/partition.c            | 188 ++++++++++++++++++++++++++++-
 src/backend/commands/tablecmds.c           | 112 +++++++++++++----
 src/include/catalog/partition.h            |   3 +
 src/include/commands/tablecmds.h           |   4 +
 src/test/regress/expected/alter_table.out  |  39 ++++--
 src/test/regress/expected/create_table.out |  22 ++--
 src/test/regress/expected/insert.out       |   8 +-
 src/test/regress/expected/plancache.out    |   4 +
 src/test/regress/sql/alter_table.sql       |  31 ++++-
 src/test/regress/sql/create_table.sql      |  17 ++-
 src/test/regress/sql/insert.sql            |   3 -
 src/test/regress/sql/plancache.sql         |   2 +
 13 files changed, 389 insertions(+), 77 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 2952d40..ef213b6 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1778,15 +1778,8 @@ heap_drop_with_catalog(Oid relid)
 		LockRelationOid(parentOid, AccessExclusiveLock);
 
 		/*
-		 * The partition constraint of the default partition depends on the
-		 * partition bounds of every other partition. It is possible that
-		 * other backend might be about to execute a query on the default
-		 * partition table and the query relies on previously cached default
-		 * partition constraints, which won't be correct after removal of a
-		 * partition. We must therefore take a table lock strong enough to
-		 * prevent all queries on the default partition from proceeding until
-		 * we commit and send out a shared-cache-inval notice that will make
-		 * them update their index lists. Note that we need not lock the
+		 * We must also lock the default partition, for the same reasons
+		 * explained in DefineRelation(). Note that we need not lock the
 		 * default partition if the table being dropped itself is the default
 		 * partition, because it is going to be locked further.
 		 */
@@ -1910,11 +1903,9 @@ heap_drop_with_catalog(Oid relid)
 	if (OidIsValid(parentOid))
 	{
 		/*
-		 * The partition constraint for the default partition depends on the
-		 * partition bounds of every other partition, so we must invalidate
-		 * the relcache entry for that partition every time a partition is
-		 * added or removed. If the default partition itself is dropped no
-		 * need to invalidate its relcache.
+		 * We must invalidate default partition's relcache, for the same
+		 * reasons explained in StorePartitionBound(). If the default
+		 * partition itself is dropped no need to invalidate its relcache.
 		 */
 		if (OidIsValid(defaultPartOid) && relid != defaultPartOid)
 			CacheInvalidateRelcacheByRelid(defaultPartOid);
@@ -3259,7 +3250,8 @@ RemovePartitionKeyByRelId(Oid relid)
  *		relispartition to true
  *
  * Also, invalidate the parent's relcache, so that the next rebuild will load
- * the new partition's info into its partition descriptor.
+ * the new partition's info into its partition descriptor.  If there is a
+ * default partition, we must invalidate its relcache entry as well.
  */
 void
 StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
@@ -3270,6 +3262,7 @@ StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
 	Datum		new_val[Natts_pg_class];
 	bool		new_null[Natts_pg_class],
 				new_repl[Natts_pg_class];
+	Oid			defaultPartOid;
 
 	/* Update pg_class tuple */
 	classRel = heap_open(RelationRelationId, RowExclusiveLock);
@@ -3307,5 +3300,15 @@ StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
 	heap_freetuple(newtuple);
 	heap_close(classRel, RowExclusiveLock);
 
+	/*
+	 * The partition constraint for the default partition depends on the
+	 * partition bounds of every other partition, so we must invalidate the
+	 * relcache entry for that partition every time a partition is added or
+	 * removed.
+	 */
+	defaultPartOid = get_default_partition_oid(RelationGetRelid(parent));
+	if (OidIsValid(defaultPartOid))
+		CacheInvalidateRelcacheByRelid(defaultPartOid);
+
 	CacheInvalidateRelcache(parent);
 }
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index ac51bca..782d5a4 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -29,6 +29,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_type.h"
+#include "commands/tablecmds.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -36,6 +37,7 @@
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planmain.h"
+#include "optimizer/prep.h"
 #include "optimizer/var.h"
 #include "rewrite/rewriteManip.h"
 #include "storage/lmgr.h"
@@ -707,12 +710,23 @@ check_new_partition_bound(char *relname, Relation parent,
 {
 	PartitionKey key = RelationGetPartitionKey(parent);
 	PartitionDesc partdesc = RelationGetPartitionDesc(parent);
+	PartitionBoundInfo boundinfo = partdesc->boundinfo;
 	ParseState *pstate = make_parsestate(NULL);
 	int			with = -1;
 	bool		overlap = false;
 
 	if (spec->is_default)
-		return;
+	{
+		if (boundinfo == NULL || !partition_bound_has_default(boundinfo))
+			return;
+
+		/* Default partition already exists, error out. */
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("partition \"%s\" conflicts with existing default partition \"%s\"",
+						relname, get_rel_name(partdesc->oids[boundinfo->default_index])),
+				 parser_errposition(pstate, spec->location)));
+	}
 
 	switch (key->strategy)
 	{
@@ -722,13 +736,13 @@ check_new_partition_bound(char *relname, Relation parent,
 
 				if (partdesc->nparts > 0)
 				{
-					PartitionBoundInfo boundinfo = partdesc->boundinfo;
 					ListCell   *cell;
 
 					Assert(boundinfo &&
 						   boundinfo->strategy == PARTITION_STRATEGY_LIST &&
 						   (boundinfo->ndatums > 0 ||
-							partition_bound_accepts_nulls(boundinfo)));
+							partition_bound_accepts_nulls(boundinfo) ||
+							partition_bound_has_default(boundinfo)));
 
 					foreach(cell, spec->listdatums)
 					{
@@ -793,8 +807,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
@@ -872,6 +888,139 @@ check_new_partition_bound(char *relname, Relation parent,
 }
 
 /*
+ * check_default_allows_bound
+ *
+ * This function checks if there exists a row in the default partition that
+ * fits in the new partition being added and throws an error if it finds one.
+ */
+void
+check_default_allows_bound(Relation parent, Relation default_rel,
+						   PartitionBoundSpec *new_spec)
+{
+	List	   *new_part_constraints;
+	List	   *def_part_constraints;
+	List	   *all_parts;
+	ListCell   *lc;
+
+	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);
+
+	/*
+	 * If the existing constraints on the default partition imply that it will
+	 * not contain any row that would belong to the new partition, we can
+	 * avoid scanning the default partition.
+	 */
+	if (PartConstraintImpliedByRelConstraint(default_rel, def_part_constraints))
+	{
+		ereport(INFO,
+				(errmsg("partition constraint for table \"%s\" is implied by existing constraints",
+						RelationGetRelationName(default_rel))));
+		return;
+	}
+
+	/*
+	 * Bad luck, scan the default partition and its subpartitions, and check
+	 * if any of the row does not satisfy the partition constraints that are
+	 * going to be imposed as additional constraints on default partition by
+	 * addition of the new partition.
+	 */
+	if (default_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		all_parts = find_all_inheritors(RelationGetRelid(default_rel),
+										AccessExclusiveLock, NULL);
+	else
+		all_parts = list_make1_oid(RelationGetRelid(default_rel));
+
+	foreach(lc, all_parts)
+	{
+		Oid			part_relid = lfirst_oid(lc);
+		Relation	part_rel;
+		Expr	   *constr;
+		Expr	   *partition_constraint;
+		EState	   *estate;
+		HeapTuple	tuple;
+		ExprState  *partqualstate = NULL;
+		Snapshot	snapshot;
+		TupleDesc	tupdesc;
+		ExprContext *econtext;
+		HeapScanDesc scan;
+		MemoryContext oldCxt;
+		TupleTableSlot *tupslot;
+
+		/* Lock already taken above. */
+		if (part_relid != RelationGetRelid(default_rel))
+			part_rel = heap_open(part_relid, NoLock);
+		else
+			part_rel = default_rel;
+
+		/*
+		 * Only RELKIND_RELATION relations (i.e. leaf partitions) need to be
+		 * scanned.
+		 */
+		if (part_rel->rd_rel->relkind != RELKIND_RELATION)
+		{
+			if (part_rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+				ereport(WARNING,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("skipped scanning foreign table \"%s\" which is a partition of default partition \"%s\"",
+								RelationGetRelationName(part_rel),
+								RelationGetRelationName(default_rel))));
+
+			if (RelationGetRelid(default_rel) != RelationGetRelid(part_rel))
+				heap_close(part_rel, NoLock);
+
+			continue;
+		}
+
+		tupdesc = CreateTupleDescCopy(RelationGetDescr(part_rel));
+		constr = linitial(def_part_constraints);
+		partition_constraint = (Expr *) map_partition_varattnos((List *) constr,
+																1, part_rel, parent, NULL);
+		estate = CreateExecutorState();
+
+		/* Build expression execution states for partition check quals */
+		partqualstate = ExecPrepareExpr(partition_constraint, estate);
+
+		econtext = GetPerTupleExprContext(estate);
+		snapshot = RegisterSnapshot(GetLatestSnapshot());
+		scan = heap_beginscan(part_rel, snapshot, 0, NULL);
+		tupslot = MakeSingleTupleTableSlot(tupdesc);
+
+		/*
+		 * Switch to per-tuple memory context and reset it for each tuple
+		 * produced, so we don't leak memory.
+		 */
+		oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		{
+			ExecStoreTuple(tuple, tupslot, InvalidBuffer, false);
+			econtext->ecxt_scantuple = tupslot;
+
+			if (!ExecCheck(partqualstate, econtext))
+				ereport(ERROR,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("updated partition constraint for default partition \"%s\" would be violated by some row",
+								RelationGetRelationName(default_rel))));
+
+			ResetExprContext(econtext);
+			CHECK_FOR_INTERRUPTS();
+		}
+
+		MemoryContextSwitchTo(oldCxt);
+		heap_endscan(scan);
+		UnregisterSnapshot(snapshot);
+		ExecDropSingleTupleTableSlot(tupslot);
+		FreeExecutorState(estate);
+
+		if (RelationGetRelid(default_rel) != RelationGetRelid(part_rel))
+			heap_close(part_rel, NoLock);	/* keep the lock until commit */
+	}
+}
+
+/*
  * get_partition_parent
  *
  * Returns inheritance parent of a partition by scanning pg_inherits
@@ -2600,3 +2749,32 @@ update_default_partition_oid(Oid parentId, Oid defaultPartId)
 	heap_freetuple(tuple);
 	heap_close(pg_partitioned_table, RowExclusiveLock);
 }
+
+/*
+ * get_default_part_validation_constraint
+ *
+ * This function returns negated constraint of new_part_constraints which
+ * would be an integral part of the default partition constraints after
+ * addition of the partition to which the new_part_constraints belongs.
+ */
+List *
+get_default_part_validation_constraint(List *new_part_constraints)
+{
+	Expr	   *defPartConstraint;
+
+	defPartConstraint = make_ands_explicit(new_part_constraints);
+
+	/*
+	 * Derieve the partition constraints of default partition by negating the
+	 * given partition constraints. The partition constraint never evaluates
+	 * to NULL, so negating it like this is safe.
+	 */
+	defPartConstraint = makeBoolExpr(NOT_EXPR,
+									 list_make1(defPartConstraint),
+									 -1);
+	defPartConstraint = (Expr *) eval_const_expressions(NULL,
+														(Node *) defPartConstraint);
+	defPartConstraint = canonicalize_qual(defPartConstraint);
+
+	return list_make1(defPartConstraint);
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 9829616..bd25070 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -168,6 +168,8 @@ typedef struct AlteredTableInfo
 	bool		chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
 	char		newrelpersistence;	/* if above is true */
 	Expr	   *partition_constraint;	/* for attach partition validation */
+	/* true, if is a default partition or a child of default partition */
+	bool		is_default_partition;
 	/* Objects to rebuild after completing ALTER TYPE operations */
 	List	   *changedConstraintOids;	/* OIDs of constraints to rebuild */
 	List	   *changedConstraintDefs;	/* string definitions of same */
@@ -473,11 +475,10 @@ static void CreateInheritance(Relation child_rel, Relation parent_rel);
 static void RemoveInheritance(Relation child_rel, Relation parent_rel);
 static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
 					  PartitionCmd *cmd);
-static bool PartConstraintImpliedByRelConstraint(Relation scanrel,
-									 List *partConstraint);
 static void ValidatePartitionConstraints(List **wqueue, Relation scanrel,
 							 List *scanrel_children,
-							 List *partConstraint);
+							 List *partConstraint,
+							 bool scanrel_is_default);
 static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
 
 
@@ -776,7 +777,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		ParseState *pstate;
 		Oid			parentId = linitial_oid(inheritOids),
 					defaultPartOid;
-		Relation	parent;
+		Relation	parent,
+					defaultRel;
 
 		/* Already have strong enough lock on the parent */
 		parent = heap_open(parentId, NoLock);
@@ -792,15 +794,28 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 							RelationGetRelationName(parent))));
 
 		/*
-		 * A table cannot be created as a partition of a parent already having
-		 * a default partition.
+		 * The partition constraint of the default partition depends on the
+		 * partition bounds of every other partition. It is possible that
+		 * other backend might be about to execute a query on the default
+		 * partition table and the query relies on previously cached default
+		 * partition constraints, which won't be correct after removal of a
+		 * partition. We must therefore take a table lock strong enough to
+		 * prevent all queries on the default partition from proceeding until
+		 * we commit and send out a shared-cache-inval notice that will make
+		 * them update their index lists.
+		 *
+		 * Order of locking: The relation being added won't be visible to
+		 * other backends until it is committed, hence here in
+		 * DefineRelation() the order of locking the default partition and the
+		 * relation being added does not matter. But at all other places we
+		 * need to lock the default relation before we lock the relation being
+		 * added or removed i.e. we should take the lock in same order at all
+		 * the places such that lock parent, lock default partition and then
+		 * lock the partition so as to avoid a deadlock.
 		 */
 		defaultPartOid = get_default_partition_oid(RelationGetRelid(parent));
 		if (OidIsValid(defaultPartOid))
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-					 errmsg("cannot add a new partition to table \"%s\" having a default partition",
-							RelationGetRelationName(parent))));
+			defaultRel = heap_open(defaultPartOid, AccessExclusiveLock);
 
 		/* Tranform the bound values */
 		pstate = make_parsestate(NULL);
@@ -815,6 +830,20 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		 */
 		check_new_partition_bound(relname, parent, bound);
 
+		/*
+		 * If the default partition exists, its partition constraints will
+		 * change after the addition of this new partition such that it won't
+		 * allow any row that qualifies for this new partition. So, check that
+		 * the existing data in the default partition satisfies the constraint
+		 * as it will exist after adding this partition.
+		 */
+		if (OidIsValid(defaultPartOid))
+		{
+			check_default_allows_bound(parent, defaultRel, bound);
+			/* Keep the lock until commit. */
+			heap_close(defaultRel, NoLock);
+		}
+
 		/* Update the pg_class entry. */
 		StorePartitionBound(rel, parent, bound);
 
@@ -4611,9 +4640,16 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 			}
 
 			if (partqualstate && !ExecCheck(partqualstate, econtext))
-				ereport(ERROR,
-						(errcode(ERRCODE_CHECK_VIOLATION),
-						 errmsg("partition constraint is violated by some row")));
+			{
+				if (tab->is_default_partition)
+					ereport(ERROR,
+							(errcode(ERRCODE_CHECK_VIOLATION),
+							 errmsg("updated partition constraint for default partition would be violated by some row")));
+				else
+					ereport(ERROR,
+							(errcode(ERRCODE_CHECK_VIOLATION),
+							 errmsg("partition constraint is violated by some row")));
+			}
 
 			/* Write the tuple out to the new relation */
 			if (newrel)
@@ -13467,7 +13503,7 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
  * Existing constraints includes its check constraints and column-level
  * NOT NULL constraints and partConstraint describes the partition constraint.
  */
-static bool
+bool
 PartConstraintImpliedByRelConstraint(Relation scanrel,
 									 List *partConstraint)
 {
@@ -13554,7 +13590,8 @@ PartConstraintImpliedByRelConstraint(Relation scanrel,
 static void
 ValidatePartitionConstraints(List **wqueue, Relation scanrel,
 							 List *scanrel_children,
-							 List *partConstraint)
+							 List *partConstraint,
+							 bool scanrel_is_default)
 {
 	bool		found_whole_row;
 	ListCell   *lc;
@@ -13616,6 +13653,7 @@ ValidatePartitionConstraints(List **wqueue, Relation scanrel,
 		/* Grab a work queue entry. */
 		tab = ATGetQueueEntry(wqueue, part_rel);
 		tab->partition_constraint = (Expr *) linitial(my_partconstr);
+		tab->is_default_partition = scanrel_is_default;
 
 		/* keep our lock until commit */
 		if (part_rel != scanrel)
@@ -13644,14 +13682,15 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	const char *trigger_name;
 	bool		found_whole_row;
 	Oid			defaultPartOid;
+	List	   *partBoundConstraint;
 
-	/* A partition cannot be attached if there exists a default partition */
+	/*
+	 * We must lock the default partition, for the same reasons explained in
+	 * DefineRelation().
+	 */
 	defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
 	if (OidIsValid(defaultPartOid))
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-				 errmsg("cannot attach a new partition to table \"%s\" having a default partition",
-						RelationGetRelationName(rel))));
+		LockRelationOid(defaultPartOid, AccessExclusiveLock);
 
 	attachrel = heap_openrv(cmd->name, AccessExclusiveLock);
 
@@ -13829,8 +13868,8 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	 * If the parent itself is a partition, make sure to include its
 	 * constraint as well.
 	 */
-	partConstraint = list_concat(get_qual_from_partbound(attachrel, rel,
-														 cmd->bound),
+	partBoundConstraint = get_qual_from_partbound(attachrel, rel, cmd->bound);
+	partConstraint = list_concat(partBoundConstraint,
 								 RelationGetPartitionQual(rel));
 
 	/* Skip validation if there are no constraints to validate. */
@@ -13853,7 +13892,30 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 
 		/* Validate partition constraints against the table being attached. */
 		ValidatePartitionConstraints(wqueue, attachrel, attachrel_children,
-									 partConstraint);
+									 partConstraint, false);
+
+		/*
+		 * Check whether default partition has a row that would fit the
+		 * partition being attached.
+		 */
+		defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
+		if (OidIsValid(defaultPartOid))
+		{
+			Relation	defaultrel;
+			List	   *defaultrel_children;
+			List	   *defPartConstraint;
+
+			/* We already have taken a lock on default partition. */
+			defaultrel = heap_open(defaultPartOid, NoLock);
+			defPartConstraint = get_default_part_validation_constraint(partBoundConstraint);
+			defaultrel_children = find_all_inheritors(defaultPartOid,
+													  AccessExclusiveLock, NULL);
+			ValidatePartitionConstraints(wqueue, defaultrel, defaultrel_children,
+										 defPartConstraint, true);
+
+			/* keep our lock until commit. */
+			heap_close(defaultrel, NoLock);
+		}
 	}
 
 	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachrel));
@@ -13885,7 +13947,7 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 
 	/*
 	 * We must lock the default partition, for the same reasons explained in
-	 * heap_drop_with_catalog().
+	 * DefineRelation().
 	 */
 	defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
 	if (OidIsValid(defaultPartOid))
@@ -13941,7 +14003,7 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 		{
 			/*
 			 * We must invalidate default partition's relcache, for the same
-			 * reasons explained in heap_drop_with_catalog().
+			 * reasons explained in StorePartitionBound().
 			 */
 			CacheInvalidateRelcacheByRelid(defaultPartOid);
 		}
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 1dd70f2..ee0cc15 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -101,5 +101,8 @@ extern int get_partition_for_tuple(PartitionDispatch *pd,
 						TupleTableSlot **failed_slot);
 extern Oid	get_default_partition_oid(Oid parentId);
 extern void update_default_partition_oid(Oid parentId, Oid defaultPartId);
+extern void check_default_allows_bound(Relation parent, Relation defaultRel,
+						   PartitionBoundSpec *new_spec);
+extern List *get_default_part_validation_constraint(List *new_part_constaints);
 
 #endif							/* PARTITION_H */
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index abd31b6..da3ff5d 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -18,6 +18,7 @@
 #include "catalog/dependency.h"
 #include "catalog/objectaddress.h"
 #include "nodes/parsenodes.h"
+#include "catalog/partition.h"
 #include "storage/lock.h"
 #include "utils/relcache.h"
 
@@ -87,4 +88,7 @@ extern void RangeVarCallbackOwnsTable(const RangeVar *relation,
 
 extern void RangeVarCallbackOwnsRelation(const RangeVar *relation,
 							 Oid relId, Oid oldRelId, void *noCatalogs);
+extern bool PartConstraintImpliedByRelConstraint(Relation scanrel,
+									 List *partConstraint);
+
 #endif							/* TABLECMDS_H */
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 053e472..b9d843b 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3280,7 +3280,7 @@ ALTER TABLE list_parted ATTACH PARTITION def_part DEFAULT;
 -- exists
 CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT;
-ERROR:  cannot attach a new partition to table "list_parted" having a default partition
+ERROR:  partition "fail_def_part" conflicts with existing default partition "def_part"
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
 	a int,
@@ -3294,14 +3294,14 @@ ERROR:  partition constraint is violated by some row
 -- should be ok after deleting the bad row
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
--- check partition cannot be attached if default exists
+-- check partition cannot be attached if default has some row for its values
 CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 INSERT INTO list_parted2_def VALUES (11, 'z');
 CREATE TABLE part_3 (LIKE list_parted2);
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
-ERROR:  cannot attach a new partition to table "list_parted2" having a default partition
--- should be ok after detaching a default partition
-ALTER TABLE list_parted2 DETACH PARTITION list_parted2_def;
+ERROR:  updated partition constraint for default partition would be violated by some row
+-- should be ok after deleting the bad row
+DELETE FROM list_parted2_def WHERE a = 11;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
@@ -3318,6 +3318,10 @@ ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
 ALTER TABLE part_3_4 ALTER a SET NOT NULL;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
 INFO:  partition constraint for table "part_3_4" is implied by existing constraints
+-- check if default partition scan skipped
+ALTER TABLE list_parted2_def ADD CONSTRAINT check_a CHECK (a IN (5, 6));
+CREATE TABLE part_55_66 PARTITION OF list_parted2 FOR VALUES IN (55, 66);
+INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
 -- check validation when attaching range partitions
 CREATE TABLE range_parted (
 	a int,
@@ -3345,10 +3349,17 @@ ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 2
 INFO:  partition constraint for table "part2" is implied by existing constraints
 -- Create default partition
 CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
--- New partition cannot be attached if a default partition exists
+-- 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:  cannot attach a new partition to table "range_parted" having a default partition
+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 (
@@ -3401,6 +3412,7 @@ ALTER TABLE part_7 ATTACH PARTITION part_7_a_null FOR VALUES IN ('a', null);
 INFO:  partition constraint for table "part_7_a_null" is implied by existing constraints
 ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
 INFO:  partition constraint for table "part_7" is implied by existing constraints
+INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
 -- Same example, but check this time that the constraint correctly detects
 -- violating rows
 ALTER TABLE list_parted2 DETACH PARTITION part_7;
@@ -3414,7 +3426,20 @@ SELECT tableoid::regclass, a, b FROM part_7 order by a;
 (2 rows)
 
 ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
+INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
 ERROR:  partition constraint is violated by some row
+-- check that leaf partitions of default partition are scanned when
+-- attaching a partitioned table.
+ALTER TABLE part_5 DROP CONSTRAINT check_a;
+CREATE TABLE part5_def PARTITION OF part_5 DEFAULT PARTITION BY LIST(a);
+CREATE TABLE part5_def_p1 PARTITION OF part5_def FOR VALUES IN (5);
+INSERT INTO part5_def_p1 VALUES (5, 'y');
+CREATE TABLE part5_p1 (LIKE part_5);
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+ERROR:  updated partition constraint for default partition would be violated by some row
+-- should be ok after deleting the bad row
+DELETE FROM part5_def_p1 WHERE b = 'y';
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
 -- check that the table being attached is not already a partition
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 ERROR:  "part_2" is already a partition
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 4f79612..58c755b 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -470,10 +470,7 @@ LINE 1: ...BLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) T...
 -- check default partition cannot be created more than once
 CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
 CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
-ERROR:  cannot add a new partition to table "list_parted" having a default partition
--- check that no partition can be created after default partition
-CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (3);
-ERROR:  cannot add a new partition to table "list_parted" having a default partition
+ERROR:  partition "fail_default_part" conflicts with existing default partition "part_default"
 -- specified literal can't be cast to the partition column data type
 CREATE TABLE bools (
 	a bool
@@ -565,10 +562,15 @@ CREATE TABLE list_parted2 (
 ) PARTITION BY LIST (a);
 CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
 CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
 ERROR:  partition "fail_part" would overlap partition "part_null_z"
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
 ERROR:  partition "fail_part" would overlap partition "part_ab"
+-- check default partition overlap
+INSERT INTO list_parted2 VALUES('X');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y');
+ERROR:  updated partition constraint for default partition "list_parted2_def" would be violated by some row
 CREATE TABLE range_parted2 (
 	a int
 ) PARTITION BY RANGE (a);
@@ -594,9 +596,14 @@ 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;
--- New partition cannot be created if a default partition exists
+-- 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:  cannot add a new partition to table "range_parted2" having a default partition
+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,
@@ -610,13 +617,12 @@ 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
 CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, minvalue) TO (1, maxvalue);
 ERROR:  partition "fail_part" would overlap partition "part10"
--- check default partition can be added to multi-column range partitioned table
-CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
 -- check schema propagation from parent
 CREATE TABLE parted (
 	a text,
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 48f5292..9872643 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -276,9 +276,6 @@ select tableoid::regclass, * from list_parted;
  part_default_p2    | de | 35
 (9 rows)
 
--- drop default partition, as we need to add some more partitions to test tuple
--- routing
-drop table part_default;
 -- Check tuple routing for partitioned tables
 -- fail
 insert into range_parted values ('a', 0);
@@ -352,7 +349,10 @@ select tableoid::regclass, * from list_parted;
  part_xx_yy_defpart | yy |  2
  part_null          |    |  0
  part_null          |    |  1
-(10 rows)
+ part_default_p1    | cd | 25
+ part_default_p1    | ab | 21
+ part_default_p2    | de | 35
+(13 rows)
 
 -- some more tests to exercise tuple-routing with multi-level partitioning
 create table part_gg partition of list_parted for values in ('gg') partition by range (b);
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
index fb90357..c2eeff1 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -266,6 +266,10 @@ DETAIL:  Failing row contains (null).
 execute pstmt_def_insert(1);
 ERROR:  new row for relation "list_part_def" violates partition constraint
 DETAIL:  Failing row contains (1).
+create table list_part_2 partition of list_parted for values in (2);
+execute pstmt_def_insert(2);
+ERROR:  new row for relation "list_part_def" violates partition constraint
+DETAIL:  Failing row contains (2).
 alter table list_parted detach partition list_part_null;
 -- should be ok
 execute pstmt_def_insert(null);
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index b6aa9e3..ab5d23c 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2118,13 +2118,13 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 
--- check partition cannot be attached if default exists
+-- check partition cannot be attached if default has some row for its values
 CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 INSERT INTO list_parted2_def VALUES (11, 'z');
 CREATE TABLE part_3 (LIKE list_parted2);
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
--- should be ok after detaching a default partition
-ALTER TABLE list_parted2 DETACH PARTITION list_parted2_def;
+-- should be ok after deleting the bad row
+DELETE FROM list_parted2_def WHERE a = 11;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
 
 -- adding constraints that describe the desired partition constraint
@@ -2144,6 +2144,9 @@ ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
 ALTER TABLE part_3_4 ALTER a SET NOT NULL;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
 
+-- check if default partition scan skipped
+ALTER TABLE list_parted2_def ADD CONSTRAINT check_a CHECK (a IN (5, 6));
+CREATE TABLE part_55_66 PARTITION OF list_parted2 FOR VALUES IN (55, 66);
 
 -- check validation when attaching range partitions
 CREATE TABLE range_parted (
@@ -2175,10 +2178,18 @@ ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 2
 -- Create default partition
 CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
 
--- New partition cannot be attached if a default partition exists
+-- 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 (
@@ -2239,6 +2250,18 @@ INSERT INTO part_7 (a, b) VALUES (8, null), (9, 'a');
 SELECT tableoid::regclass, a, b FROM part_7 order by a;
 ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
 
+-- check that leaf partitions of default partition are scanned when
+-- attaching a partitioned table.
+ALTER TABLE part_5 DROP CONSTRAINT check_a;
+CREATE TABLE part5_def PARTITION OF part_5 DEFAULT PARTITION BY LIST(a);
+CREATE TABLE part5_def_p1 PARTITION OF part5_def FOR VALUES IN (5);
+INSERT INTO part5_def_p1 VALUES (5, 'y');
+CREATE TABLE part5_p1 (LIKE part_5);
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+-- should be ok after deleting the bad row
+DELETE FROM part5_def_p1 WHERE b = 'y';
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+
 -- check that the table being attached is not already a partition
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 24f005e..eeab5d9 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -450,8 +450,6 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
 -- check default partition cannot be created more than once
 CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
 CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
--- check that no partition can be created after default partition
-CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (3);
 
 -- specified literal can't be cast to the partition column data type
 CREATE TABLE bools (
@@ -530,9 +528,13 @@ CREATE TABLE list_parted2 (
 ) PARTITION BY LIST (a);
 CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
 CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+-- check default partition overlap
+INSERT INTO list_parted2 VALUES('X');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y');
 
 CREATE TABLE range_parted2 (
 	a int
@@ -555,8 +557,13 @@ 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;
 
--- New partition cannot be created if a default partition exists
+-- 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 (
@@ -571,15 +578,13 @@ 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
 -- more specific ranges
 CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, minvalue) TO (1, maxvalue);
 
--- check default partition can be added to multi-column range partitioned table
-CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
-
 -- check schema propagation from parent
 
 CREATE TABLE parted (
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 7fc5688..0e68480 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -165,9 +165,6 @@ insert into list_parted values ('ab', 21);
 insert into list_parted values ('xx', 1);
 insert into list_parted values ('yy', 2);
 select tableoid::regclass, * from list_parted;
--- drop default partition, as we need to add some more partitions to test tuple
--- routing
-drop table part_default;
 
 -- Check tuple routing for partitioned tables
 
diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql
index a8e357c..cb2a551 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -167,6 +167,8 @@ prepare pstmt_def_insert (int) as insert into list_part_def values($1);
 -- should fail
 execute pstmt_def_insert(null);
 execute pstmt_def_insert(1);
+create table list_part_2 partition of list_parted for values in (2);
+execute pstmt_def_insert(2);
 alter table list_parted detach partition list_part_null;
 -- should be ok
 execute pstmt_def_insert(null);
-- 
2.7.4

#181Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Jeevan Ladhe (#180)
1 attachment(s)
Re: Adding support for Default partition in partitioning

Hi,

On Thu, Sep 7, 2017 at 5:43 PM, Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
wrote:

I will work on a fix and send a patch shortly.

Attached is the V28 patch that fixes the issue reported by Rajkumar.
The patch series is exactly same as that of V27 series[1].
The fix is in patch 0002, and macro partition_bound_has_default() is
again moved in 0002 from 0003, as the fix needed to use it.

Somehow only 3 patches are their in tar.
Please find the correct tar attached.

Regards,
Jeevan Ladhe

Attachments:

default_partition_V28.tarapplication/x-tar; name=default_partition_V28.tarDownload
0001-Fix-assumptions-that-get_qual_from_partbound-cannot-.patch0000664000175000017500000001146013154226523024272 0ustar  jeevanjeevanFrom 2657d7e82e7a82906939664676430eebe30d97ee Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Wed, 6 Sep 2017 16:22:37 +0530
Subject: [PATCH 1/5] Fix assumptions that get_qual_from_partbound() cannot
 return NIL list.

Current partitioning code assumes that there cannot be any partition
without partition constraints, but in future this assumption might
not hold true. This patch makes sure that the callers of
generate_partition_qual() can handle NIL partition constraint.
e.g. if we introduce support for default partition, then default
partition will not have any constraints in case it is the only
partition of its parent.

Jeevan Ladhe.
---
 src/backend/catalog/partition.c   | 11 ++++++++---
 src/backend/commands/tablecmds.c  | 37 +++++++++++++++++++++----------------
 src/backend/utils/adt/ruleutils.c |  2 +-
 3 files changed, 30 insertions(+), 20 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 5016263..807ceee 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -935,7 +935,8 @@ RelationGetPartitionQual(Relation rel)
  * get_partition_qual_relid
  *
  * Returns an expression tree describing the passed-in relation's partition
- * constraint.
+ * constraint. If there is no partition constraint returns NULL e.g. in case
+ * default partition is the only partition.
  */
 Expr *
 get_partition_qual_relid(Oid relid)
@@ -948,7 +949,10 @@ get_partition_qual_relid(Oid relid)
 	if (rel->rd_rel->relispartition)
 	{
 		and_args = generate_partition_qual(rel);
-		if (list_length(and_args) > 1)
+
+		if (and_args == NIL)
+			result = NULL;
+		else if (list_length(and_args) > 1)
 			result = makeBoolExpr(AND_EXPR, and_args, -1);
 		else
 			result = linitial(and_args);
@@ -1756,7 +1760,8 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 /*
  * generate_partition_qual
  *
- * Generate partition predicate from rel's partition bound expression
+ * Generate partition predicate from rel's partition bound expression. The
+ * function returns a NIL list if there is no predicate.
  *
  * Result expression tree is stored CacheMemoryContext to ensure it survives
  * as long as the relcache entry. But we should be running in a less long-lived
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index c8fc9cb..77f5761 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13833,24 +13833,29 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	partConstraint = list_concat(get_qual_from_partbound(attachrel, rel,
 														 cmd->bound),
 								 RelationGetPartitionQual(rel));
-	partConstraint = (List *) eval_const_expressions(NULL,
-													 (Node *) partConstraint);
-	partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
-	partConstraint = list_make1(make_ands_explicit(partConstraint));
 
-	/*
-	 * Adjust the generated constraint to match this partition's attribute
-	 * numbers.
-	 */
-	partConstraint = map_partition_varattnos(partConstraint, 1, attachrel,
-											 rel, &found_whole_row);
-	/* There can never be a whole-row reference here */
-	if (found_whole_row)
-		elog(ERROR, "unexpected whole-row reference found in partition key");
+	/* Skip validation if there are no constraints to validate. */
+	if (partConstraint)
+	{
+		partConstraint = (List *) eval_const_expressions(NULL,
+														 (Node *) partConstraint);
+		partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
+		partConstraint = list_make1(make_ands_explicit(partConstraint));
 
-	/* Validate partition constraints against the table being attached. */
-	ValidatePartitionConstraints(wqueue, attachrel, attachrel_children,
-								 partConstraint);
+		/*
+		 * Adjust the generated constraint to match this partition's attribute
+		 * numbers.
+		 */
+		partConstraint = map_partition_varattnos(partConstraint, 1, attachrel,
+												 rel, &found_whole_row);
+		/* There can never be a whole-row reference here */
+		if (found_whole_row)
+			elog(ERROR, "unexpected whole-row reference found in partition key");
+
+		/* Validate partition constraints against the table being attached. */
+		ValidatePartitionConstraints(wqueue, attachrel, attachrel_children,
+									 partConstraint);
+	}
 
 	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachrel));
 
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index f9ea7ed..12decb0 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -1750,7 +1750,7 @@ pg_get_partition_constraintdef(PG_FUNCTION_ARGS)
 
 	constr_expr = get_partition_qual_relid(relationId);
 
-	/* Quick exit if not a partition */
+	/* Quick exit if no partition constraint */
 	if (constr_expr == NULL)
 		PG_RETURN_NULL();
 
-- 
2.7.4

0002-Implement-default-partition-support.patch0000664000175000017500000021406713154226523021074 0ustar  jeevanjeevanFrom 684d0f965ed6c8fa56942fd4baf77293ce459405 Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Wed, 6 Sep 2017 16:34:36 +0530
Subject: [PATCH 2/5] Implement default partition support

This patch introduces default partition for a list or range
partitioned table. However, in this version of patch no other
partition can be attached to a partitioned table if it has a
default partition. To ease the retrieval of default partition
OID, this patch adds a new column 'partdefid' to catalog
pg_partitioned_table.

Jeevan Ladhe, range partitioning related changes by Beena Emerson.
---
 src/backend/catalog/heap.c                 |  38 ++-
 src/backend/catalog/partition.c            | 451 ++++++++++++++++++++++++-----
 src/backend/commands/tablecmds.c           |  69 ++++-
 src/backend/nodes/copyfuncs.c              |   1 +
 src/backend/nodes/equalfuncs.c             |   1 +
 src/backend/nodes/outfuncs.c               |   1 +
 src/backend/nodes/readfuncs.c              |   1 +
 src/backend/parser/gram.y                  |  27 +-
 src/backend/parser/parse_utilcmd.c         |  14 +
 src/backend/utils/adt/ruleutils.c          |  11 +-
 src/bin/psql/describe.c                    |  10 +-
 src/bin/psql/tab-complete.c                |   4 +-
 src/include/catalog/partition.h            |   3 +
 src/include/catalog/pg_partitioned_table.h |  13 +-
 src/include/nodes/parsenodes.h             |   1 +
 src/test/regress/expected/alter_table.out  |  24 ++
 src/test/regress/expected/create_table.out |  14 +
 src/test/regress/expected/insert.out       | 139 ++++++++-
 src/test/regress/expected/plancache.out    |  22 ++
 src/test/regress/expected/sanity_check.out |   4 +
 src/test/regress/expected/update.out       |  24 ++
 src/test/regress/sql/alter_table.sql       |  24 ++
 src/test/regress/sql/create_table.sql      |  15 +
 src/test/regress/sql/insert.sql            |  71 ++++-
 src/test/regress/sql/plancache.sql         |  19 ++
 src/test/regress/sql/update.sql            |  23 ++
 26 files changed, 921 insertions(+), 103 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 45ee9ac..2952d40 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1759,7 +1759,8 @@ heap_drop_with_catalog(Oid relid)
 {
 	Relation	rel;
 	HeapTuple	tuple;
-	Oid			parentOid = InvalidOid;
+	Oid			parentOid = InvalidOid,
+				defaultPartOid = InvalidOid;
 
 	/*
 	 * To drop a partition safely, we must grab exclusive lock on its parent,
@@ -1775,6 +1776,23 @@ heap_drop_with_catalog(Oid relid)
 	{
 		parentOid = get_partition_parent(relid);
 		LockRelationOid(parentOid, AccessExclusiveLock);
+
+		/*
+		 * The partition constraint of the default partition depends on the
+		 * partition bounds of every other partition. It is possible that
+		 * other backend might be about to execute a query on the default
+		 * partition table and the query relies on previously cached default
+		 * partition constraints, which won't be correct after removal of a
+		 * partition. We must therefore take a table lock strong enough to
+		 * prevent all queries on the default partition from proceeding until
+		 * we commit and send out a shared-cache-inval notice that will make
+		 * them update their index lists. Note that we need not lock the
+		 * default partition if the table being dropped itself is the default
+		 * partition, because it is going to be locked further.
+		 */
+		defaultPartOid = get_default_partition_oid(parentOid);
+		if (OidIsValid(defaultPartOid) && relid != defaultPartOid)
+			LockRelationOid(defaultPartOid, AccessExclusiveLock);
 	}
 
 	ReleaseSysCache(tuple);
@@ -1826,6 +1844,13 @@ heap_drop_with_catalog(Oid relid)
 		RemovePartitionKeyByRelId(relid);
 
 	/*
+	 * If the relation being dropped is the default partition itself,
+	 * invalidate its entry in pg_partitioned_table.
+	 */
+	if (relid == defaultPartOid)
+		update_default_partition_oid(parentOid, InvalidOid);
+
+	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -1885,6 +1910,16 @@ heap_drop_with_catalog(Oid relid)
 	if (OidIsValid(parentOid))
 	{
 		/*
+		 * The partition constraint for the default partition depends on the
+		 * partition bounds of every other partition, so we must invalidate
+		 * the relcache entry for that partition every time a partition is
+		 * added or removed. If the default partition itself is dropped no
+		 * need to invalidate its relcache.
+		 */
+		if (OidIsValid(defaultPartOid) && relid != defaultPartOid)
+			CacheInvalidateRelcacheByRelid(defaultPartOid);
+
+		/*
 		 * Invalidate the parent's relcache so that the partition is no longer
 		 * included in its partition descriptor.
 		 */
@@ -3138,6 +3173,7 @@ StorePartitionKey(Relation rel,
 	values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
 	values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
 	values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
+	values[Anum_pg_partitioned_table_partdefid - 1] = ObjectIdGetDatum(InvalidOid);
 	values[Anum_pg_partitioned_table_partattrs - 1] = PointerGetDatum(partattrs_vec);
 	values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
 	values[Anum_pg_partitioned_table_partcollation - 1] = PointerGetDatum(partcollation_vec);
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 807ceee..02431a9 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -27,6 +27,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_inherits_fn.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_type.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
@@ -80,9 +81,12 @@ typedef struct PartitionBoundInfoData
 								 * partitioned table) */
 	int			null_index;		/* Index of the null-accepting partition; -1
 								 * if there isn't one */
+	int			default_index;	/* Index of the default partition; -1 if there
+								 * isn't one */
 } 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
@@ -120,8 +124,10 @@ static void get_range_key_properties(PartitionKey key, int keynum,
 						 ListCell **partexprs_item,
 						 Expr **keyCol,
 						 Const **lower_val, Const **upper_val);
-static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
-static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
+static List *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 *generate_partition_qual(Relation rel);
 
 static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index,
@@ -162,6 +168,7 @@ RelationBuildPartitionDesc(Relation rel)
 	MemoryContext oldcxt;
 
 	int			ndatums = 0;
+	int			default_index = -1;
 
 	/* List partitioning specific */
 	PartitionListValue **all_values = NULL;
@@ -213,6 +220,21 @@ RelationBuildPartitionDesc(Relation rel)
 								&isnull);
 		Assert(!isnull);
 		boundspec = (Node *) stringToNode(TextDatumGetCString(datum));
+
+		/*
+		 * If this is a default partition, pg_partitioned_table must have it's
+		 * OID as value of 'partdefid' for it's parent (i.e. rel) entry.
+		 */
+		if (castNode(PartitionBoundSpec, boundspec)->is_default)
+		{
+			Oid			partdefid;
+
+			partdefid = get_default_partition_oid(RelationGetRelid(rel));
+			if (partdefid != inhrelid)
+				elog(WARNING, "unexpected partdefid %u for pg_partition_table record of relation %s",
+					 partdefid, RelationGetRelationName(rel));
+		}
+
 		boundspecs = lappend(boundspecs, boundspec);
 		partoids = lappend_oid(partoids, inhrelid);
 		ReleaseSysCache(tuple);
@@ -246,6 +268,18 @@ RelationBuildPartitionDesc(Relation rel)
 				if (spec->strategy != PARTITION_STRATEGY_LIST)
 					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 list of non-null
+				 * datums for this partition.
+				 */
+				if (spec->is_default)
+				{
+					default_index = i;
+					i++;
+					continue;
+				}
+
 				foreach(c, spec->listdatums)
 				{
 					Const	   *val = castNode(Const, lfirst(c));
@@ -325,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,
@@ -334,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);
@@ -421,6 +467,7 @@ RelationBuildPartitionDesc(Relation rel)
 		boundinfo = (PartitionBoundInfoData *)
 			palloc0(sizeof(PartitionBoundInfoData));
 		boundinfo->strategy = key->strategy;
+		boundinfo->default_index = -1;
 		boundinfo->ndatums = ndatums;
 		boundinfo->null_index = -1;
 		boundinfo->datums = (Datum **) palloc0(ndatums * sizeof(Datum *));
@@ -473,6 +520,21 @@ RelationBuildPartitionDesc(Relation rel)
 						boundinfo->null_index = mapping[null_index];
 					}
 
+					/* Assign mapped index for the default partition. */
+					if (default_index != -1)
+					{
+						/*
+						 * The default partition accepts any value not
+						 * specified in the lists of other partitions, hence
+						 * it should not get mapped index while assigning
+						 * those for non-null datums.
+						 */
+						Assert(default_index >= 0 &&
+							   mapping[default_index] == -1);
+						mapping[default_index] = next_index++;
+						boundinfo->default_index = mapping[default_index];
+					}
+
 					/* All partition must now have a valid mapping */
 					Assert(next_index == nparts);
 					break;
@@ -527,6 +589,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;
 				}
@@ -577,6 +647,9 @@ partition_bounds_equal(int partnatts, int16 *parttyplen, bool *parttypbyval,
 	if (b1->null_index != b2->null_index)
 		return false;
 
+	if (b1->default_index != b2->default_index)
+		return false;
+
 	for (i = 0; i < b1->ndatums; i++)
 	{
 		int			j;
@@ -639,6 +712,9 @@ check_new_partition_bound(char *relname, Relation parent,
 	int			with = -1;
 	bool		overlap = false;
 
+	if (spec->is_default)
+		return;
+
 	switch (key->strategy)
 	{
 		case PARTITION_STRATEGY_LIST:
@@ -860,12 +936,12 @@ get_qual_from_partbound(Relation rel, Relation parent,
 	{
 		case PARTITION_STRATEGY_LIST:
 			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
-			my_qual = get_qual_for_list(key, spec);
+			my_qual = get_qual_for_list(parent, spec);
 			break;
 
 		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:
@@ -1267,10 +1343,14 @@ make_partition_op_expr(PartitionKey key, int keynum,
  *
  * Returns an implicit-AND list of expressions to use as a list partition's
  * constraint, given the partition key and bound structures.
+ *
+ * The function returns NIL for a default partition when it's the only
+ * partition since it can not have any partition constraint.
  */
 static List *
-get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
+get_qual_for_list(Relation parent, PartitionBoundSpec *spec)
 {
+	PartitionKey key = RelationGetPartitionKey(parent);
 	List	   *result;
 	Expr	   *keyCol;
 	ArrayExpr  *arr;
@@ -1297,15 +1377,63 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 	else
 		keyCol = (Expr *) copyObject(linitial(key->partexprs));
 
-	/* Create list of Consts for the allowed values, excluding any nulls */
-	foreach(cell, spec->listdatums)
+	/*
+	 * For default list partition, collect datums for all the partitions. The
+	 * default partition constraint should check that the partition key is
+	 * equal to none of those.
+	 */
+	if (spec->is_default)
 	{
-		Const	   *val = castNode(Const, lfirst(cell));
+		int			i;
+		int			ndatums = 0;
+		PartitionDesc pdesc = RelationGetPartitionDesc(parent);
+		PartitionBoundInfo boundinfo = pdesc->boundinfo;
 
-		if (val->constisnull)
-			list_has_null = true;
-		else
-			arrelems = lappend(arrelems, copyObject(val));
+		if (boundinfo)
+		{
+			ndatums = boundinfo->ndatums;
+
+			if (partition_bound_accepts_nulls(boundinfo))
+				list_has_null = true;
+		}
+
+		/*
+		 * If default is the only partition, there need not be any partition
+		 * constraint on it.
+		 */
+		if (ndatums == 0 && !list_has_null)
+			return NIL;
+
+		for (i = 0; i < ndatums; i++)
+		{
+			Const	   *val;
+
+			/* Construct const from datum */
+			val = makeConst(key->parttypid[0],
+							key->parttypmod[0],
+							key->parttypcoll[0],
+							key->parttyplen[0],
+							*boundinfo->datums[i],
+							false,	/* isnull */
+							key->parttypbyval[0]);
+
+			arrelems = lappend(arrelems, val);
+		}
+	}
+	else
+	{
+		/*
+		 * Create list of Consts for the allowed values, excluding any nulls.
+		 */
+		foreach(cell, spec->listdatums)
+		{
+			Const	   *val = castNode(Const, lfirst(cell));
+
+			if (val->constisnull)
+				list_has_null = true;
+			else
+				arrelems = lappend(arrelems, copyObject(val));
+		}
 	}
 
 	if (arrelems)
@@ -1369,6 +1497,25 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 			result = list_make1(nulltest);
 	}
 
+	/*
+	 * In case of the default partition, the constraint is of the form
+	 * "!(result)" i.e. one of the following two forms:
+	 *
+	 * 1. NOT ((keycol IS NULL) OR (keycol = ANY (arr)))
+	 *
+	 * 2. NOT ((keycol IS NOT NULL) AND (keycol = ANY (arr)))
+	 *
+	 * Note that, in general, applying NOT to a constraint expression doesn't
+	 * necessarily invert the set of rows it accepts, because NOT (NULL) is
+	 * NULL.  However, the partition constraints we construct here never
+	 * evaluate to NULL, so applying NOT works as intended.
+	 */
+	if (spec->is_default)
+	{
+		result = list_make1(make_ands_explicit(result));
+		result = list_make1(makeBoolExpr(NOT_EXPR, result, -1));
+	}
+
 	return result;
 }
 
@@ -1425,6 +1572,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
  *
@@ -1463,11 +1657,17 @@ 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 default is the only partition, then this function returns NIL. In case of
+ * non-default default partition, we return a single-member list with the
+ * constatnt TRUE when the result is empty.
+ *
  */
 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,
@@ -1478,10 +1678,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,
@@ -1491,44 +1691,72 @@ 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 it is the recursive call for default, we skip the get_range_nulltest
+	 * to avoid accumulating the NullTest on the same keys for each partition.
+	 */
+	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
@@ -1750,9 +1978,16 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 						 ? makeBoolExpr(OR_EXPR, upper_or_arms, -1)
 						 : linitial(upper_or_arms));
 
-	/* As noted above, caller expects the list to be non-empty. */
+	/*
+	 * As noted above, for non-default, we return list with constant TRUE. If
+	 * the result is NIL during the recursive call for default, it implies
+	 * this is the only other partition which can hold every value of the key
+	 * except NULL. Hence we return the NullTest result skipped earlier.
+	 */
 	if (result == NIL)
-		result = list_make1(makeBoolConst(true, false));
+		result = for_default
+			? get_range_nulltest(key)
+			: list_make1(makeBoolConst(true, false));
 
 	return result;
 }
@@ -1924,11 +2159,12 @@ get_partition_for_tuple(PartitionDispatch *pd,
 {
 	PartitionDispatch parent;
 	Datum		values[PARTITION_MAX_KEYS];
-	bool		isnull[PARTITION_MAX_KEYS];
+	bool		isnull[PARTITION_MAX_KEYS],
+				range_partkey_has_null = false;
 	int			cur_offset,
-				cur_index;
-	int			i,
-				result;
+				cur_index = -1,
+				result,
+				i;
 	ExprContext *ecxt = GetPerTupleExprContext(estate);
 	TupleTableSlot *ecxt_scantuple_old = ecxt->ecxt_scantuple;
 
@@ -1973,30 +2209,46 @@ get_partition_for_tuple(PartitionDispatch *pd,
 
 		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;
+					/*
+					 * We cannot route tuples with NULL partition keys through
+					 * a range-partitioned table if it does not have a default
+					 * partition. In such case simply return that no partition
+					 * exists for routing null partition key.
+					 */
+					if (!partition_bound_has_default(partdesc->boundinfo))
+					{
+						*failed_at = parent;
+						*failed_slot = slot;
+						result = -1;
+						goto error_exit;
+					}
+					else
+					{
+						/*
+						 * If there is any null partition key, it would be
+						 * routed to the default partition.
+						 */
+						range_partkey_has_null = true;
+						break;
+					}
 				}
 			}
 		}
 
 		/*
-		 * A null partition key is only acceptable if null-accepting list
-		 * partition exists.
+		 * If partition strategy is LIST and this is a null partition key,
+		 * route it to the null-accepting partition. Otherwise, route by
+		 * searching the array of partition bounds.
 		 */
 		cur_index = -1;
-		if (isnull[0] && partition_bound_accepts_nulls(partdesc->boundinfo))
+		if (key->strategy == PARTITION_STRATEGY_LIST && isnull[0] &&
+			partition_bound_accepts_nulls(partdesc->boundinfo))
 			cur_index = partdesc->boundinfo->null_index;
-		else if (!isnull[0])
+		else if (!range_partkey_has_null && !isnull[0])
 		{
 			/* Else bsearch in partdesc->boundinfo */
 			bool		equal = false;
@@ -2029,11 +2281,17 @@ get_partition_for_tuple(PartitionDispatch *pd,
 		}
 
 		/*
-		 * cur_index < 0 means we failed to find a partition of this parent.
-		 * cur_index >= 0 means we either found the leaf partition, or the
-		 * next parent to find a partition of.
+		 * cur_index < 0 means we could not find a non-default partition of
+		 * this parent. cur_index >= 0 means we either found the leaf
+		 * partition, or the next parent to find a partition of.
+		 *
+		 * If we couldn't find a non-default partition check if the default
+		 * partition exists, if it does, get its index.
 		 */
 		if (cur_index < 0)
+			cur_index = partdesc->boundinfo->default_index;
+
+		if (cur_index < 0)
 		{
 			result = -1;
 			*failed_at = parent;
@@ -2085,6 +2343,8 @@ make_one_range_bound(PartitionKey key, int index, List *datums, bool lower)
 	ListCell   *lc;
 	int			i;
 
+	Assert(datums != NIL);
+
 	bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
 	bound->index = index;
 	bound->datums = (Datum *) palloc0(key->partnatts * sizeof(Datum));
@@ -2321,3 +2581,58 @@ partition_bound_bsearch(PartitionKey key, PartitionBoundInfo boundinfo,
 
 	return lo;
 }
+
+/*
+ * get_default_partition_oid
+ *
+ * If the given relation has a default partition return the OID of the default
+ * partition, otherwise return InvalidOid.
+ */
+Oid
+get_default_partition_oid(Oid parentId)
+{
+	HeapTuple	tuple;
+	Oid			defaultPartId = InvalidOid;
+
+	tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(parentId));
+
+	if (HeapTupleIsValid(tuple))
+	{
+		Form_pg_partitioned_table part_table_form;
+
+		part_table_form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+		defaultPartId = part_table_form->partdefid;
+	}
+
+	ReleaseSysCache(tuple);
+	return defaultPartId;
+}
+
+/*
+ * update_default_partition_oid
+ *
+ * Updates the pg_partition_table catalog partdefid field for the given parent
+ * with the given default partition oid.
+ */
+void
+update_default_partition_oid(Oid parentId, Oid defaultPartId)
+{
+	HeapTuple	tuple;
+	Relation	pg_partitioned_table;
+	Form_pg_partitioned_table part_table_form;
+
+	pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCacheCopy1(PARTRELID, ObjectIdGetDatum(parentId));
+
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of relation %u",
+			 parentId);
+
+	part_table_form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+	part_table_form->partdefid = defaultPartId;
+	CatalogTupleUpdate(pg_partitioned_table, &tuple->t_self, tuple);
+
+	heap_freetuple(tuple);
+	heap_close(pg_partitioned_table, RowExclusiveLock);
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 77f5761..d0a8223 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -774,7 +774,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	{
 		PartitionBoundSpec *bound;
 		ParseState *pstate;
-		Oid			parentId = linitial_oid(inheritOids);
+		Oid			parentId = linitial_oid(inheritOids),
+					defaultPartOid;
 		Relation	parent;
 
 		/* Already have strong enough lock on the parent */
@@ -790,6 +791,17 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 					 errmsg("\"%s\" is not partitioned",
 							RelationGetRelationName(parent))));
 
+		/*
+		 * A table cannot be created as a partition of a parent already having
+		 * a default partition.
+		 */
+		defaultPartOid = get_default_partition_oid(RelationGetRelid(parent));
+		if (OidIsValid(defaultPartOid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("cannot add a new partition to table \"%s\" having a default partition",
+							RelationGetRelationName(parent))));
+
 		/* Tranform the bound values */
 		pstate = make_parsestate(NULL);
 		pstate->p_sourcetext = queryString;
@@ -806,6 +818,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		/* Update the pg_class entry. */
 		StorePartitionBound(rel, parent, bound);
 
+		/* Update the default partition oid */
+		if (bound->is_default)
+			update_default_partition_oid(RelationGetRelid(parent), relationId);
+
 		heap_close(parent, NoLock);
 
 		/*
@@ -13658,6 +13674,15 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	ObjectAddress address;
 	const char *trigger_name;
 	bool		found_whole_row;
+	Oid			defaultPartOid;
+
+	/* A partition cannot be attached if there exists a default partition */
+	defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
+	if (OidIsValid(defaultPartOid))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot attach a new partition to table \"%s\" having a default partition",
+						RelationGetRelationName(rel))));
 
 	attachrel = heap_openrv(cmd->name, AccessExclusiveLock);
 
@@ -13814,6 +13839,11 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	/* OK to create inheritance.  Rest of the checks performed there */
 	CreateInheritance(attachrel, rel);
 
+	/* Update the default partition oid */
+	if (cmd->bound->is_default)
+		update_default_partition_oid(RelationGetRelid(rel),
+									 RelationGetRelid(attachrel));
+
 	/*
 	 * Check that the new partition's bound is valid and does not overlap any
 	 * of existing partitions of the parent - note that it does not return on
@@ -13882,8 +13912,25 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 				new_null[Natts_pg_class],
 				new_repl[Natts_pg_class];
 	ObjectAddress address;
+	Oid			defaultPartOid;
 
-	partRel = heap_openrv(name, AccessShareLock);
+	/*
+	 * We must lock the default partition, for the same reasons explained in
+	 * heap_drop_with_catalog().
+	 */
+	defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
+	if (OidIsValid(defaultPartOid))
+		LockRelationOid(defaultPartOid, AccessExclusiveLock);
+
+	/*
+	 * We already hold an exclusive lock on the default partition that is
+	 * going to be held until the transaction is commited, hence need to
+	 * acquire a shared lock only if the partition being detached is not the
+	 * default partition.
+	 */
+	partRel = heap_openrv(name, NoLock);
+	if (RelationGetRelid(partRel) != defaultPartOid)
+		LockRelationOid(RelationGetRelid(partRel), AccessShareLock);
 
 	/* All inheritance related checks are performed within the function */
 	RemoveInheritance(partRel, rel);
@@ -13913,6 +13960,24 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 	heap_freetuple(newtuple);
 	heap_close(classRel, RowExclusiveLock);
 
+	if (OidIsValid(defaultPartOid))
+	{
+		/*
+		 * If the detach relation is the default partition itself, invalidate
+		 * its entry in pg_partitioned_table.
+		 */
+		if (RelationGetRelid(partRel) == defaultPartOid)
+			update_default_partition_oid(RelationGetRelid(rel), InvalidOid);
+		else
+		{
+			/*
+			 * We must invalidate default partition's relcache, for the same
+			 * reasons explained in heap_drop_with_catalog().
+			 */
+			CacheInvalidateRelcacheByRelid(defaultPartOid);
+		}
+	}
+
 	/*
 	 * Invalidate the parent's relcache so that the partition is no longer
 	 * included in its partition descriptor.
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 9bae264..f1bed14 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4450,6 +4450,7 @@ _copyPartitionBoundSpec(const PartitionBoundSpec *from)
 	PartitionBoundSpec *newnode = makeNode(PartitionBoundSpec);
 
 	COPY_SCALAR_FIELD(strategy);
+	COPY_SCALAR_FIELD(is_default);
 	COPY_NODE_FIELD(listdatums);
 	COPY_NODE_FIELD(lowerdatums);
 	COPY_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 11731da..8b56b91 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2839,6 +2839,7 @@ static bool
 _equalPartitionBoundSpec(const PartitionBoundSpec *a, const PartitionBoundSpec *b)
 {
 	COMPARE_SCALAR_FIELD(strategy);
+	COMPARE_SCALAR_FIELD(is_default);
 	COMPARE_NODE_FIELD(listdatums);
 	COMPARE_NODE_FIELD(lowerdatums);
 	COMPARE_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 9ee3e23..b83d919 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3573,6 +3573,7 @@ _outPartitionBoundSpec(StringInfo str, const PartitionBoundSpec *node)
 	WRITE_NODE_TYPE("PARTITIONBOUNDSPEC");
 
 	WRITE_CHAR_FIELD(strategy);
+	WRITE_BOOL_FIELD(is_default);
 	WRITE_NODE_FIELD(listdatums);
 	WRITE_NODE_FIELD(lowerdatums);
 	WRITE_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 67b9e19..fbf8330 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2390,6 +2390,7 @@ _readPartitionBoundSpec(void)
 	READ_LOCALS(PartitionBoundSpec);
 
 	READ_CHAR_FIELD(strategy);
+	READ_BOOL_FIELD(is_default);
 	READ_NODE_FIELD(listdatums);
 	READ_NODE_FIELD(lowerdatums);
 	READ_NODE_FIELD(upperdatums);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 5eb3981..c303818 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -575,7 +575,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>			part_strategy
 %type <partelem>	part_elem
 %type <list>		part_params
-%type <partboundspec> ForValues
+%type <partboundspec> PartitionBoundSpec
 %type <node>		partbound_datum PartitionRangeDatum
 %type <list>		partbound_datum_list range_datum_list
 
@@ -1980,7 +1980,7 @@ alter_table_cmds:
 
 partition_cmd:
 			/* ALTER TABLE <name> ATTACH PARTITION <table_name> FOR VALUES */
-			ATTACH PARTITION qualified_name ForValues
+			ATTACH PARTITION qualified_name PartitionBoundSpec
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
 					PartitionCmd *cmd = makeNode(PartitionCmd);
@@ -2635,13 +2635,14 @@ alter_identity_column_option:
 				}
 		;
 
-ForValues:
+PartitionBoundSpec:
 			/* a LIST partition */
 			FOR VALUES IN_P '(' partbound_datum_list ')'
 				{
 					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
 
 					n->strategy = PARTITION_STRATEGY_LIST;
+					n->is_default = false;
 					n->listdatums = $5;
 					n->location = @3;
 
@@ -2654,12 +2655,24 @@ ForValues:
 					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
 
 					n->strategy = PARTITION_STRATEGY_RANGE;
+					n->is_default = false;
 					n->lowerdatums = $5;
 					n->upperdatums = $9;
 					n->location = @3;
 
 					$$ = n;
 				}
+
+			/* a DEFAULT partition */
+			| DEFAULT
+				{
+					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+					n->is_default = true;
+					n->location = @1;
+
+					$$ = n;
+				}
 		;
 
 partbound_datum:
@@ -3130,7 +3143,7 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
-			OptTypedTableElementList ForValues OptPartitionSpec OptWith
+			OptTypedTableElementList PartitionBoundSpec OptPartitionSpec OptWith
 			OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
@@ -3149,7 +3162,7 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
-			qualified_name OptTypedTableElementList ForValues OptPartitionSpec
+			qualified_name OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
 			OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
@@ -4864,7 +4877,7 @@ CreateForeignTableStmt:
 					$$ = (Node *) n;
 				}
 		| CREATE FOREIGN TABLE qualified_name
-			PARTITION OF qualified_name OptTypedTableElementList ForValues
+			PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
 			SERVER name create_generic_options
 				{
 					CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
@@ -4885,7 +4898,7 @@ CreateForeignTableStmt:
 					$$ = (Node *) n;
 				}
 		| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
-			PARTITION OF qualified_name OptTypedTableElementList ForValues
+			PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
 			SERVER name create_generic_options
 				{
 					CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 2058679..e285964 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -60,6 +61,7 @@
 #include "parser/parse_utilcmd.h"
 #include "parser/parser.h"
 #include "rewrite/rewriteManip.h"
+#include "storage/lmgr.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
@@ -3307,6 +3309,18 @@ transformPartitionBound(ParseState *pstate, Relation parent,
 	/* Avoid scribbling on input */
 	result_spec = copyObject(spec);
 
+	if (spec->is_default)
+	{
+		/*
+		 * In case of the default partition, parser had no way to identify the
+		 * partition strategy. Assign the parent's strategy to the default
+		 * partition bound spec.
+		 */
+		result_spec->strategy = strategy;
+
+		return result_spec;
+	}
+
 	if (strategy == PARTITION_STRATEGY_LIST)
 	{
 		ListCell   *cell;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 12decb0..71e8b1d 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8696,10 +8696,17 @@ get_rule_expr(Node *node, deparse_context *context,
 		case T_PartitionBoundSpec:
 			{
 				PartitionBoundSpec *spec = (PartitionBoundSpec *) node;
+				char		strategy = spec->strategy;
 				ListCell   *cell;
 				char	   *sep;
 
-				switch (spec->strategy)
+				if (spec->is_default)
+				{
+					appendStringInfoString(buf, "DEFAULT");
+					break;
+				}
+
+				switch (strategy)
 				{
 					case PARTITION_STRATEGY_LIST:
 						Assert(spec->listdatums != NIL);
@@ -8731,7 +8738,7 @@ get_rule_expr(Node *node, deparse_context *context,
 
 					default:
 						elog(ERROR, "unrecognized partition strategy: %d",
-							 (int) spec->strategy);
+							 (int) strategy);
 						break;
 				}
 			}
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6fb9bdd..579dd92 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1893,7 +1893,7 @@ describeOneTableDetails(const char *schemaname,
 			parent_name = PQgetvalue(result, 0, 0);
 			partdef = PQgetvalue(result, 0, 1);
 
-			if (PQnfields(result) == 3)
+			if (PQnfields(result) == 3 && !PQgetisnull(result, 0, 2))
 				partconstraintdef = PQgetvalue(result, 0, 2);
 
 			printfPQExpBuffer(&tmpbuf, _("Partition of: %s %s"), parent_name,
@@ -1902,8 +1902,12 @@ describeOneTableDetails(const char *schemaname,
 
 			if (partconstraintdef)
 			{
-				printfPQExpBuffer(&tmpbuf, _("Partition constraint: %s"),
-								  partconstraintdef);
+				/* If there isn't any constraint, show that explicitly */
+				if (partconstraintdef[0] == '\0')
+					printfPQExpBuffer(&tmpbuf, _("No partition constraint"));
+				else
+					printfPQExpBuffer(&tmpbuf, _("Partition constraint: %s"),
+									  partconstraintdef);
 				printTableAddFooter(&cont, tmpbuf.data);
 			}
 
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 2ab8809..a09c49d 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2053,7 +2053,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("ATTACH", "PARTITION", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 	else if (TailMatches2("FOR", "VALUES"))
 		COMPLETE_WITH_LIST2("FROM (", "IN (");
 
@@ -2492,7 +2492,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("PARTITION", "OF", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 
 /* CREATE TABLESPACE */
 	else if (Matches3("CREATE", "TABLESPACE", MatchAny))
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 2283c67..1dd70f2 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -99,4 +99,7 @@ extern int get_partition_for_tuple(PartitionDispatch *pd,
 						EState *estate,
 						PartitionDispatchData **failed_at,
 						TupleTableSlot **failed_slot);
+extern Oid	get_default_partition_oid(Oid parentId);
+extern void update_default_partition_oid(Oid parentId, Oid defaultPartId);
+
 #endif							/* PARTITION_H */
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
index 38d64d6..525e541 100644
--- a/src/include/catalog/pg_partitioned_table.h
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -32,6 +32,8 @@ CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
 	Oid			partrelid;		/* partitioned table oid */
 	char		partstrat;		/* partitioning strategy */
 	int16		partnatts;		/* number of partition key columns */
+	Oid			partdefid;		/* default partition oid; InvalidOid if there
+								 * isn't one */
 
 	/*
 	 * variable-length fields start here, but we allow direct access to
@@ -62,13 +64,14 @@ typedef FormData_pg_partitioned_table *Form_pg_partitioned_table;
  *		compiler constants for pg_partitioned_table
  * ----------------
  */
-#define Natts_pg_partitioned_table				7
+#define Natts_pg_partitioned_table				8
 #define Anum_pg_partitioned_table_partrelid		1
 #define Anum_pg_partitioned_table_partstrat		2
 #define Anum_pg_partitioned_table_partnatts		3
-#define Anum_pg_partitioned_table_partattrs		4
-#define Anum_pg_partitioned_table_partclass		5
-#define Anum_pg_partitioned_table_partcollation 6
-#define Anum_pg_partitioned_table_partexprs		7
+#define Anum_pg_partitioned_table_partdefid		4
+#define Anum_pg_partitioned_table_partattrs		5
+#define Anum_pg_partitioned_table_partclass		6
+#define Anum_pg_partitioned_table_partcollation 7
+#define Anum_pg_partitioned_table_partexprs		8
 
 #endif							/* PG_PARTITIONED_TABLE_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3171815..f3e4c69 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -797,6 +797,7 @@ typedef struct PartitionBoundSpec
 	NodeTag		type;
 
 	char		strategy;		/* see PARTITION_STRATEGY codes above */
+	bool		is_default;		/* is it a default partition bound? */
 
 	/* Partitioning info for LIST strategy: */
 	List	   *listdatums;		/* List of Consts (or A_Consts in raw tree) */
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 0f36423..c82c533 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3297,6 +3297,14 @@ SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::reg
 CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
 ERROR:  partition "fail_part" would overlap partition "part_1"
+-- check that an existing table can be attached as a default partition
+CREATE TABLE def_part (LIKE list_parted INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION def_part DEFAULT;
+-- check attaching default partition fails if a default partition already
+-- exists
+CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT;
+ERROR:  cannot attach a new partition to table "list_parted" having a default partition
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
 	a int,
@@ -3310,6 +3318,15 @@ ERROR:  partition constraint is violated by some row
 -- should be ok after deleting the bad row
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+-- check partition cannot be attached if default exists
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
+INSERT INTO list_parted2_def VALUES (11, 'z');
+CREATE TABLE part_3 (LIKE list_parted2);
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+ERROR:  cannot attach a new partition to table "list_parted2" having a default partition
+-- should be ok after detaching a default partition
+ALTER TABLE list_parted2 DETACH PARTITION list_parted2_def;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
 CREATE TABLE part_3_4 (
@@ -3350,6 +3367,12 @@ 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;
+-- New partition cannot be attached if a default partition exists
+CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS);
+ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT;
+ERROR:  cannot attach a new partition to table "range_parted" having a default partition
 -- check that leaf partitions are scanned when attaching a partitioned
 -- table
 CREATE TABLE part_5 (
@@ -3538,6 +3561,7 @@ ALTER TABLE list_parted2 ALTER COLUMN b TYPE text;
 ERROR:  cannot alter type of column named in partition key
 -- cleanup
 DROP TABLE list_parted, list_parted2, range_parted;
+DROP TABLE fail_def_part;
 -- more tests for certain multi-level partitioning scenarios
 create table p (a int, b int) partition by range (a, b);
 create table p1 (b int, a int not null) partition by range (b);
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index babda89..4f79612 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -467,6 +467,13 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
 ERROR:  invalid bound specification for a list partition
 LINE 1: ...BLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) T...
                                                              ^
+-- check default partition cannot be created more than once
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
+ERROR:  cannot add a new partition to table "list_parted" having a default partition
+-- check that no partition can be created after default partition
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (3);
+ERROR:  cannot add a new partition to table "list_parted" having a default partition
 -- specified literal can't be cast to the partition column data type
 CREATE TABLE bools (
 	a bool
@@ -585,6 +592,11 @@ 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;
+-- New partition cannot be created if a default partition exists
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90);
+ERROR:  cannot add a new partition to table "range_parted2" having a default partition
 -- now check for multi-column range partition key
 CREATE TABLE range_parted3 (
 	a int,
@@ -603,6 +615,8 @@ ERROR:  partition "fail_part" would overlap partition "part12"
 -- more specific ranges
 CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, minvalue) TO (1, maxvalue);
 ERROR:  partition "fail_part" would overlap partition "part10"
+-- check default partition can be added to multi-column range partitioned table
+CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
 -- check schema propagation from parent
 CREATE TABLE parted (
 	a text,
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index e159d62..48f5292 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -219,17 +219,66 @@ insert into part_null values (null, 0);
 create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b);
 create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
 create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
+-- test default partition
+create table part_default partition of list_parted default;
+-- Negative test: a row, which would fit in other partition, does not fit
+-- default partition, even when inserted directly
+insert into part_default values ('aa', 2);
+ERROR:  new row for relation "part_default" violates partition constraint
+DETAIL:  Failing row contains (aa, 2).
+insert into part_default values (null, 2);
+ERROR:  new row for relation "part_default" violates partition constraint
+DETAIL:  Failing row contains (null, 2).
+-- ok
+insert into part_default values ('Zz', 2);
+-- test if default partition works as expected for multi-level partitioned
+-- table as well as when default partition itself is further partitioned
+drop table part_default;
+create table part_xx_yy partition of list_parted for values in ('xx', 'yy') partition by list (a);
+create table part_xx_yy_p1 partition of part_xx_yy for values in ('xx');
+create table part_xx_yy_defpart partition of part_xx_yy default;
+create table part_default partition of list_parted default partition by range(b);
+create table part_default_p1 partition of part_default for values from (20) to (30);
+create table part_default_p2 partition of part_default for values from (30) to (40);
 -- fail
 insert into part_ee_ff1 values ('EE', 11);
 ERROR:  new row for relation "part_ee_ff1" violates partition constraint
 DETAIL:  Failing row contains (EE, 11).
+insert into part_default_p2 values ('gg', 43);
+ERROR:  new row for relation "part_default_p2" violates partition constraint
+DETAIL:  Failing row contains (gg, 43).
 -- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
 insert into part_ee_ff1 values ('cc', 1);
 ERROR:  new row for relation "part_ee_ff1" violates partition constraint
 DETAIL:  Failing row contains (cc, 1).
+insert into part_default values ('gg', 43);
+ERROR:  no partition of relation "part_default" found for row
+DETAIL:  Partition key of the failing row contains (b) = (43).
 -- ok
 insert into part_ee_ff1 values ('ff', 1);
 insert into part_ee_ff2 values ('ff', 11);
+insert into part_default_p1 values ('cd', 25);
+insert into part_default_p2 values ('de', 35);
+insert into list_parted values ('ab', 21);
+insert into list_parted values ('xx', 1);
+insert into list_parted values ('yy', 2);
+select tableoid::regclass, * from list_parted;
+      tableoid      | a  | b  
+--------------------+----+----
+ part_cc_dd         | cC |  1
+ part_ee_ff1        | ff |  1
+ part_ee_ff2        | ff | 11
+ part_xx_yy_p1      | xx |  1
+ part_xx_yy_defpart | yy |  2
+ part_null          |    |  0
+ part_default_p1    | cd | 25
+ part_default_p1    | ab | 21
+ part_default_p2    | de | 35
+(9 rows)
+
+-- drop default partition, as we need to add some more partitions to test tuple
+-- routing
+drop table part_default;
 -- Check tuple routing for partitioned tables
 -- fail
 insert into range_parted values ('a', 0);
@@ -249,6 +298,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  
 ----------+---+----
@@ -258,7 +319,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);
@@ -274,17 +340,19 @@ DETAIL:  Partition key of the failing row contains (b) = (0).
 insert into list_parted values ('EE', 1);
 insert into part_ee_ff values ('EE', 10);
 select tableoid::regclass, * from list_parted;
-  tableoid   | a  | b  
--------------+----+----
- part_aa_bb  | aA |   
- part_cc_dd  | cC |  1
- part_ee_ff1 | ff |  1
- part_ee_ff1 | EE |  1
- part_ee_ff2 | ff | 11
- part_ee_ff2 | EE | 10
- part_null   |    |  0
- part_null   |    |  1
-(8 rows)
+      tableoid      | a  | b  
+--------------------+----+----
+ part_aa_bb         | aA |   
+ part_cc_dd         | cC |  1
+ part_ee_ff1        | ff |  1
+ part_ee_ff1        | EE |  1
+ part_ee_ff2        | ff | 11
+ part_ee_ff2        | EE | 10
+ part_xx_yy_p1      | xx |  1
+ part_xx_yy_defpart | yy |  2
+ part_null          |    |  0
+ part_null          |    |  1
+(10 rows)
 
 -- some more tests to exercise tuple-routing with multi-level partitioning
 create table part_gg partition of list_parted for values in ('gg') partition by range (b);
@@ -316,6 +384,23 @@ select tableoid::regclass::text, a, min(b) as min_b, max(b) as max_b from list_p
 
 -- cleanup
 drop table range_parted, list_parted;
+-- test that a default partition added as the first partition accepts any value
+-- including null
+create table list_parted (a int) partition by list (a);
+create table part_default partition of list_parted default;
+insert into part_default values (null);
+insert into part_default values (1);
+insert into part_default values (-1);
+select tableoid::regclass, a from list_parted;
+   tableoid   | a  
+--------------+----
+ part_default |   
+ part_default |  1
+ part_default | -1
+(3 rows)
+
+-- cleanup
+drop table list_parted;
 -- more tests for certain multi-level partitioning scenarios
 create table mlparted (a int, b int) partition by range (a, b);
 create table mlparted1 (b int not null, a int not null) partition by range ((b+0));
@@ -425,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/plancache.out b/src/test/regress/expected/plancache.out
index 3f3db33..fb90357 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -252,3 +252,25 @@ NOTICE:  3
  
 (1 row)
 
+-- Check that addition or removal of any partition is correctly dealt with by
+-- default partition table when it is being used in prepared statement.
+create table list_parted (a int) partition by list(a);
+create table list_part_null partition of list_parted for values in (null);
+create table list_part_1 partition of list_parted for values in (1);
+create table list_part_def partition of list_parted default;
+prepare pstmt_def_insert (int) as insert into list_part_def values($1);
+-- should fail
+execute pstmt_def_insert(null);
+ERROR:  new row for relation "list_part_def" violates partition constraint
+DETAIL:  Failing row contains (null).
+execute pstmt_def_insert(1);
+ERROR:  new row for relation "list_part_def" violates partition constraint
+DETAIL:  Failing row contains (1).
+alter table list_parted detach partition list_part_null;
+-- should be ok
+execute pstmt_def_insert(null);
+drop table list_part_1;
+-- should be ok
+execute pstmt_def_insert(1);
+drop table list_parted, list_part_null;
+deallocate pstmt_def_insert;
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 9366f04..cfb6aa8 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -218,5 +218,29 @@ 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
+) partition by list (a);
+create table list_part1  partition of list_parted for values in ('a', 'b');
+create table list_default partition of list_parted default;
+insert into list_part1 values ('a', 1);
+insert into list_default values ('d', 10);
+-- fail
+update list_default set a = 'a' where a = 'd';
+ERROR:  new row for relation "list_default" violates partition constraint
+DETAIL:  Failing row contains (a, 10).
+-- ok
+update list_default set a = 'x' where a = 'd';
 -- cleanup
 drop table range_parted;
+drop table list_parted;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index e6f6669..ac6f9d3 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2111,6 +2111,13 @@ SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::reg
 -- check that the new partition won't overlap with an existing partition
 CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+-- check that an existing table can be attached as a default partition
+CREATE TABLE def_part (LIKE list_parted INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION def_part DEFAULT;
+-- check attaching default partition fails if a default partition already
+-- exists
+CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT;
 
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
@@ -2127,6 +2134,15 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 
+-- check partition cannot be attached if default exists
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
+INSERT INTO list_parted2_def VALUES (11, 'z');
+CREATE TABLE part_3 (LIKE list_parted2);
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+-- should be ok after detaching a default partition
+ALTER TABLE list_parted2 DETACH PARTITION list_parted2_def;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
 CREATE TABLE part_3_4 (
@@ -2172,6 +2188,13 @@ 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;
+
+-- New partition cannot be attached if a default partition exists
+CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS);
+ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT;
+
 -- check that leaf partitions are scanned when attaching a partitioned
 -- table
 CREATE TABLE part_5 (
@@ -2327,6 +2350,7 @@ ALTER TABLE list_parted2 ALTER COLUMN b TYPE text;
 
 -- cleanup
 DROP TABLE list_parted, list_parted2, range_parted;
+DROP TABLE fail_def_part;
 
 -- more tests for certain multi-level partitioning scenarios
 create table p (a int, b int) partition by range (a, b);
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 1c0ce927..24f005e 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -447,6 +447,12 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
 -- trying to specify range for list partitioned table
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
 
+-- check default partition cannot be created more than once
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
+-- check that no partition can be created after default partition
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (3);
+
 -- specified literal can't be cast to the partition column data type
 CREATE TABLE bools (
 	a bool
@@ -546,6 +552,12 @@ 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;
+
+-- New partition cannot be created if a default partition exists
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90);
+
 -- now check for multi-column range partition key
 CREATE TABLE range_parted3 (
 	a int,
@@ -565,6 +577,9 @@ CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1,
 -- more specific ranges
 CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, minvalue) TO (1, maxvalue);
 
+-- check default partition can be added to multi-column range partitioned table
+CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
+
 -- check schema propagation from parent
 
 CREATE TABLE parted (
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 6f17872..7fc5688 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -132,13 +132,42 @@ create table part_ee_ff partition of list_parted for values in ('ee', 'ff') part
 create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
 create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
 
+-- test default partition
+create table part_default partition of list_parted default;
+-- Negative test: a row, which would fit in other partition, does not fit
+-- default partition, even when inserted directly
+insert into part_default values ('aa', 2);
+insert into part_default values (null, 2);
+-- ok
+insert into part_default values ('Zz', 2);
+-- test if default partition works as expected for multi-level partitioned
+-- table as well as when default partition itself is further partitioned
+drop table part_default;
+create table part_xx_yy partition of list_parted for values in ('xx', 'yy') partition by list (a);
+create table part_xx_yy_p1 partition of part_xx_yy for values in ('xx');
+create table part_xx_yy_defpart partition of part_xx_yy default;
+create table part_default partition of list_parted default partition by range(b);
+create table part_default_p1 partition of part_default for values from (20) to (30);
+create table part_default_p2 partition of part_default for values from (30) to (40);
+
 -- fail
 insert into part_ee_ff1 values ('EE', 11);
+insert into part_default_p2 values ('gg', 43);
 -- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
 insert into part_ee_ff1 values ('cc', 1);
+insert into part_default values ('gg', 43);
 -- ok
 insert into part_ee_ff1 values ('ff', 1);
 insert into part_ee_ff2 values ('ff', 11);
+insert into part_default_p1 values ('cd', 25);
+insert into part_default_p2 values ('de', 35);
+insert into list_parted values ('ab', 21);
+insert into list_parted values ('xx', 1);
+insert into list_parted values ('yy', 2);
+select tableoid::regclass, * from list_parted;
+-- drop default partition, as we need to add some more partitions to test tuple
+-- routing
+drop table part_default;
 
 -- Check tuple routing for partitioned tables
 
@@ -154,8 +183,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');
@@ -188,6 +228,17 @@ select tableoid::regclass::text, a, min(b) as min_b, max(b) as max_b from list_p
 -- cleanup
 drop table range_parted, list_parted;
 
+-- test that a default partition added as the first partition accepts any value
+-- including null
+create table list_parted (a int) partition by list (a);
+create table part_default partition of list_parted default;
+insert into part_default values (null);
+insert into part_default values (1);
+insert into part_default values (-1);
+select tableoid::regclass, a from list_parted;
+-- cleanup
+drop table list_parted;
+
 -- more tests for certain multi-level partitioning scenarios
 create table mlparted (a int, b int) partition by range (a, b);
 create table mlparted1 (b int not null, a int not null) partition by range ((b+0));
@@ -274,6 +325,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/plancache.sql b/src/test/regress/sql/plancache.sql
index bc20861..a8e357c 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -156,3 +156,22 @@ end$$ language plpgsql;
 
 select cachebug();
 select cachebug();
+
+-- Check that addition or removal of any partition is correctly dealt with by
+-- default partition table when it is being used in prepared statement.
+create table list_parted (a int) partition by list(a);
+create table list_part_null partition of list_parted for values in (null);
+create table list_part_1 partition of list_parted for values in (1);
+create table list_part_def partition of list_parted default;
+prepare pstmt_def_insert (int) as insert into list_part_def values($1);
+-- should fail
+execute pstmt_def_insert(null);
+execute pstmt_def_insert(1);
+alter table list_parted detach partition list_part_null;
+-- should be ok
+execute pstmt_def_insert(null);
+drop table list_part_1;
+-- should be ok
+execute pstmt_def_insert(1);
+drop table list_parted, list_part_null;
+deallocate pstmt_def_insert;
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 6637119..1ce7a94 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -125,5 +125,28 @@ 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
+) partition by list (a);
+create table list_part1  partition of list_parted for values in ('a', 'b');
+create table list_default partition of list_parted default;
+insert into list_part1 values ('a', 1);
+insert into list_default values ('d', 10);
+
+-- fail
+update list_default set a = 'a' where a = 'd';
+-- ok
+update list_default set a = 'x' where a = 'd';
+
 -- cleanup
 drop table range_parted;
+drop table list_parted;
-- 
2.7.4

0003-Default-partition-extended-for-create-and-attach.patch0000664000175000017500000012441613154226523023216 0ustar  jeevanjeevanFrom b9ff318de5243249790143d525c7439548dd8705 Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Wed, 6 Sep 2017 16:35:31 +0530
Subject: [PATCH 3/5] Default partition extended for create and attach

1. This patch extends the previous patch in this series to allow a
new partition to be created or attached even when a default
partition exists.
2. Also, extends the regression tests to test this functionality.
in this series.
3. While adding a new partition we need to make sure that default
partition does not contain any rows that would fit in newly added
partition. So, in this patch adds a code to negate the partition
constraints of new partition so as these constraints form the part
of would be default partition constraints. Then the default
partition is scanned to check if there exist a row that does not
hold these negated partition constraints, if there exists such a
row error out.
4. While doing 3, to optimize the validation scan a check is done,
if the existing constraints on the default partition imply that it
will not contain any row that would belong to the new partition,
then the validation scan is skipped.

Jeevan Ladhe, some refactoring by Ashutosh bapat.
---
 src/backend/catalog/heap.c                 |  33 ++---
 src/backend/catalog/partition.c            | 187 ++++++++++++++++++++++++++++-
 src/backend/commands/tablecmds.c           | 112 +++++++++++++----
 src/include/catalog/partition.h            |   3 +
 src/include/commands/tablecmds.h           |   4 +
 src/test/regress/expected/alter_table.out  |  39 ++++--
 src/test/regress/expected/create_table.out |  22 ++--
 src/test/regress/expected/insert.out       |   8 +-
 src/test/regress/expected/plancache.out    |   4 +
 src/test/regress/sql/alter_table.sql       |  31 ++++-
 src/test/regress/sql/create_table.sql      |  17 ++-
 src/test/regress/sql/insert.sql            |   3 -
 src/test/regress/sql/plancache.sql         |   2 +
 13 files changed, 388 insertions(+), 77 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 2952d40..ef213b6 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1778,15 +1778,8 @@ heap_drop_with_catalog(Oid relid)
 		LockRelationOid(parentOid, AccessExclusiveLock);
 
 		/*
-		 * The partition constraint of the default partition depends on the
-		 * partition bounds of every other partition. It is possible that
-		 * other backend might be about to execute a query on the default
-		 * partition table and the query relies on previously cached default
-		 * partition constraints, which won't be correct after removal of a
-		 * partition. We must therefore take a table lock strong enough to
-		 * prevent all queries on the default partition from proceeding until
-		 * we commit and send out a shared-cache-inval notice that will make
-		 * them update their index lists. Note that we need not lock the
+		 * We must also lock the default partition, for the same reasons
+		 * explained in DefineRelation(). Note that we need not lock the
 		 * default partition if the table being dropped itself is the default
 		 * partition, because it is going to be locked further.
 		 */
@@ -1910,11 +1903,9 @@ heap_drop_with_catalog(Oid relid)
 	if (OidIsValid(parentOid))
 	{
 		/*
-		 * The partition constraint for the default partition depends on the
-		 * partition bounds of every other partition, so we must invalidate
-		 * the relcache entry for that partition every time a partition is
-		 * added or removed. If the default partition itself is dropped no
-		 * need to invalidate its relcache.
+		 * We must invalidate default partition's relcache, for the same
+		 * reasons explained in StorePartitionBound(). If the default
+		 * partition itself is dropped no need to invalidate its relcache.
 		 */
 		if (OidIsValid(defaultPartOid) && relid != defaultPartOid)
 			CacheInvalidateRelcacheByRelid(defaultPartOid);
@@ -3259,7 +3250,8 @@ RemovePartitionKeyByRelId(Oid relid)
  *		relispartition to true
  *
  * Also, invalidate the parent's relcache, so that the next rebuild will load
- * the new partition's info into its partition descriptor.
+ * the new partition's info into its partition descriptor.  If there is a
+ * default partition, we must invalidate its relcache entry as well.
  */
 void
 StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
@@ -3270,6 +3262,7 @@ StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
 	Datum		new_val[Natts_pg_class];
 	bool		new_null[Natts_pg_class],
 				new_repl[Natts_pg_class];
+	Oid			defaultPartOid;
 
 	/* Update pg_class tuple */
 	classRel = heap_open(RelationRelationId, RowExclusiveLock);
@@ -3307,5 +3300,15 @@ StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
 	heap_freetuple(newtuple);
 	heap_close(classRel, RowExclusiveLock);
 
+	/*
+	 * The partition constraint for the default partition depends on the
+	 * partition bounds of every other partition, so we must invalidate the
+	 * relcache entry for that partition every time a partition is added or
+	 * removed.
+	 */
+	defaultPartOid = get_default_partition_oid(RelationGetRelid(parent));
+	if (OidIsValid(defaultPartOid))
+		CacheInvalidateRelcacheByRelid(defaultPartOid);
+
 	CacheInvalidateRelcache(parent);
 }
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 02431a9..2448a3a 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -29,6 +29,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_type.h"
+#include "commands/tablecmds.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -36,6 +37,7 @@
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planmain.h"
+#include "optimizer/prep.h"
 #include "optimizer/var.h"
 #include "rewrite/rewriteManip.h"
 #include "storage/lmgr.h"
@@ -708,12 +710,23 @@ check_new_partition_bound(char *relname, Relation parent,
 {
 	PartitionKey key = RelationGetPartitionKey(parent);
 	PartitionDesc partdesc = RelationGetPartitionDesc(parent);
+	PartitionBoundInfo boundinfo = partdesc->boundinfo;
 	ParseState *pstate = make_parsestate(NULL);
 	int			with = -1;
 	bool		overlap = false;
 
 	if (spec->is_default)
-		return;
+	{
+		if (boundinfo == NULL || !partition_bound_has_default(boundinfo))
+			return;
+
+		/* Default partition already exists, error out. */
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("partition \"%s\" conflicts with existing default partition \"%s\"",
+						relname, get_rel_name(partdesc->oids[boundinfo->default_index])),
+				 parser_errposition(pstate, spec->location)));
+	}
 
 	switch (key->strategy)
 	{
@@ -723,13 +736,13 @@ check_new_partition_bound(char *relname, Relation parent,
 
 				if (partdesc->nparts > 0)
 				{
-					PartitionBoundInfo boundinfo = partdesc->boundinfo;
 					ListCell   *cell;
 
 					Assert(boundinfo &&
 						   boundinfo->strategy == PARTITION_STRATEGY_LIST &&
 						   (boundinfo->ndatums > 0 ||
-							partition_bound_accepts_nulls(boundinfo)));
+							partition_bound_accepts_nulls(boundinfo) ||
+							partition_bound_has_default(boundinfo)));
 
 					foreach(cell, spec->listdatums)
 					{
@@ -794,8 +807,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
@@ -873,6 +888,139 @@ check_new_partition_bound(char *relname, Relation parent,
 }
 
 /*
+ * check_default_allows_bound
+ *
+ * This function checks if there exists a row in the default partition that
+ * fits in the new partition being added and throws an error if it finds one.
+ */
+void
+check_default_allows_bound(Relation parent, Relation default_rel,
+						   PartitionBoundSpec *new_spec)
+{
+	List	   *new_part_constraints;
+	List	   *def_part_constraints;
+	List	   *all_parts;
+	ListCell   *lc;
+
+	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);
+
+	/*
+	 * If the existing constraints on the default partition imply that it will
+	 * not contain any row that would belong to the new partition, we can
+	 * avoid scanning the default partition.
+	 */
+	if (PartConstraintImpliedByRelConstraint(default_rel, def_part_constraints))
+	{
+		ereport(INFO,
+				(errmsg("partition constraint for table \"%s\" is implied by existing constraints",
+						RelationGetRelationName(default_rel))));
+		return;
+	}
+
+	/*
+	 * Bad luck, scan the default partition and its subpartitions, and check
+	 * if any of the row does not satisfy the partition constraints that are
+	 * going to be imposed as additional constraints on default partition by
+	 * addition of the new partition.
+	 */
+	if (default_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		all_parts = find_all_inheritors(RelationGetRelid(default_rel),
+										AccessExclusiveLock, NULL);
+	else
+		all_parts = list_make1_oid(RelationGetRelid(default_rel));
+
+	foreach(lc, all_parts)
+	{
+		Oid			part_relid = lfirst_oid(lc);
+		Relation	part_rel;
+		Expr	   *constr;
+		Expr	   *partition_constraint;
+		EState	   *estate;
+		HeapTuple	tuple;
+		ExprState  *partqualstate = NULL;
+		Snapshot	snapshot;
+		TupleDesc	tupdesc;
+		ExprContext *econtext;
+		HeapScanDesc scan;
+		MemoryContext oldCxt;
+		TupleTableSlot *tupslot;
+
+		/* Lock already taken above. */
+		if (part_relid != RelationGetRelid(default_rel))
+			part_rel = heap_open(part_relid, NoLock);
+		else
+			part_rel = default_rel;
+
+		/*
+		 * Only RELKIND_RELATION relations (i.e. leaf partitions) need to be
+		 * scanned.
+		 */
+		if (part_rel->rd_rel->relkind != RELKIND_RELATION)
+		{
+			if (part_rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+				ereport(WARNING,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("skipped scanning foreign table \"%s\" which is a partition of default partition \"%s\"",
+								RelationGetRelationName(part_rel),
+								RelationGetRelationName(default_rel))));
+
+			if (RelationGetRelid(default_rel) != RelationGetRelid(part_rel))
+				heap_close(part_rel, NoLock);
+
+			continue;
+		}
+
+		tupdesc = CreateTupleDescCopy(RelationGetDescr(part_rel));
+		constr = linitial(def_part_constraints);
+		partition_constraint = (Expr *) map_partition_varattnos((List *) constr,
+																1, part_rel, parent, NULL);
+		estate = CreateExecutorState();
+
+		/* Build expression execution states for partition check quals */
+		partqualstate = ExecPrepareExpr(partition_constraint, estate);
+
+		econtext = GetPerTupleExprContext(estate);
+		snapshot = RegisterSnapshot(GetLatestSnapshot());
+		scan = heap_beginscan(part_rel, snapshot, 0, NULL);
+		tupslot = MakeSingleTupleTableSlot(tupdesc);
+
+		/*
+		 * Switch to per-tuple memory context and reset it for each tuple
+		 * produced, so we don't leak memory.
+		 */
+		oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		{
+			ExecStoreTuple(tuple, tupslot, InvalidBuffer, false);
+			econtext->ecxt_scantuple = tupslot;
+
+			if (!ExecCheck(partqualstate, econtext))
+				ereport(ERROR,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("updated partition constraint for default partition \"%s\" would be violated by some row",
+								RelationGetRelationName(default_rel))));
+
+			ResetExprContext(econtext);
+			CHECK_FOR_INTERRUPTS();
+		}
+
+		MemoryContextSwitchTo(oldCxt);
+		heap_endscan(scan);
+		UnregisterSnapshot(snapshot);
+		ExecDropSingleTupleTableSlot(tupslot);
+		FreeExecutorState(estate);
+
+		if (RelationGetRelid(default_rel) != RelationGetRelid(part_rel))
+			heap_close(part_rel, NoLock);	/* keep the lock until commit */
+	}
+}
+
+/*
  * get_partition_parent
  *
  * Returns inheritance parent of a partition by scanning pg_inherits
@@ -2636,3 +2784,32 @@ update_default_partition_oid(Oid parentId, Oid defaultPartId)
 	heap_freetuple(tuple);
 	heap_close(pg_partitioned_table, RowExclusiveLock);
 }
+
+/*
+ * get_default_part_validation_constraint
+ *
+ * This function returns negated constraint of new_part_constraints which
+ * would be an integral part of the default partition constraints after
+ * addition of the partition to which the new_part_constraints belongs.
+ */
+List *
+get_default_part_validation_constraint(List *new_part_constraints)
+{
+	Expr	   *defPartConstraint;
+
+	defPartConstraint = make_ands_explicit(new_part_constraints);
+
+	/*
+	 * Derieve the partition constraints of default partition by negating the
+	 * given partition constraints. The partition constraint never evaluates
+	 * to NULL, so negating it like this is safe.
+	 */
+	defPartConstraint = makeBoolExpr(NOT_EXPR,
+									 list_make1(defPartConstraint),
+									 -1);
+	defPartConstraint = (Expr *) eval_const_expressions(NULL,
+														(Node *) defPartConstraint);
+	defPartConstraint = canonicalize_qual(defPartConstraint);
+
+	return list_make1(defPartConstraint);
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d0a8223..2013ed2 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -168,6 +168,8 @@ typedef struct AlteredTableInfo
 	bool		chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
 	char		newrelpersistence;	/* if above is true */
 	Expr	   *partition_constraint;	/* for attach partition validation */
+	/* true, if is a default partition or a child of default partition */
+	bool		is_default_partition;
 	/* Objects to rebuild after completing ALTER TYPE operations */
 	List	   *changedConstraintOids;	/* OIDs of constraints to rebuild */
 	List	   *changedConstraintDefs;	/* string definitions of same */
@@ -473,11 +475,10 @@ static void CreateInheritance(Relation child_rel, Relation parent_rel);
 static void RemoveInheritance(Relation child_rel, Relation parent_rel);
 static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
 					  PartitionCmd *cmd);
-static bool PartConstraintImpliedByRelConstraint(Relation scanrel,
-									 List *partConstraint);
 static void ValidatePartitionConstraints(List **wqueue, Relation scanrel,
 							 List *scanrel_children,
-							 List *partConstraint);
+							 List *partConstraint,
+							 bool scanrel_is_default);
 static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
 
 
@@ -776,7 +777,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		ParseState *pstate;
 		Oid			parentId = linitial_oid(inheritOids),
 					defaultPartOid;
-		Relation	parent;
+		Relation	parent,
+					defaultRel;
 
 		/* Already have strong enough lock on the parent */
 		parent = heap_open(parentId, NoLock);
@@ -792,15 +794,28 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 							RelationGetRelationName(parent))));
 
 		/*
-		 * A table cannot be created as a partition of a parent already having
-		 * a default partition.
+		 * The partition constraint of the default partition depends on the
+		 * partition bounds of every other partition. It is possible that
+		 * other backend might be about to execute a query on the default
+		 * partition table and the query relies on previously cached default
+		 * partition constraints, which won't be correct after removal of a
+		 * partition. We must therefore take a table lock strong enough to
+		 * prevent all queries on the default partition from proceeding until
+		 * we commit and send out a shared-cache-inval notice that will make
+		 * them update their index lists.
+		 *
+		 * Order of locking: The relation being added won't be visible to
+		 * other backends until it is committed, hence here in
+		 * DefineRelation() the order of locking the default partition and the
+		 * relation being added does not matter. But at all other places we
+		 * need to lock the default relation before we lock the relation being
+		 * added or removed i.e. we should take the lock in same order at all
+		 * the places such that lock parent, lock default partition and then
+		 * lock the partition so as to avoid a deadlock.
 		 */
 		defaultPartOid = get_default_partition_oid(RelationGetRelid(parent));
 		if (OidIsValid(defaultPartOid))
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-					 errmsg("cannot add a new partition to table \"%s\" having a default partition",
-							RelationGetRelationName(parent))));
+			defaultRel = heap_open(defaultPartOid, AccessExclusiveLock);
 
 		/* Tranform the bound values */
 		pstate = make_parsestate(NULL);
@@ -815,6 +830,20 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		 */
 		check_new_partition_bound(relname, parent, bound);
 
+		/*
+		 * If the default partition exists, its partition constraints will
+		 * change after the addition of this new partition such that it won't
+		 * allow any row that qualifies for this new partition. So, check that
+		 * the existing data in the default partition satisfies the constraint
+		 * as it will exist after adding this partition.
+		 */
+		if (OidIsValid(defaultPartOid))
+		{
+			check_default_allows_bound(parent, defaultRel, bound);
+			/* Keep the lock until commit. */
+			heap_close(defaultRel, NoLock);
+		}
+
 		/* Update the pg_class entry. */
 		StorePartitionBound(rel, parent, bound);
 
@@ -4611,9 +4640,16 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 			}
 
 			if (partqualstate && !ExecCheck(partqualstate, econtext))
-				ereport(ERROR,
-						(errcode(ERRCODE_CHECK_VIOLATION),
-						 errmsg("partition constraint is violated by some row")));
+			{
+				if (tab->is_default_partition)
+					ereport(ERROR,
+							(errcode(ERRCODE_CHECK_VIOLATION),
+							 errmsg("updated partition constraint for default partition would be violated by some row")));
+				else
+					ereport(ERROR,
+							(errcode(ERRCODE_CHECK_VIOLATION),
+							 errmsg("partition constraint is violated by some row")));
+			}
 
 			/* Write the tuple out to the new relation */
 			if (newrel)
@@ -13498,7 +13534,7 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
  * Existing constraints includes its check constraints and column-level
  * NOT NULL constraints and partConstraint describes the partition constraint.
  */
-static bool
+bool
 PartConstraintImpliedByRelConstraint(Relation scanrel,
 									 List *partConstraint)
 {
@@ -13585,7 +13621,8 @@ PartConstraintImpliedByRelConstraint(Relation scanrel,
 static void
 ValidatePartitionConstraints(List **wqueue, Relation scanrel,
 							 List *scanrel_children,
-							 List *partConstraint)
+							 List *partConstraint,
+							 bool scanrel_is_default)
 {
 	bool		found_whole_row;
 	ListCell   *lc;
@@ -13647,6 +13684,7 @@ ValidatePartitionConstraints(List **wqueue, Relation scanrel,
 		/* Grab a work queue entry. */
 		tab = ATGetQueueEntry(wqueue, part_rel);
 		tab->partition_constraint = (Expr *) linitial(my_partconstr);
+		tab->is_default_partition = scanrel_is_default;
 
 		/* keep our lock until commit */
 		if (part_rel != scanrel)
@@ -13675,14 +13713,15 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	const char *trigger_name;
 	bool		found_whole_row;
 	Oid			defaultPartOid;
+	List	   *partBoundConstraint;
 
-	/* A partition cannot be attached if there exists a default partition */
+	/*
+	 * We must lock the default partition, for the same reasons explained in
+	 * DefineRelation().
+	 */
 	defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
 	if (OidIsValid(defaultPartOid))
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-				 errmsg("cannot attach a new partition to table \"%s\" having a default partition",
-						RelationGetRelationName(rel))));
+		LockRelationOid(defaultPartOid, AccessExclusiveLock);
 
 	attachrel = heap_openrv(cmd->name, AccessExclusiveLock);
 
@@ -13860,8 +13899,8 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	 * If the parent itself is a partition, make sure to include its
 	 * constraint as well.
 	 */
-	partConstraint = list_concat(get_qual_from_partbound(attachrel, rel,
-														 cmd->bound),
+	partBoundConstraint = get_qual_from_partbound(attachrel, rel, cmd->bound);
+	partConstraint = list_concat(partBoundConstraint,
 								 RelationGetPartitionQual(rel));
 
 	/* Skip validation if there are no constraints to validate. */
@@ -13884,7 +13923,30 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 
 		/* Validate partition constraints against the table being attached. */
 		ValidatePartitionConstraints(wqueue, attachrel, attachrel_children,
-									 partConstraint);
+									 partConstraint, false);
+
+		/*
+		 * Check whether default partition has a row that would fit the
+		 * partition being attached.
+		 */
+		defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
+		if (OidIsValid(defaultPartOid))
+		{
+			Relation	defaultrel;
+			List	   *defaultrel_children;
+			List	   *defPartConstraint;
+
+			/* We already have taken a lock on default partition. */
+			defaultrel = heap_open(defaultPartOid, NoLock);
+			defPartConstraint = get_default_part_validation_constraint(partBoundConstraint);
+			defaultrel_children = find_all_inheritors(defaultPartOid,
+													  AccessExclusiveLock, NULL);
+			ValidatePartitionConstraints(wqueue, defaultrel, defaultrel_children,
+										 defPartConstraint, true);
+
+			/* keep our lock until commit. */
+			heap_close(defaultrel, NoLock);
+		}
 	}
 
 	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachrel));
@@ -13916,7 +13978,7 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 
 	/*
 	 * We must lock the default partition, for the same reasons explained in
-	 * heap_drop_with_catalog().
+	 * DefineRelation().
 	 */
 	defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
 	if (OidIsValid(defaultPartOid))
@@ -13972,7 +14034,7 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 		{
 			/*
 			 * We must invalidate default partition's relcache, for the same
-			 * reasons explained in heap_drop_with_catalog().
+			 * reasons explained in StorePartitionBound().
 			 */
 			CacheInvalidateRelcacheByRelid(defaultPartOid);
 		}
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 1dd70f2..ee0cc15 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -101,5 +101,8 @@ extern int get_partition_for_tuple(PartitionDispatch *pd,
 						TupleTableSlot **failed_slot);
 extern Oid	get_default_partition_oid(Oid parentId);
 extern void update_default_partition_oid(Oid parentId, Oid defaultPartId);
+extern void check_default_allows_bound(Relation parent, Relation defaultRel,
+						   PartitionBoundSpec *new_spec);
+extern List *get_default_part_validation_constraint(List *new_part_constaints);
 
 #endif							/* PARTITION_H */
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index abd31b6..da3ff5d 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -18,6 +18,7 @@
 #include "catalog/dependency.h"
 #include "catalog/objectaddress.h"
 #include "nodes/parsenodes.h"
+#include "catalog/partition.h"
 #include "storage/lock.h"
 #include "utils/relcache.h"
 
@@ -87,4 +88,7 @@ extern void RangeVarCallbackOwnsTable(const RangeVar *relation,
 
 extern void RangeVarCallbackOwnsRelation(const RangeVar *relation,
 							 Oid relId, Oid oldRelId, void *noCatalogs);
+extern bool PartConstraintImpliedByRelConstraint(Relation scanrel,
+									 List *partConstraint);
+
 #endif							/* TABLECMDS_H */
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index c82c533..0d400d9 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3304,7 +3304,7 @@ ALTER TABLE list_parted ATTACH PARTITION def_part DEFAULT;
 -- exists
 CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT;
-ERROR:  cannot attach a new partition to table "list_parted" having a default partition
+ERROR:  partition "fail_def_part" conflicts with existing default partition "def_part"
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
 	a int,
@@ -3318,14 +3318,14 @@ ERROR:  partition constraint is violated by some row
 -- should be ok after deleting the bad row
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
--- check partition cannot be attached if default exists
+-- check partition cannot be attached if default has some row for its values
 CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 INSERT INTO list_parted2_def VALUES (11, 'z');
 CREATE TABLE part_3 (LIKE list_parted2);
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
-ERROR:  cannot attach a new partition to table "list_parted2" having a default partition
--- should be ok after detaching a default partition
-ALTER TABLE list_parted2 DETACH PARTITION list_parted2_def;
+ERROR:  updated partition constraint for default partition would be violated by some row
+-- should be ok after deleting the bad row
+DELETE FROM list_parted2_def WHERE a = 11;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
@@ -3342,6 +3342,10 @@ ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
 ALTER TABLE part_3_4 ALTER a SET NOT NULL;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
 INFO:  partition constraint for table "part_3_4" is implied by existing constraints
+-- check if default partition scan skipped
+ALTER TABLE list_parted2_def ADD CONSTRAINT check_a CHECK (a IN (5, 6));
+CREATE TABLE part_55_66 PARTITION OF list_parted2 FOR VALUES IN (55, 66);
+INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
 -- check validation when attaching range partitions
 CREATE TABLE range_parted (
 	a int,
@@ -3369,10 +3373,17 @@ ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 2
 INFO:  partition constraint for table "part2" is implied by existing constraints
 -- Create default partition
 CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
--- New partition cannot be attached if a default partition exists
+-- 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:  cannot attach a new partition to table "range_parted" having a default partition
+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 (
@@ -3425,6 +3436,7 @@ ALTER TABLE part_7 ATTACH PARTITION part_7_a_null FOR VALUES IN ('a', null);
 INFO:  partition constraint for table "part_7_a_null" is implied by existing constraints
 ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
 INFO:  partition constraint for table "part_7" is implied by existing constraints
+INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
 -- Same example, but check this time that the constraint correctly detects
 -- violating rows
 ALTER TABLE list_parted2 DETACH PARTITION part_7;
@@ -3438,7 +3450,20 @@ SELECT tableoid::regclass, a, b FROM part_7 order by a;
 (2 rows)
 
 ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
+INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
 ERROR:  partition constraint is violated by some row
+-- check that leaf partitions of default partition are scanned when
+-- attaching a partitioned table.
+ALTER TABLE part_5 DROP CONSTRAINT check_a;
+CREATE TABLE part5_def PARTITION OF part_5 DEFAULT PARTITION BY LIST(a);
+CREATE TABLE part5_def_p1 PARTITION OF part5_def FOR VALUES IN (5);
+INSERT INTO part5_def_p1 VALUES (5, 'y');
+CREATE TABLE part5_p1 (LIKE part_5);
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+ERROR:  updated partition constraint for default partition would be violated by some row
+-- should be ok after deleting the bad row
+DELETE FROM part5_def_p1 WHERE b = 'y';
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
 -- check that the table being attached is not already a partition
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 ERROR:  "part_2" is already a partition
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 4f79612..58c755b 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -470,10 +470,7 @@ LINE 1: ...BLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) T...
 -- check default partition cannot be created more than once
 CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
 CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
-ERROR:  cannot add a new partition to table "list_parted" having a default partition
--- check that no partition can be created after default partition
-CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (3);
-ERROR:  cannot add a new partition to table "list_parted" having a default partition
+ERROR:  partition "fail_default_part" conflicts with existing default partition "part_default"
 -- specified literal can't be cast to the partition column data type
 CREATE TABLE bools (
 	a bool
@@ -565,10 +562,15 @@ CREATE TABLE list_parted2 (
 ) PARTITION BY LIST (a);
 CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
 CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
 ERROR:  partition "fail_part" would overlap partition "part_null_z"
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
 ERROR:  partition "fail_part" would overlap partition "part_ab"
+-- check default partition overlap
+INSERT INTO list_parted2 VALUES('X');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y');
+ERROR:  updated partition constraint for default partition "list_parted2_def" would be violated by some row
 CREATE TABLE range_parted2 (
 	a int
 ) PARTITION BY RANGE (a);
@@ -594,9 +596,14 @@ 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;
--- New partition cannot be created if a default partition exists
+-- 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:  cannot add a new partition to table "range_parted2" having a default partition
+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,
@@ -610,13 +617,12 @@ 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
 CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, minvalue) TO (1, maxvalue);
 ERROR:  partition "fail_part" would overlap partition "part10"
--- check default partition can be added to multi-column range partitioned table
-CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
 -- check schema propagation from parent
 CREATE TABLE parted (
 	a text,
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 48f5292..9872643 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -276,9 +276,6 @@ select tableoid::regclass, * from list_parted;
  part_default_p2    | de | 35
 (9 rows)
 
--- drop default partition, as we need to add some more partitions to test tuple
--- routing
-drop table part_default;
 -- Check tuple routing for partitioned tables
 -- fail
 insert into range_parted values ('a', 0);
@@ -352,7 +349,10 @@ select tableoid::regclass, * from list_parted;
  part_xx_yy_defpart | yy |  2
  part_null          |    |  0
  part_null          |    |  1
-(10 rows)
+ part_default_p1    | cd | 25
+ part_default_p1    | ab | 21
+ part_default_p2    | de | 35
+(13 rows)
 
 -- some more tests to exercise tuple-routing with multi-level partitioning
 create table part_gg partition of list_parted for values in ('gg') partition by range (b);
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
index fb90357..c2eeff1 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -266,6 +266,10 @@ DETAIL:  Failing row contains (null).
 execute pstmt_def_insert(1);
 ERROR:  new row for relation "list_part_def" violates partition constraint
 DETAIL:  Failing row contains (1).
+create table list_part_2 partition of list_parted for values in (2);
+execute pstmt_def_insert(2);
+ERROR:  new row for relation "list_part_def" violates partition constraint
+DETAIL:  Failing row contains (2).
 alter table list_parted detach partition list_part_null;
 -- should be ok
 execute pstmt_def_insert(null);
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index ac6f9d3..37cca72 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2134,13 +2134,13 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 
--- check partition cannot be attached if default exists
+-- check partition cannot be attached if default has some row for its values
 CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 INSERT INTO list_parted2_def VALUES (11, 'z');
 CREATE TABLE part_3 (LIKE list_parted2);
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
--- should be ok after detaching a default partition
-ALTER TABLE list_parted2 DETACH PARTITION list_parted2_def;
+-- should be ok after deleting the bad row
+DELETE FROM list_parted2_def WHERE a = 11;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
 
 -- adding constraints that describe the desired partition constraint
@@ -2160,6 +2160,9 @@ ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
 ALTER TABLE part_3_4 ALTER a SET NOT NULL;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
 
+-- check if default partition scan skipped
+ALTER TABLE list_parted2_def ADD CONSTRAINT check_a CHECK (a IN (5, 6));
+CREATE TABLE part_55_66 PARTITION OF list_parted2 FOR VALUES IN (55, 66);
 
 -- check validation when attaching range partitions
 CREATE TABLE range_parted (
@@ -2191,10 +2194,18 @@ ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 2
 -- Create default partition
 CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
 
--- New partition cannot be attached if a default partition exists
+-- 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 (
@@ -2255,6 +2266,18 @@ INSERT INTO part_7 (a, b) VALUES (8, null), (9, 'a');
 SELECT tableoid::regclass, a, b FROM part_7 order by a;
 ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
 
+-- check that leaf partitions of default partition are scanned when
+-- attaching a partitioned table.
+ALTER TABLE part_5 DROP CONSTRAINT check_a;
+CREATE TABLE part5_def PARTITION OF part_5 DEFAULT PARTITION BY LIST(a);
+CREATE TABLE part5_def_p1 PARTITION OF part5_def FOR VALUES IN (5);
+INSERT INTO part5_def_p1 VALUES (5, 'y');
+CREATE TABLE part5_p1 (LIKE part_5);
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+-- should be ok after deleting the bad row
+DELETE FROM part5_def_p1 WHERE b = 'y';
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+
 -- check that the table being attached is not already a partition
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 24f005e..eeab5d9 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -450,8 +450,6 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
 -- check default partition cannot be created more than once
 CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
 CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
--- check that no partition can be created after default partition
-CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (3);
 
 -- specified literal can't be cast to the partition column data type
 CREATE TABLE bools (
@@ -530,9 +528,13 @@ CREATE TABLE list_parted2 (
 ) PARTITION BY LIST (a);
 CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
 CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+-- check default partition overlap
+INSERT INTO list_parted2 VALUES('X');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y');
 
 CREATE TABLE range_parted2 (
 	a int
@@ -555,8 +557,13 @@ 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;
 
--- New partition cannot be created if a default partition exists
+-- 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 (
@@ -571,15 +578,13 @@ 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
 -- more specific ranges
 CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, minvalue) TO (1, maxvalue);
 
--- check default partition can be added to multi-column range partitioned table
-CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
-
 -- check schema propagation from parent
 
 CREATE TABLE parted (
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 7fc5688..0e68480 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -165,9 +165,6 @@ insert into list_parted values ('ab', 21);
 insert into list_parted values ('xx', 1);
 insert into list_parted values ('yy', 2);
 select tableoid::regclass, * from list_parted;
--- drop default partition, as we need to add some more partitions to test tuple
--- routing
-drop table part_default;
 
 -- Check tuple routing for partitioned tables
 
diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql
index a8e357c..cb2a551 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -167,6 +167,8 @@ prepare pstmt_def_insert (int) as insert into list_part_def values($1);
 -- should fail
 execute pstmt_def_insert(null);
 execute pstmt_def_insert(1);
+create table list_part_2 partition of list_parted for values in (2);
+execute pstmt_def_insert(2);
 alter table list_parted detach partition list_part_null;
 -- should be ok
 execute pstmt_def_insert(null);
-- 
2.7.4

0004-Check-default-partitition-child-validation-scan-is-s.patch0000664000175000017500000001277313154226523024010 0ustar  jeevanjeevanFrom 6573be2074c43b45555bbcaad1d944ba6c6f8391 Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Wed, 6 Sep 2017 16:38:01 +0530
Subject: [PATCH 4/5] Check default partitition child validation scan is
 skippable

Add code to check_default_allows_bound() such that the default
partition children constraints are checked against new partition
constraints for implication and avoid scan of the child of which
existing constraints are implied by new default partition
constraints. Also, added testcase for the same.

Jeevan Ladhe.
---
 src/backend/catalog/partition.c           | 18 ++++++++++++++++++
 src/test/regress/expected/alter_table.out | 14 ++++++++++++--
 src/test/regress/sql/alter_table.sql      |  9 +++++++++
 3 files changed, 39 insertions(+), 2 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 2448a3a..2049328 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -951,7 +951,25 @@ check_default_allows_bound(Relation parent, Relation default_rel,
 
 		/* Lock already taken above. */
 		if (part_relid != RelationGetRelid(default_rel))
+		{
 			part_rel = heap_open(part_relid, NoLock);
+
+			/*
+			 * If the partition constraints on default partition child imply
+			 * that it will not contain any row that would belong to the new
+			 * partition, we can avoid scanning the child table.
+			 */
+			if (PartConstraintImpliedByRelConstraint(part_rel,
+													 def_part_constraints))
+			{
+				ereport(INFO,
+						(errmsg("partition constraint for table \"%s\" is implied by existing constraints",
+								RelationGetRelationName(part_rel))));
+
+				heap_close(part_rel, NoLock);
+				continue;
+			}
+		}
 		else
 			part_rel = default_rel;
 
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 0d400d9..f1abf9a 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3346,6 +3346,18 @@ INFO:  partition constraint for table "part_3_4" is implied by existing constrai
 ALTER TABLE list_parted2_def ADD CONSTRAINT check_a CHECK (a IN (5, 6));
 CREATE TABLE part_55_66 PARTITION OF list_parted2 FOR VALUES IN (55, 66);
 INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
+-- check if default partition child relation scan skipped
+DROP TABLE list_parted2_def;
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT PARTITION BY RANGE (a);
+CREATE TABLE list_parted2_def_p1 PARTITION OF list_parted2_def FOR VALUES FROM (21) TO (30);
+CREATE TABLE list_parted2_def_p2 PARTITION OF list_parted2_def FOR VALUES FROM (31) TO (40);
+ALTER TABLE list_parted2_def_p1 ADD CONSTRAINT check_a CHECK (a IN (21, 22));
+ALTER TABLE list_parted2_def_p2 ADD CONSTRAINT check_a CHECK (a IN (31, 32));
+CREATE TABLE part_9 PARTITION OF list_parted2 FOR VALUES IN (9);
+INFO:  partition constraint for table "list_parted2_def_p1" is implied by existing constraints
+INFO:  partition constraint for table "list_parted2_def_p2" is implied by existing constraints
+CREATE TABLE part_31 PARTITION OF list_parted2 FOR VALUES IN (31);
+INFO:  partition constraint for table "list_parted2_def_p1" is implied by existing constraints
 -- check validation when attaching range partitions
 CREATE TABLE range_parted (
 	a int,
@@ -3436,7 +3448,6 @@ ALTER TABLE part_7 ATTACH PARTITION part_7_a_null FOR VALUES IN ('a', null);
 INFO:  partition constraint for table "part_7_a_null" is implied by existing constraints
 ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
 INFO:  partition constraint for table "part_7" is implied by existing constraints
-INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
 -- Same example, but check this time that the constraint correctly detects
 -- violating rows
 ALTER TABLE list_parted2 DETACH PARTITION part_7;
@@ -3450,7 +3461,6 @@ SELECT tableoid::regclass, a, b FROM part_7 order by a;
 (2 rows)
 
 ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
-INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
 ERROR:  partition constraint is violated by some row
 -- check that leaf partitions of default partition are scanned when
 -- attaching a partitioned table.
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 37cca72..3ad6302 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2163,6 +2163,15 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
 -- check if default partition scan skipped
 ALTER TABLE list_parted2_def ADD CONSTRAINT check_a CHECK (a IN (5, 6));
 CREATE TABLE part_55_66 PARTITION OF list_parted2 FOR VALUES IN (55, 66);
+-- check if default partition child relation scan skipped
+DROP TABLE list_parted2_def;
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT PARTITION BY RANGE (a);
+CREATE TABLE list_parted2_def_p1 PARTITION OF list_parted2_def FOR VALUES FROM (21) TO (30);
+CREATE TABLE list_parted2_def_p2 PARTITION OF list_parted2_def FOR VALUES FROM (31) TO (40);
+ALTER TABLE list_parted2_def_p1 ADD CONSTRAINT check_a CHECK (a IN (21, 22));
+ALTER TABLE list_parted2_def_p2 ADD CONSTRAINT check_a CHECK (a IN (31, 32));
+CREATE TABLE part_9 PARTITION OF list_parted2 FOR VALUES IN (9);
+CREATE TABLE part_31 PARTITION OF list_parted2 FOR VALUES IN (31);
 
 -- check validation when attaching range partitions
 CREATE TABLE range_parted (
-- 
2.7.4

0005-Documentation-for-default-partition.patch0000664000175000017500000002245213154226523021023 0ustar  jeevanjeevanFrom 848d5779f53c004236e7e25133e4b216f47ea5e0 Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Wed, 6 Sep 2017 16:38:54 +0530
Subject: [PATCH 5/5] Documentation for default partition.

This patch adds documentation for default partition for
CREATE TABLE and ALTER TABLE commands with example.
Also, added documentation for pg_partitioned_table new
field partdefid.

Jeevan Ladhe
---
 doc/src/sgml/catalogs.sgml         | 11 +++++++++++
 doc/src/sgml/ref/alter_table.sgml  | 35 ++++++++++++++++++++++++++++++++---
 doc/src/sgml/ref/create_table.sgml | 32 +++++++++++++++++++++++++++++---
 3 files changed, 72 insertions(+), 6 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 4f56188..4978b47 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -4739,6 +4739,17 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</>:<replaceable>&lt;salt&gt;<
      </row>
 
      <row>
+      <entry><structfield>partdefid</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+      <entry>
+       The OID of the <structname>pg_class</> entry for the default partition
+       of this partitioned table, or zero if this partitioned table does not
+       have a default partition.
+     </entry>
+     </row>
+
+     <row>
       <entry><structfield>partattrs</structfield></entry>
       <entry><type>int2vector</type></entry>
       <entry><literal><link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.attnum</literal></entry>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index dae6307..f27d487 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -34,7 +34,7 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
 ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable> [ OWNED BY <replaceable class="PARAMETER">role_name</replaceable> [, ... ] ]
     SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable> [ NOWAIT ]
 ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
-    ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+    ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> { FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> | DEFAULT }
 ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
     DETACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable>
 
@@ -765,11 +765,18 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
    </varlistentry>
 
    <varlistentry>
-    <term><literal>ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable></literal></term>
+    <term><literal>ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> { FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> | DEFAULT }</literal></term>
     <listitem>
      <para>
       This form attaches an existing table (which might itself be partitioned)
-      as a partition of the target table using the same syntax for
+      as a partition of the target table. The table can be attached
+      as a partition for specific values using <literal>FOR VALUES
+      </literal> or as a default partition by using <literal>DEFAULT
+      </literal>.
+     </para>
+
+     <para>
+      A partition using <literal>FOR VALUES</literal> uses same syntax for
       <replaceable class="PARAMETER">partition_bound_spec</replaceable> as
       <xref linkend="sql-createtable">.  The partition bound specification
       must correspond to the partitioning strategy and partition key of the
@@ -798,6 +805,10 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       a list partition that will not accept <literal>NULL</literal> values,
       also add <literal>NOT NULL</literal> constraint to the partition key
       column, unless it's an expression.
+      Also, if there exists a default partition table for the parent table,
+      then the default partition (if it is a regular table) is scanned to
+      check that no existing row in default partition would fit in the
+      partition that is being attached.
      </para>
 
      <para>
@@ -806,6 +817,17 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       (See the discussion in <xref linkend="SQL-CREATEFOREIGNTABLE"> about
       constraints on the foreign table.)
      </para>
+
+     <para>
+      When a table has a default partition, defining a new partition changes
+      the partition constraint for the default partition. The default
+      partition can't contain any rows that would need to be moved to the new
+      partition, and will be scanned to verify that none are present. This
+      scan, like the scan of the new partition, can be avoided if an
+      appropriate <literal>CHECK</literal> constraint is present. Also like
+      the scan of the new partition, it is always skipped when the default
+      partition is a foreign table.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -1396,6 +1418,13 @@ ALTER TABLE cities
 </programlisting></para>
 
   <para>
+   Attach a default partition to a partitioned table:
+<programlisting>
+ALTER TABLE cities
+    ATTACH PARTITION cities_partdef DEFAULT;
+</programlisting></para>
+
+  <para>
    Detach a partition from partitioned table:
 <programlisting>
 ALTER TABLE measurement
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index a6ca590..84d43c6 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -49,7 +49,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
   { <replaceable class="PARAMETER">column_name</replaceable> [ WITH OPTIONS ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
     | <replaceable>table_constraint</replaceable> }
     [, ... ]
-) ] FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+) ] { FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> | DEFAULT }
 [ PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
 [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
 [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
@@ -250,11 +250,13 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
    </varlistentry>
 
    <varlistentry id="SQL-CREATETABLE-PARTITION">
-    <term><literal>PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable> FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable></literal></term>
+    <term><literal>PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable> { FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> | DEFAULT }</literal></term>
     <listitem>
      <para>
       Creates the table as a <firstterm>partition</firstterm> of the specified
-      parent table.
+      parent table. The table can be created either as a partition for specific
+      values using <literal>FOR VALUES</literal> or as a default partition
+      using <literal>DEFAULT</literal>.
      </para>
 
      <para>
@@ -343,6 +345,23 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
      </para>
 
      <para>
+     If <literal>DEFAULT</literal> is specified, the table will be
+     created as a default partition of the parent table. The parent can
+     either be a list or range partitioned table. A partition key value
+     not fitting into any other partition of the given parent will be
+     routed to the default partition. There can be only one default
+     partition for a given parent table.
+     </para>
+
+     <para>
+     If the given parent is already having a default partition then
+     adding a new partition would result in an error if the default
+     partition contains a record that would fit in the new partition
+     being added. This check is not performed if the default partition
+     is a foreign table.
+     </para>
+
+     <para>
       A partition must have the same column names and types as the partitioned
       table to which it belongs.  If the parent is specified <literal>WITH
       OIDS</literal> then all partitions must have OIDs; the parent's OID
@@ -1679,6 +1698,13 @@ CREATE TABLE cities_ab
 CREATE TABLE cities_ab_10000_to_100000
     PARTITION OF cities_ab FOR VALUES FROM (10000) TO (100000);
 </programlisting></para>
+
+  <para>
+   Create a default partition of partitioned table:
+<programlisting>
+CREATE TABLE cities_partdef
+    PARTITION OF cities DEFAULT;
+</programlisting></para>
  </refsect1>
 
  <refsect1 id="SQL-CREATETABLE-compatibility">
-- 
2.7.4

#182Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Jeevan Ladhe (#177)
Re: Adding support for Default partition in partitioning

On Wed, Sep 6, 2017 at 5:50 PM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

I am wondering whether we could avoid call to get_default_partition_oid()
in
the above block, thus avoiding a sys cache lookup. The sys cache search
shouldn't be expensive since the cache should already have that entry, but
still if we can avoid it, we save some CPU cycles. The default partition
OID is
stored in pg_partition_table catalog, which is looked up in
RelationGetPartitionKey(), a function which precedes
RelationGetPartitionDesc()
everywhere. What if that RelationGetPartitionKey() also returns the
default
partition OID and the common caller passes it to
RelationGetPartitionDesc()?.

The purpose here is to cross check the relid with partdefid stored in
catalog
pg_partitioned_table, though its going to be the same in the parents cache,
I
think its better that we retrieve it from the catalog syscache.
Further, RelationGetPartitionKey() is a macro and not a function, so
modifying
the existing simple macro for this reason does not sound a good idea to me.
Having said this I am open to ideas here.

Sorry, I meant RelationBuildPartitionKey() and
RelationBuildPartitionDesc() instead of RelationGetPartitionKey() and
RelationGetPartitionDesc() resp.

+    /* A partition cannot be attached if there exists a default partition
*/
+    defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
+    if (OidIsValid(defaultPartOid))
+        ereport(ERROR,
+                (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                 errmsg("cannot attach a new partition to table
\"%s\" having a default partition",
+                        RelationGetRelationName(rel))));
get_default_partition_oid() searches the catalogs, which is not needed
when we
have relation descriptor of the partitioned table (to which a new
partition is
being attached). You should get the default partition OID from partition
descriptor. That will be cheaper.

Something like following can be done here:
/* A partition cannot be attached if there exists a default partition */
if (partition_bound_has_default(rel->partdesc->boundinfo))
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("cannot attach a new partition to table \"%s\"
having a default partition",
RelationGetRelationName(rel))));

But, partition_bound_has_default() is defined in partition.c and not in
partition.h. This is done that way because boundinfo is not available in
partition.h. Further, this piece of code is removed in next patch where we
extend default partition support to add/attach partition even when default
partition exists. So, to me I don’t see much of the correction issue here.

If the code is being removed, I don't think we should sweat too much
about it. Sorry for the noise.

Another way to get around this is, we can define another version of
get_default_partition_oid() something like
get_default_partition_oid_from_parent_rel()
in partition.c which looks around in relcache instead of catalog and returns
the
oid of default partition, or get_default_partition_oid() accepts both parent
OID,
and parent ‘Relation’ rel, if rel is not null look into relcahce and return,
else search from catalog using OID.

I think we should define a function to return default partition OID
from partition descriptor and make it extern. Define a wrapper which
accepts Relation and returns calls this function to get default
partition OID from partition descriptor. The wrapper will be called
only on an open Relation, wherever it's available.

I haven't gone through the full patch yet, so there may be more
comments here. There is some duplication of code in
check_default_allows_bound() and ValidatePartitionConstraints() to
scan the children of partition being validated. The difference is that
the first one scans the relations in the same function and the second
adds them to work queue. May be we could use
ValidatePartitionConstraints() to scan the relation or add to the
queue based on some input flag may be wqueue argument itself. But I
haven't thought through this completely. Any thoughts?

check_default_allows_bound() is called only from DefineRelation(),
and not for alter command. I am not really sure how can we use
work queue for create command.

No, we shouldn't use work queue for CREATE command. We should extract
the common code into a function to be called from
check_default_allows_bound() and ValidatePartitionConstraints(). To
that function we pass a flag (or the work queue argument itself),
which decides whether to add a work queue item or scan the relation
directly.

--
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

#183Robert Haas
robertmhaas@gmail.com
In reply to: Jeevan Ladhe (#180)
Re: Adding support for Default partition in partitioning

On Thu, Sep 7, 2017 at 8:13 AM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

The fix would be much easier if the refactoring patch 0001 by Amul in hash
partitioning thread[2] is committed.

Done.

--
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

#184Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Ashutosh Bapat (#182)
1 attachment(s)
Re: Adding support for Default partition in partitioning

Hi,

On Thu, Sep 7, 2017 at 6:27 PM, Ashutosh Bapat <
ashutosh.bapat@enterprisedb.com> wrote:

On Wed, Sep 6, 2017 at 5:50 PM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

I am wondering whether we could avoid call to

get_default_partition_oid()

in
the above block, thus avoiding a sys cache lookup. The sys cache search
shouldn't be expensive since the cache should already have that entry,

but

still if we can avoid it, we save some CPU cycles. The default partition
OID is
stored in pg_partition_table catalog, which is looked up in
RelationGetPartitionKey(), a function which precedes
RelationGetPartitionDesc()
everywhere. What if that RelationGetPartitionKey() also returns the
default
partition OID and the common caller passes it to
RelationGetPartitionDesc()?.

The purpose here is to cross check the relid with partdefid stored in
catalog
pg_partitioned_table, though its going to be the same in the parents

cache,

I
think its better that we retrieve it from the catalog syscache.
Further, RelationGetPartitionKey() is a macro and not a function, so
modifying
the existing simple macro for this reason does not sound a good idea to

me.

Having said this I am open to ideas here.

Sorry, I meant RelationBuildPartitionKey() and
RelationBuildPartitionDesc() instead of RelationGetPartitionKey() and
RelationGetPartitionDesc() resp.

I get your concern here that we are scanning the pg_partitioned_table
syscache
twice when we are building a partition descriptor; first in
RelationBuildPartitionKey() and next in RelationBuildPartitionDesc() when we
call get_default_partition_oid().

To avoid this, I can think of following three different solutions:
1.
Introduce a default partition OID field in PartitionKey structure, and
store the
partdefid while we scan pg_partitioned_table syscache in function
RelationBuildPartitionKey(). RelationBuildPartitionDesc() can later retrieve
this field from PartitionKey.

2.
Return the default OID RelationBuildPartitionKey() , and pass that as a
parameter to
RelationBuildPartitionDesc().

3.
Introduce a out parameter OID to function RelationBuildPartitionKey() which
would store
the partdefid, and pass that as a parameter to RelationBuildPartitionDesc().

I really do not think any of the above solution is very neat and organized
or
intuitive. While I understand that the syscache would be scanned twice if we
don’t fix this, we are not building a new cache here for
pg_partitioned_table,
we are just scanning it. Moreover, if there is a heavy OLTP going on this
partitioned table we could expect that this relation cache is going to be
mostly
there, and RelationBuildPartitionDesc() won’t happen for the same table more
often.

I guess it would be worth getting others(excluding me and Ashutosh)
opinion/views
also here.

+ /* A partition cannot be attached if there exists a default

partition

*/
+    defaultPartOid = get_default_partition_oid(RelationGetRelid(rel));
+    if (OidIsValid(defaultPartOid))
+        ereport(ERROR,
+                (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                 errmsg("cannot attach a new partition to table
\"%s\" having a default partition",
+                        RelationGetRelationName(rel))));
get_default_partition_oid() searches the catalogs, which is not needed
when we
have relation descriptor of the partitioned table (to which a new
partition is
being attached). You should get the default partition OID from partition
descriptor. That will be cheaper.

Something like following can be done here:
/* A partition cannot be attached if there exists a default

partition */

if (partition_bound_has_default(rel->partdesc->boundinfo))
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("cannot attach a new partition to table \"%s\"
having a default partition",
RelationGetRelationName(rel))));

But, partition_bound_has_default() is defined in partition.c and not in
partition.h. This is done that way because boundinfo is not available in
partition.h. Further, this piece of code is removed in next patch where

we

extend default partition support to add/attach partition even when

default

partition exists. So, to me I don’t see much of the correction issue

here.

If the code is being removed, I don't think we should sweat too much
about it. Sorry for the noise.

Another way to get around this is, we can define another version of
get_default_partition_oid() something like
get_default_partition_oid_from_parent_rel()
in partition.c which looks around in relcache instead of catalog and

returns

the
oid of default partition, or get_default_partition_oid() accepts both

parent

OID,
and parent ‘Relation’ rel, if rel is not null look into relcahce and

return,

else search from catalog using OID.

I think we should define a function to return default partition OID
from partition descriptor and make it extern. Define a wrapper which
accepts Relation and returns calls this function to get default
partition OID from partition descriptor. The wrapper will be called
only on an open Relation, wherever it's available.

I have introduced a new function partdesc_get_defpart_oid() to
retrieve the default oid from the partition descriptor and used it
whereever we have relation partition desc available.
Also, I have renamed the existing function get get_default_partition_oid()
to partition_catalog_get_defpart_oid().

I haven't gone through the full patch yet, so there may be more
comments here. There is some duplication of code in
check_default_allows_bound() and ValidatePartitionConstraints() to
scan the children of partition being validated. The difference is that
the first one scans the relations in the same function and the second
adds them to work queue. May be we could use
ValidatePartitionConstraints() to scan the relation or add to the
queue based on some input flag may be wqueue argument itself. But I
haven't thought through this completely. Any thoughts?

check_default_allows_bound() is called only from DefineRelation(),
and not for alter command. I am not really sure how can we use
work queue for create command.

No, we shouldn't use work queue for CREATE command. We should extract
the common code into a function to be called from
check_default_allows_bound() and ValidatePartitionConstraints(). To
that function we pass a flag (or the work queue argument itself),
which decides whether to add a work queue item or scan the relation
directly.

I still need to look into this.

Regards,
Jeevan Ladhe

Attachments:

default_partition_V29.tarapplication/x-tar; name=default_partition_V29.tarDownload
0001-Fix-assumptions-that-get_qual_from_partbound-cannot.patch0000664000175000017500000001146013154520373024215 0ustar  jeevanjeevanFrom b31292834c828cefb22353029cc691de67965e12 Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Fri, 8 Sep 2017 12:14:46 +0530
Subject: [PATCH 1/5] Fix assumptions that get_qual_from_partbound() cannot
 return NIL list.

Current partitioning code assumes that there cannot be any partition
without partition constraints, but in future this assumption might
not hold true. This patch makes sure that the callers of
generate_partition_qual() can handle NIL partition constraint.
e.g. if we introduce support for default partition, then default
partition will not have any constraints in case it is the only
partition of its parent.

Jeevan Ladhe.
---
 src/backend/catalog/partition.c   | 11 ++++++++---
 src/backend/commands/tablecmds.c  | 37 +++++++++++++++++++++----------------
 src/backend/utils/adt/ruleutils.c |  2 +-
 3 files changed, 30 insertions(+), 20 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index c6bd02f..af45ad7 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -935,7 +935,8 @@ RelationGetPartitionQual(Relation rel)
  * get_partition_qual_relid
  *
  * Returns an expression tree describing the passed-in relation's partition
- * constraint.
+ * constraint. If there is no partition constraint returns NULL e.g. in case
+ * default partition is the only partition.
  */
 Expr *
 get_partition_qual_relid(Oid relid)
@@ -948,7 +949,10 @@ get_partition_qual_relid(Oid relid)
 	if (rel->rd_rel->relispartition)
 	{
 		and_args = generate_partition_qual(rel);
-		if (list_length(and_args) > 1)
+
+		if (and_args == NIL)
+			result = NULL;
+		else if (list_length(and_args) > 1)
 			result = makeBoolExpr(AND_EXPR, and_args, -1);
 		else
 			result = linitial(and_args);
@@ -1756,7 +1760,8 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 /*
  * generate_partition_qual
  *
- * Generate partition predicate from rel's partition bound expression
+ * Generate partition predicate from rel's partition bound expression. The
+ * function returns a NIL list if there is no predicate.
  *
  * Result expression tree is stored CacheMemoryContext to ensure it survives
  * as long as the relcache entry. But we should be running in a less long-lived
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index c8fc9cb..77f5761 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13833,24 +13833,29 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	partConstraint = list_concat(get_qual_from_partbound(attachrel, rel,
 														 cmd->bound),
 								 RelationGetPartitionQual(rel));
-	partConstraint = (List *) eval_const_expressions(NULL,
-													 (Node *) partConstraint);
-	partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
-	partConstraint = list_make1(make_ands_explicit(partConstraint));
 
-	/*
-	 * Adjust the generated constraint to match this partition's attribute
-	 * numbers.
-	 */
-	partConstraint = map_partition_varattnos(partConstraint, 1, attachrel,
-											 rel, &found_whole_row);
-	/* There can never be a whole-row reference here */
-	if (found_whole_row)
-		elog(ERROR, "unexpected whole-row reference found in partition key");
+	/* Skip validation if there are no constraints to validate. */
+	if (partConstraint)
+	{
+		partConstraint = (List *) eval_const_expressions(NULL,
+														 (Node *) partConstraint);
+		partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
+		partConstraint = list_make1(make_ands_explicit(partConstraint));
 
-	/* Validate partition constraints against the table being attached. */
-	ValidatePartitionConstraints(wqueue, attachrel, attachrel_children,
-								 partConstraint);
+		/*
+		 * Adjust the generated constraint to match this partition's attribute
+		 * numbers.
+		 */
+		partConstraint = map_partition_varattnos(partConstraint, 1, attachrel,
+												 rel, &found_whole_row);
+		/* There can never be a whole-row reference here */
+		if (found_whole_row)
+			elog(ERROR, "unexpected whole-row reference found in partition key");
+
+		/* Validate partition constraints against the table being attached. */
+		ValidatePartitionConstraints(wqueue, attachrel, attachrel_children,
+									 partConstraint);
+	}
 
 	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachrel));
 
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index f9ea7ed..12decb0 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -1750,7 +1750,7 @@ pg_get_partition_constraintdef(PG_FUNCTION_ARGS)
 
 	constr_expr = get_partition_qual_relid(relationId);
 
-	/* Quick exit if not a partition */
+	/* Quick exit if no partition constraint */
 	if (constr_expr == NULL)
 		PG_RETURN_NULL();
 
-- 
2.7.4

0002-Implement-default-partition-support.patch0000664000175000017500000021337313154520373021073 0ustar  jeevanjeevanFrom 9d5173d169420082786d169a794e58b821a675b3 Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Fri, 8 Sep 2017 12:40:10 +0530
Subject: [PATCH 2/5] Implement default partition support

This patch introduces default partition for a list or range
partitioned table. However, in this version of patch no other
partition can be attached to a partitioned table if it has a
default partition. To ease the retrieval of default partition
OID, this patch adds a new column 'partdefid' to catalog
pg_partitioned_table.

Jeevan Ladhe, range partitioning related changes by Beena Emerson.
---
 src/backend/catalog/heap.c                 |  38 ++-
 src/backend/catalog/partition.c            | 447 +++++++++++++++++++++++++----
 src/backend/commands/tablecmds.c           |  69 ++++-
 src/backend/nodes/copyfuncs.c              |   1 +
 src/backend/nodes/equalfuncs.c             |   1 +
 src/backend/nodes/outfuncs.c               |   1 +
 src/backend/nodes/readfuncs.c              |   1 +
 src/backend/parser/gram.y                  |  27 +-
 src/backend/parser/parse_utilcmd.c         |  14 +
 src/backend/utils/adt/ruleutils.c          |  11 +-
 src/bin/psql/describe.c                    |  10 +-
 src/bin/psql/tab-complete.c                |   4 +-
 src/include/catalog/partition.h            |   4 +
 src/include/catalog/pg_partitioned_table.h |  13 +-
 src/include/nodes/parsenodes.h             |   1 +
 src/test/regress/expected/alter_table.out  |  24 ++
 src/test/regress/expected/create_table.out |  14 +
 src/test/regress/expected/insert.out       | 139 ++++++++-
 src/test/regress/expected/plancache.out    |  22 ++
 src/test/regress/expected/sanity_check.out |   4 +
 src/test/regress/expected/update.out       |  24 ++
 src/test/regress/sql/alter_table.sql       |  24 ++
 src/test/regress/sql/create_table.sql      |  15 +
 src/test/regress/sql/insert.sql            |  71 ++++-
 src/test/regress/sql/plancache.sql         |  19 ++
 src/test/regress/sql/update.sql            |  23 ++
 26 files changed, 931 insertions(+), 90 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 45ee9ac..53b61f7 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1759,7 +1759,8 @@ heap_drop_with_catalog(Oid relid)
 {
 	Relation	rel;
 	HeapTuple	tuple;
-	Oid			parentOid = InvalidOid;
+	Oid			parentOid = InvalidOid,
+				defaultPartOid = InvalidOid;
 
 	/*
 	 * To drop a partition safely, we must grab exclusive lock on its parent,
@@ -1775,6 +1776,23 @@ heap_drop_with_catalog(Oid relid)
 	{
 		parentOid = get_partition_parent(relid);
 		LockRelationOid(parentOid, AccessExclusiveLock);
+
+		/*
+		 * The partition constraint of the default partition depends on the
+		 * partition bounds of every other partition. It is possible that
+		 * other backend might be about to execute a query on the default
+		 * partition table and the query relies on previously cached default
+		 * partition constraints, which won't be correct after removal of a
+		 * partition. We must therefore take a table lock strong enough to
+		 * prevent all queries on the default partition from proceeding until
+		 * we commit and send out a shared-cache-inval notice that will make
+		 * them update their index lists. Note that we need not lock the
+		 * default partition if the table being dropped itself is the default
+		 * partition, because it is going to be locked further.
+		 */
+		defaultPartOid = partition_catalog_get_defpart_oid(parentOid);
+		if (OidIsValid(defaultPartOid) && relid != defaultPartOid)
+			LockRelationOid(defaultPartOid, AccessExclusiveLock);
 	}
 
 	ReleaseSysCache(tuple);
@@ -1826,6 +1844,13 @@ heap_drop_with_catalog(Oid relid)
 		RemovePartitionKeyByRelId(relid);
 
 	/*
+	 * If the relation being dropped is the default partition itself,
+	 * invalidate its entry in pg_partitioned_table.
+	 */
+	if (relid == defaultPartOid)
+		update_default_partition_oid(parentOid, InvalidOid);
+
+	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -1885,6 +1910,16 @@ heap_drop_with_catalog(Oid relid)
 	if (OidIsValid(parentOid))
 	{
 		/*
+		 * The partition constraint for the default partition depends on the
+		 * partition bounds of every other partition, so we must invalidate
+		 * the relcache entry for that partition every time a partition is
+		 * added or removed. If the default partition itself is dropped no
+		 * need to invalidate its relcache.
+		 */
+		if (OidIsValid(defaultPartOid) && relid != defaultPartOid)
+			CacheInvalidateRelcacheByRelid(defaultPartOid);
+
+		/*
 		 * Invalidate the parent's relcache so that the partition is no longer
 		 * included in its partition descriptor.
 		 */
@@ -3138,6 +3173,7 @@ StorePartitionKey(Relation rel,
 	values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
 	values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
 	values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
+	values[Anum_pg_partitioned_table_partdefid - 1] = ObjectIdGetDatum(InvalidOid);
 	values[Anum_pg_partitioned_table_partattrs - 1] = PointerGetDatum(partattrs_vec);
 	values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
 	values[Anum_pg_partitioned_table_partcollation - 1] = PointerGetDatum(partcollation_vec);
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index af45ad7..f8cec69 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -27,6 +27,7 @@
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_inherits_fn.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_type.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
@@ -80,9 +81,12 @@ typedef struct PartitionBoundInfoData
 								 * partitioned table) */
 	int			null_index;		/* Index of the null-accepting partition; -1
 								 * if there isn't one */
+	int			default_index;	/* Index of the default partition; -1 if there
+								 * isn't one */
 } 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
@@ -120,8 +124,10 @@ static void get_range_key_properties(PartitionKey key, int keynum,
 						 ListCell **partexprs_item,
 						 Expr **keyCol,
 						 Const **lower_val, Const **upper_val);
-static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
-static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
+static List *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 *generate_partition_qual(Relation rel);
 
 static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index,
@@ -162,6 +168,7 @@ RelationBuildPartitionDesc(Relation rel)
 	MemoryContext oldcxt;
 
 	int			ndatums = 0;
+	int			default_index = -1;
 
 	/* List partitioning specific */
 	PartitionListValue **all_values = NULL;
@@ -213,6 +220,21 @@ RelationBuildPartitionDesc(Relation rel)
 								&isnull);
 		Assert(!isnull);
 		boundspec = (Node *) stringToNode(TextDatumGetCString(datum));
+
+		/*
+		 * If this is a default partition, pg_partitioned_table must have it's
+		 * OID as value of 'partdefid' for it's parent (i.e. rel) entry.
+		 */
+		if (castNode(PartitionBoundSpec, boundspec)->is_default)
+		{
+			Oid			partdefid;
+
+			partdefid = partition_catalog_get_defpart_oid(RelationGetRelid(rel));
+			if (partdefid != inhrelid)
+				elog(ERROR, "unexpected partdefid %u for pg_partition_table record of relation %s",
+					 partdefid, RelationGetRelationName(rel));
+		}
+
 		boundspecs = lappend(boundspecs, boundspec);
 		partoids = lappend_oid(partoids, inhrelid);
 		ReleaseSysCache(tuple);
@@ -246,6 +268,18 @@ RelationBuildPartitionDesc(Relation rel)
 				if (spec->strategy != PARTITION_STRATEGY_LIST)
 					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 list of non-null
+				 * datums for this partition.
+				 */
+				if (spec->is_default)
+				{
+					default_index = i;
+					i++;
+					continue;
+				}
+
 				foreach(c, spec->listdatums)
 				{
 					Const	   *val = castNode(Const, lfirst(c));
@@ -325,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,
@@ -334,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);
@@ -421,6 +467,7 @@ RelationBuildPartitionDesc(Relation rel)
 		boundinfo = (PartitionBoundInfoData *)
 			palloc0(sizeof(PartitionBoundInfoData));
 		boundinfo->strategy = key->strategy;
+		boundinfo->default_index = -1;
 		boundinfo->ndatums = ndatums;
 		boundinfo->null_index = -1;
 		boundinfo->datums = (Datum **) palloc0(ndatums * sizeof(Datum *));
@@ -473,6 +520,21 @@ RelationBuildPartitionDesc(Relation rel)
 						boundinfo->null_index = mapping[null_index];
 					}
 
+					/* Assign mapped index for the default partition. */
+					if (default_index != -1)
+					{
+						/*
+						 * The default partition accepts any value not
+						 * specified in the lists of other partitions, hence
+						 * it should not get mapped index while assigning
+						 * those for non-null datums.
+						 */
+						Assert(default_index >= 0 &&
+							   mapping[default_index] == -1);
+						mapping[default_index] = next_index++;
+						boundinfo->default_index = mapping[default_index];
+					}
+
 					/* All partition must now have a valid mapping */
 					Assert(next_index == nparts);
 					break;
@@ -527,6 +589,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;
 				}
@@ -577,6 +647,9 @@ partition_bounds_equal(int partnatts, int16 *parttyplen, bool *parttypbyval,
 	if (b1->null_index != b2->null_index)
 		return false;
 
+	if (b1->default_index != b2->default_index)
+		return false;
+
 	for (i = 0; i < b1->ndatums; i++)
 	{
 		int			j;
@@ -639,6 +712,9 @@ check_new_partition_bound(char *relname, Relation parent,
 	int			with = -1;
 	bool		overlap = false;
 
+	if (spec->is_default)
+		return;
+
 	switch (key->strategy)
 	{
 		case PARTITION_STRATEGY_LIST:
@@ -860,12 +936,12 @@ get_qual_from_partbound(Relation rel, Relation parent,
 	{
 		case PARTITION_STRATEGY_LIST:
 			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
-			my_qual = get_qual_for_list(key, spec);
+			my_qual = get_qual_for_list(parent, spec);
 			break;
 
 		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:
@@ -1267,10 +1343,14 @@ make_partition_op_expr(PartitionKey key, int keynum,
  *
  * Returns an implicit-AND list of expressions to use as a list partition's
  * constraint, given the partition key and bound structures.
+ *
+ * The function returns NIL for a default partition when it's the only
+ * partition since it can not have any partition constraint.
  */
 static List *
-get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
+get_qual_for_list(Relation parent, PartitionBoundSpec *spec)
 {
+	PartitionKey key = RelationGetPartitionKey(parent);
 	List	   *result;
 	Expr	   *keyCol;
 	ArrayExpr  *arr;
@@ -1297,15 +1377,63 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 	else
 		keyCol = (Expr *) copyObject(linitial(key->partexprs));
 
-	/* Create list of Consts for the allowed values, excluding any nulls */
-	foreach(cell, spec->listdatums)
+	/*
+	 * For default list partition, collect datums for all the partitions. The
+	 * default partition constraint should check that the partition key is
+	 * equal to none of those.
+	 */
+	if (spec->is_default)
 	{
-		Const	   *val = castNode(Const, lfirst(cell));
+		int			i;
+		int			ndatums = 0;
+		PartitionDesc pdesc = RelationGetPartitionDesc(parent);
+		PartitionBoundInfo boundinfo = pdesc->boundinfo;
 
-		if (val->constisnull)
-			list_has_null = true;
-		else
-			arrelems = lappend(arrelems, copyObject(val));
+		if (boundinfo)
+		{
+			ndatums = boundinfo->ndatums;
+
+			if (partition_bound_accepts_nulls(boundinfo))
+				list_has_null = true;
+		}
+
+		/*
+		 * If default is the only partition, there need not be any partition
+		 * constraint on it.
+		 */
+		if (ndatums == 0 && !list_has_null)
+			return NIL;
+
+		for (i = 0; i < ndatums; i++)
+		{
+			Const	   *val;
+
+			/* Construct const from datum */
+			val = makeConst(key->parttypid[0],
+							key->parttypmod[0],
+							key->parttypcoll[0],
+							key->parttyplen[0],
+							*boundinfo->datums[i],
+							false,	/* isnull */
+							key->parttypbyval[0]);
+
+			arrelems = lappend(arrelems, val);
+		}
+	}
+	else
+	{
+		/*
+		 * Create list of Consts for the allowed values, excluding any nulls.
+		 */
+		foreach(cell, spec->listdatums)
+		{
+			Const	   *val = castNode(Const, lfirst(cell));
+
+			if (val->constisnull)
+				list_has_null = true;
+			else
+				arrelems = lappend(arrelems, copyObject(val));
+		}
 	}
 
 	if (arrelems)
@@ -1369,6 +1497,25 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
 			result = list_make1(nulltest);
 	}
 
+	/*
+	 * In case of the default partition, the constraint is of the form
+	 * "!(result)" i.e. one of the following two forms:
+	 *
+	 * 1. NOT ((keycol IS NULL) OR (keycol = ANY (arr)))
+	 *
+	 * 2. NOT ((keycol IS NOT NULL) AND (keycol = ANY (arr)))
+	 *
+	 * Note that, in general, applying NOT to a constraint expression doesn't
+	 * necessarily invert the set of rows it accepts, because NOT (NULL) is
+	 * NULL.  However, the partition constraints we construct here never
+	 * evaluate to NULL, so applying NOT works as intended.
+	 */
+	if (spec->is_default)
+	{
+		result = list_make1(make_ands_explicit(result));
+		result = list_make1(makeBoolExpr(NOT_EXPR, result, -1));
+	}
+
 	return result;
 }
 
@@ -1425,6 +1572,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
  *
@@ -1463,11 +1657,17 @@ 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 default is the only partition, then this function returns NIL. In case of
+ * non-default default partition, we return a single-member list with the
+ * constatnt TRUE when the result is empty.
+ *
  */
 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,
@@ -1478,10 +1678,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,
@@ -1491,44 +1691,72 @@ 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 it is the recursive call for default, we skip the get_range_nulltest
+	 * to avoid accumulating the NullTest on the same keys for each partition.
+	 */
+	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
@@ -1750,9 +1978,16 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
 						 ? makeBoolExpr(OR_EXPR, upper_or_arms, -1)
 						 : linitial(upper_or_arms));
 
-	/* As noted above, caller expects the list to be non-empty. */
+	/*
+	 * As noted above, for non-default, we return list with constant TRUE. If
+	 * the result is NIL during the recursive call for default, it implies
+	 * this is the only other partition which can hold every value of the key
+	 * except NULL. Hence we return the NullTest result skipped earlier.
+	 */
 	if (result == NIL)
-		result = list_make1(makeBoolConst(true, false));
+		result = for_default
+			? get_range_nulltest(key)
+			: list_make1(makeBoolConst(true, false));
 
 	return result;
 }
@@ -1996,15 +2231,27 @@ get_partition_for_tuple(PartitionDispatch *pd,
 
 			case PARTITION_STRATEGY_RANGE:
 				{
-					bool		equal = false;
+					bool		equal = false,
+								range_partkey_has_null = false;
 					int			cur_offset;
 					int			i;
 
-					/* No range includes NULL. */
 					for (i = 0; i < key->partnatts; i++)
 					{
-						if (isnull[i])
+						/*
+						 * If there exists default partition and tuple has a
+						 * null partition key, it will be routed to the
+						 * default partition.
+						 */
+						if (isnull[i] &&
+							partition_bound_has_default(partdesc->boundinfo))
 						{
+							range_partkey_has_null = true;
+							break;
+						}
+						else if (isnull[i])
+						{
+							/* No other range includes NULL. */
 							*failed_at = parent;
 							*failed_slot = slot;
 							result = -1;
@@ -2012,6 +2259,13 @@ get_partition_for_tuple(PartitionDispatch *pd,
 						}
 					}
 
+					/*
+					 * No need to search for partition, as the null key will
+					 * be routed to the default partition.
+					 */
+					if (range_partkey_has_null)
+						break;
+
 					cur_offset = partition_bound_bsearch(key,
 														 partdesc->boundinfo,
 														 values,
@@ -2033,11 +2287,17 @@ get_partition_for_tuple(PartitionDispatch *pd,
 		}
 
 		/*
-		 * cur_index < 0 means we failed to find a partition of this parent.
-		 * cur_index >= 0 means we either found the leaf partition, or the
-		 * next parent to find a partition of.
+		 * cur_index < 0 means we could not find a non-default partition of
+		 * this parent. cur_index >= 0 means we either found the leaf
+		 * partition, or the next parent to find a partition of.
+		 *
+		 * If we couldn't find a non-default partition check if the default
+		 * partition exists, if it does, get its index.
 		 */
 		if (cur_index < 0)
+			cur_index = partdesc->boundinfo->default_index;
+
+		if (cur_index < 0)
 		{
 			result = -1;
 			*failed_at = parent;
@@ -2089,6 +2349,8 @@ make_one_range_bound(PartitionKey key, int index, List *datums, bool lower)
 	ListCell   *lc;
 	int			i;
 
+	Assert(datums != NIL);
+
 	bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
 	bound->index = index;
 	bound->datums = (Datum *) palloc0(key->partnatts * sizeof(Datum));
@@ -2325,3 +2587,78 @@ partition_bound_bsearch(PartitionKey key, PartitionBoundInfo boundinfo,
 
 	return lo;
 }
+
+/*
+ * partdesc_get_defpart_oid
+ *
+ * Given a partition descriptor, if there exists a default partition return
+ * the default partition oid, otherwise return InvalidOid.
+ */
+Oid
+partdesc_get_defpart_oid(PartitionDesc partdesc)
+{
+	if (partdesc && partdesc->boundinfo &&
+		partition_bound_has_default(partdesc->boundinfo))
+		return partdesc->oids[partdesc->boundinfo->default_index];
+
+	return InvalidOid;
+}
+
+/*
+ * partition_catalog_get_defpart_oid
+ *
+ * Searches the pg_partitioned_table syscache for the given parent relation id,
+ * and if it has default partition returns the oid of the default partition,
+ * otherwise returns InvalidOid.
+ *
+ * Use this version only if relation descriptor of parent is not available,
+ * otherwise it is advisable to use partdesc_get_defpart_oid().
+ */
+Oid
+partition_catalog_get_defpart_oid(Oid parentId)
+{
+	HeapTuple	tuple;
+	Oid			defaultPartId = InvalidOid;
+
+	tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(parentId));
+
+	if (HeapTupleIsValid(tuple))
+	{
+		Form_pg_partitioned_table part_table_form;
+
+		part_table_form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+		defaultPartId = part_table_form->partdefid;
+	}
+
+	ReleaseSysCache(tuple);
+	return defaultPartId;
+}
+
+/*
+ * update_default_partition_oid
+ *
+ * Updates the pg_partition_table catalog partdefid field for the given parent
+ * with the given default partition oid.
+ */
+void
+update_default_partition_oid(Oid parentId, Oid defaultPartId)
+{
+	HeapTuple	tuple;
+	Relation	pg_partitioned_table;
+	Form_pg_partitioned_table part_table_form;
+
+	pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCacheCopy1(PARTRELID, ObjectIdGetDatum(parentId));
+
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition key of relation %u",
+			 parentId);
+
+	part_table_form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+	part_table_form->partdefid = defaultPartId;
+	CatalogTupleUpdate(pg_partitioned_table, &tuple->t_self, tuple);
+
+	heap_freetuple(tuple);
+	heap_close(pg_partitioned_table, RowExclusiveLock);
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 77f5761..c83aa5f 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -774,7 +774,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	{
 		PartitionBoundSpec *bound;
 		ParseState *pstate;
-		Oid			parentId = linitial_oid(inheritOids);
+		Oid			parentId = linitial_oid(inheritOids),
+					defaultPartOid;
 		Relation	parent;
 
 		/* Already have strong enough lock on the parent */
@@ -790,6 +791,17 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 					 errmsg("\"%s\" is not partitioned",
 							RelationGetRelationName(parent))));
 
+		/*
+		 * A table cannot be created as a partition of a parent already having
+		 * a default partition.
+		 */
+		defaultPartOid = partdesc_get_defpart_oid(RelationGetPartitionDesc(parent));
+		if (OidIsValid(defaultPartOid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("cannot add a new partition to table \"%s\" having a default partition",
+							RelationGetRelationName(parent))));
+
 		/* Tranform the bound values */
 		pstate = make_parsestate(NULL);
 		pstate->p_sourcetext = queryString;
@@ -806,6 +818,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		/* Update the pg_class entry. */
 		StorePartitionBound(rel, parent, bound);
 
+		/* Update the default partition oid */
+		if (bound->is_default)
+			update_default_partition_oid(RelationGetRelid(parent), relationId);
+
 		heap_close(parent, NoLock);
 
 		/*
@@ -13658,6 +13674,15 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	ObjectAddress address;
 	const char *trigger_name;
 	bool		found_whole_row;
+	Oid			defaultPartOid;
+
+	/* A partition cannot be attached if there exists a default partition */
+	defaultPartOid = partdesc_get_defpart_oid(RelationGetPartitionDesc(rel));
+	if (OidIsValid(defaultPartOid))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("cannot attach a new partition to table \"%s\" having a default partition",
+						RelationGetRelationName(rel))));
 
 	attachrel = heap_openrv(cmd->name, AccessExclusiveLock);
 
@@ -13814,6 +13839,11 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	/* OK to create inheritance.  Rest of the checks performed there */
 	CreateInheritance(attachrel, rel);
 
+	/* Update the default partition oid */
+	if (cmd->bound->is_default)
+		update_default_partition_oid(RelationGetRelid(rel),
+									 RelationGetRelid(attachrel));
+
 	/*
 	 * Check that the new partition's bound is valid and does not overlap any
 	 * of existing partitions of the parent - note that it does not return on
@@ -13882,8 +13912,25 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 				new_null[Natts_pg_class],
 				new_repl[Natts_pg_class];
 	ObjectAddress address;
+	Oid			defaultPartOid;
 
-	partRel = heap_openrv(name, AccessShareLock);
+	/*
+	 * We must lock the default partition, for the same reasons explained in
+	 * heap_drop_with_catalog().
+	 */
+	defaultPartOid = partdesc_get_defpart_oid(RelationGetPartitionDesc(rel));
+	if (OidIsValid(defaultPartOid))
+		LockRelationOid(defaultPartOid, AccessExclusiveLock);
+
+	/*
+	 * We already hold an exclusive lock on the default partition that is
+	 * going to be held until the transaction is commited, hence need to
+	 * acquire a shared lock only if the partition being detached is not the
+	 * default partition.
+	 */
+	partRel = heap_openrv(name, NoLock);
+	if (RelationGetRelid(partRel) != defaultPartOid)
+		LockRelationOid(RelationGetRelid(partRel), AccessShareLock);
 
 	/* All inheritance related checks are performed within the function */
 	RemoveInheritance(partRel, rel);
@@ -13913,6 +13960,24 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 	heap_freetuple(newtuple);
 	heap_close(classRel, RowExclusiveLock);
 
+	if (OidIsValid(defaultPartOid))
+	{
+		/*
+		 * If the detach relation is the default partition itself, invalidate
+		 * its entry in pg_partitioned_table.
+		 */
+		if (RelationGetRelid(partRel) == defaultPartOid)
+			update_default_partition_oid(RelationGetRelid(rel), InvalidOid);
+		else
+		{
+			/*
+			 * We must invalidate default partition's relcache, for the same
+			 * reasons explained in heap_drop_with_catalog().
+			 */
+			CacheInvalidateRelcacheByRelid(defaultPartOid);
+		}
+	}
+
 	/*
 	 * Invalidate the parent's relcache so that the partition is no longer
 	 * included in its partition descriptor.
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 9bae264..f1bed14 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4450,6 +4450,7 @@ _copyPartitionBoundSpec(const PartitionBoundSpec *from)
 	PartitionBoundSpec *newnode = makeNode(PartitionBoundSpec);
 
 	COPY_SCALAR_FIELD(strategy);
+	COPY_SCALAR_FIELD(is_default);
 	COPY_NODE_FIELD(listdatums);
 	COPY_NODE_FIELD(lowerdatums);
 	COPY_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 11731da..8b56b91 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2839,6 +2839,7 @@ static bool
 _equalPartitionBoundSpec(const PartitionBoundSpec *a, const PartitionBoundSpec *b)
 {
 	COMPARE_SCALAR_FIELD(strategy);
+	COMPARE_SCALAR_FIELD(is_default);
 	COMPARE_NODE_FIELD(listdatums);
 	COMPARE_NODE_FIELD(lowerdatums);
 	COMPARE_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 9ee3e23..b83d919 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3573,6 +3573,7 @@ _outPartitionBoundSpec(StringInfo str, const PartitionBoundSpec *node)
 	WRITE_NODE_TYPE("PARTITIONBOUNDSPEC");
 
 	WRITE_CHAR_FIELD(strategy);
+	WRITE_BOOL_FIELD(is_default);
 	WRITE_NODE_FIELD(listdatums);
 	WRITE_NODE_FIELD(lowerdatums);
 	WRITE_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 67b9e19..fbf8330 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2390,6 +2390,7 @@ _readPartitionBoundSpec(void)
 	READ_LOCALS(PartitionBoundSpec);
 
 	READ_CHAR_FIELD(strategy);
+	READ_BOOL_FIELD(is_default);
 	READ_NODE_FIELD(listdatums);
 	READ_NODE_FIELD(lowerdatums);
 	READ_NODE_FIELD(upperdatums);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 5eb3981..c303818 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -575,7 +575,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>			part_strategy
 %type <partelem>	part_elem
 %type <list>		part_params
-%type <partboundspec> ForValues
+%type <partboundspec> PartitionBoundSpec
 %type <node>		partbound_datum PartitionRangeDatum
 %type <list>		partbound_datum_list range_datum_list
 
@@ -1980,7 +1980,7 @@ alter_table_cmds:
 
 partition_cmd:
 			/* ALTER TABLE <name> ATTACH PARTITION <table_name> FOR VALUES */
-			ATTACH PARTITION qualified_name ForValues
+			ATTACH PARTITION qualified_name PartitionBoundSpec
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
 					PartitionCmd *cmd = makeNode(PartitionCmd);
@@ -2635,13 +2635,14 @@ alter_identity_column_option:
 				}
 		;
 
-ForValues:
+PartitionBoundSpec:
 			/* a LIST partition */
 			FOR VALUES IN_P '(' partbound_datum_list ')'
 				{
 					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
 
 					n->strategy = PARTITION_STRATEGY_LIST;
+					n->is_default = false;
 					n->listdatums = $5;
 					n->location = @3;
 
@@ -2654,12 +2655,24 @@ ForValues:
 					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
 
 					n->strategy = PARTITION_STRATEGY_RANGE;
+					n->is_default = false;
 					n->lowerdatums = $5;
 					n->upperdatums = $9;
 					n->location = @3;
 
 					$$ = n;
 				}
+
+			/* a DEFAULT partition */
+			| DEFAULT
+				{
+					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+					n->is_default = true;
+					n->location = @1;
+
+					$$ = n;
+				}
 		;
 
 partbound_datum:
@@ -3130,7 +3143,7 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
-			OptTypedTableElementList ForValues OptPartitionSpec OptWith
+			OptTypedTableElementList PartitionBoundSpec OptPartitionSpec OptWith
 			OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
@@ -3149,7 +3162,7 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
-			qualified_name OptTypedTableElementList ForValues OptPartitionSpec
+			qualified_name OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
 			OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
@@ -4864,7 +4877,7 @@ CreateForeignTableStmt:
 					$$ = (Node *) n;
 				}
 		| CREATE FOREIGN TABLE qualified_name
-			PARTITION OF qualified_name OptTypedTableElementList ForValues
+			PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
 			SERVER name create_generic_options
 				{
 					CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
@@ -4885,7 +4898,7 @@ CreateForeignTableStmt:
 					$$ = (Node *) n;
 				}
 		| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
-			PARTITION OF qualified_name OptTypedTableElementList ForValues
+			PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
 			SERVER name create_generic_options
 				{
 					CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 2058679..e285964 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -60,6 +61,7 @@
 #include "parser/parse_utilcmd.h"
 #include "parser/parser.h"
 #include "rewrite/rewriteManip.h"
+#include "storage/lmgr.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
@@ -3307,6 +3309,18 @@ transformPartitionBound(ParseState *pstate, Relation parent,
 	/* Avoid scribbling on input */
 	result_spec = copyObject(spec);
 
+	if (spec->is_default)
+	{
+		/*
+		 * In case of the default partition, parser had no way to identify the
+		 * partition strategy. Assign the parent's strategy to the default
+		 * partition bound spec.
+		 */
+		result_spec->strategy = strategy;
+
+		return result_spec;
+	}
+
 	if (strategy == PARTITION_STRATEGY_LIST)
 	{
 		ListCell   *cell;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 12decb0..71e8b1d 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8696,10 +8696,17 @@ get_rule_expr(Node *node, deparse_context *context,
 		case T_PartitionBoundSpec:
 			{
 				PartitionBoundSpec *spec = (PartitionBoundSpec *) node;
+				char		strategy = spec->strategy;
 				ListCell   *cell;
 				char	   *sep;
 
-				switch (spec->strategy)
+				if (spec->is_default)
+				{
+					appendStringInfoString(buf, "DEFAULT");
+					break;
+				}
+
+				switch (strategy)
 				{
 					case PARTITION_STRATEGY_LIST:
 						Assert(spec->listdatums != NIL);
@@ -8731,7 +8738,7 @@ get_rule_expr(Node *node, deparse_context *context,
 
 					default:
 						elog(ERROR, "unrecognized partition strategy: %d",
-							 (int) spec->strategy);
+							 (int) strategy);
 						break;
 				}
 			}
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6fb9bdd..579dd92 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1893,7 +1893,7 @@ describeOneTableDetails(const char *schemaname,
 			parent_name = PQgetvalue(result, 0, 0);
 			partdef = PQgetvalue(result, 0, 1);
 
-			if (PQnfields(result) == 3)
+			if (PQnfields(result) == 3 && !PQgetisnull(result, 0, 2))
 				partconstraintdef = PQgetvalue(result, 0, 2);
 
 			printfPQExpBuffer(&tmpbuf, _("Partition of: %s %s"), parent_name,
@@ -1902,8 +1902,12 @@ describeOneTableDetails(const char *schemaname,
 
 			if (partconstraintdef)
 			{
-				printfPQExpBuffer(&tmpbuf, _("Partition constraint: %s"),
-								  partconstraintdef);
+				/* If there isn't any constraint, show that explicitly */
+				if (partconstraintdef[0] == '\0')
+					printfPQExpBuffer(&tmpbuf, _("No partition constraint"));
+				else
+					printfPQExpBuffer(&tmpbuf, _("Partition constraint: %s"),
+									  partconstraintdef);
 				printTableAddFooter(&cont, tmpbuf.data);
 			}
 
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 2ab8809..a09c49d 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2053,7 +2053,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("ATTACH", "PARTITION", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 	else if (TailMatches2("FOR", "VALUES"))
 		COMPLETE_WITH_LIST2("FROM (", "IN (");
 
@@ -2492,7 +2492,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_tables, "");
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches3("PARTITION", "OF", MatchAny))
-		COMPLETE_WITH_CONST("FOR VALUES");
+		COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT");
 
 /* CREATE TABLESPACE */
 	else if (Matches3("CREATE", "TABLESPACE", MatchAny))
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 2283c67..6798561 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -99,4 +99,8 @@ extern int get_partition_for_tuple(PartitionDispatch *pd,
 						EState *estate,
 						PartitionDispatchData **failed_at,
 						TupleTableSlot **failed_slot);
+extern Oid	partdesc_get_defpart_oid(PartitionDesc partdesc);
+extern Oid	partition_catalog_get_defpart_oid(Oid parentId);
+extern void update_default_partition_oid(Oid parentId, Oid defaultPartId);
+
 #endif							/* PARTITION_H */
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
index 38d64d6..525e541 100644
--- a/src/include/catalog/pg_partitioned_table.h
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -32,6 +32,8 @@ CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
 	Oid			partrelid;		/* partitioned table oid */
 	char		partstrat;		/* partitioning strategy */
 	int16		partnatts;		/* number of partition key columns */
+	Oid			partdefid;		/* default partition oid; InvalidOid if there
+								 * isn't one */
 
 	/*
 	 * variable-length fields start here, but we allow direct access to
@@ -62,13 +64,14 @@ typedef FormData_pg_partitioned_table *Form_pg_partitioned_table;
  *		compiler constants for pg_partitioned_table
  * ----------------
  */
-#define Natts_pg_partitioned_table				7
+#define Natts_pg_partitioned_table				8
 #define Anum_pg_partitioned_table_partrelid		1
 #define Anum_pg_partitioned_table_partstrat		2
 #define Anum_pg_partitioned_table_partnatts		3
-#define Anum_pg_partitioned_table_partattrs		4
-#define Anum_pg_partitioned_table_partclass		5
-#define Anum_pg_partitioned_table_partcollation 6
-#define Anum_pg_partitioned_table_partexprs		7
+#define Anum_pg_partitioned_table_partdefid		4
+#define Anum_pg_partitioned_table_partattrs		5
+#define Anum_pg_partitioned_table_partclass		6
+#define Anum_pg_partitioned_table_partcollation 7
+#define Anum_pg_partitioned_table_partexprs		8
 
 #endif							/* PG_PARTITIONED_TABLE_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3171815..f3e4c69 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -797,6 +797,7 @@ typedef struct PartitionBoundSpec
 	NodeTag		type;
 
 	char		strategy;		/* see PARTITION_STRATEGY codes above */
+	bool		is_default;		/* is it a default partition bound? */
 
 	/* Partitioning info for LIST strategy: */
 	List	   *listdatums;		/* List of Consts (or A_Consts in raw tree) */
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 0f36423..c82c533 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3297,6 +3297,14 @@ SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::reg
 CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
 ERROR:  partition "fail_part" would overlap partition "part_1"
+-- check that an existing table can be attached as a default partition
+CREATE TABLE def_part (LIKE list_parted INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION def_part DEFAULT;
+-- check attaching default partition fails if a default partition already
+-- exists
+CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT;
+ERROR:  cannot attach a new partition to table "list_parted" having a default partition
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
 	a int,
@@ -3310,6 +3318,15 @@ ERROR:  partition constraint is violated by some row
 -- should be ok after deleting the bad row
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+-- check partition cannot be attached if default exists
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
+INSERT INTO list_parted2_def VALUES (11, 'z');
+CREATE TABLE part_3 (LIKE list_parted2);
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+ERROR:  cannot attach a new partition to table "list_parted2" having a default partition
+-- should be ok after detaching a default partition
+ALTER TABLE list_parted2 DETACH PARTITION list_parted2_def;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
 CREATE TABLE part_3_4 (
@@ -3350,6 +3367,12 @@ 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;
+-- New partition cannot be attached if a default partition exists
+CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS);
+ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT;
+ERROR:  cannot attach a new partition to table "range_parted" having a default partition
 -- check that leaf partitions are scanned when attaching a partitioned
 -- table
 CREATE TABLE part_5 (
@@ -3538,6 +3561,7 @@ ALTER TABLE list_parted2 ALTER COLUMN b TYPE text;
 ERROR:  cannot alter type of column named in partition key
 -- cleanup
 DROP TABLE list_parted, list_parted2, range_parted;
+DROP TABLE fail_def_part;
 -- more tests for certain multi-level partitioning scenarios
 create table p (a int, b int) partition by range (a, b);
 create table p1 (b int, a int not null) partition by range (b);
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index babda89..4f79612 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -467,6 +467,13 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
 ERROR:  invalid bound specification for a list partition
 LINE 1: ...BLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) T...
                                                              ^
+-- check default partition cannot be created more than once
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
+ERROR:  cannot add a new partition to table "list_parted" having a default partition
+-- check that no partition can be created after default partition
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (3);
+ERROR:  cannot add a new partition to table "list_parted" having a default partition
 -- specified literal can't be cast to the partition column data type
 CREATE TABLE bools (
 	a bool
@@ -585,6 +592,11 @@ 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;
+-- New partition cannot be created if a default partition exists
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90);
+ERROR:  cannot add a new partition to table "range_parted2" having a default partition
 -- now check for multi-column range partition key
 CREATE TABLE range_parted3 (
 	a int,
@@ -603,6 +615,8 @@ ERROR:  partition "fail_part" would overlap partition "part12"
 -- more specific ranges
 CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, minvalue) TO (1, maxvalue);
 ERROR:  partition "fail_part" would overlap partition "part10"
+-- check default partition can be added to multi-column range partitioned table
+CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
 -- check schema propagation from parent
 CREATE TABLE parted (
 	a text,
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index e159d62..48f5292 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -219,17 +219,66 @@ insert into part_null values (null, 0);
 create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b);
 create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
 create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
+-- test default partition
+create table part_default partition of list_parted default;
+-- Negative test: a row, which would fit in other partition, does not fit
+-- default partition, even when inserted directly
+insert into part_default values ('aa', 2);
+ERROR:  new row for relation "part_default" violates partition constraint
+DETAIL:  Failing row contains (aa, 2).
+insert into part_default values (null, 2);
+ERROR:  new row for relation "part_default" violates partition constraint
+DETAIL:  Failing row contains (null, 2).
+-- ok
+insert into part_default values ('Zz', 2);
+-- test if default partition works as expected for multi-level partitioned
+-- table as well as when default partition itself is further partitioned
+drop table part_default;
+create table part_xx_yy partition of list_parted for values in ('xx', 'yy') partition by list (a);
+create table part_xx_yy_p1 partition of part_xx_yy for values in ('xx');
+create table part_xx_yy_defpart partition of part_xx_yy default;
+create table part_default partition of list_parted default partition by range(b);
+create table part_default_p1 partition of part_default for values from (20) to (30);
+create table part_default_p2 partition of part_default for values from (30) to (40);
 -- fail
 insert into part_ee_ff1 values ('EE', 11);
 ERROR:  new row for relation "part_ee_ff1" violates partition constraint
 DETAIL:  Failing row contains (EE, 11).
+insert into part_default_p2 values ('gg', 43);
+ERROR:  new row for relation "part_default_p2" violates partition constraint
+DETAIL:  Failing row contains (gg, 43).
 -- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
 insert into part_ee_ff1 values ('cc', 1);
 ERROR:  new row for relation "part_ee_ff1" violates partition constraint
 DETAIL:  Failing row contains (cc, 1).
+insert into part_default values ('gg', 43);
+ERROR:  no partition of relation "part_default" found for row
+DETAIL:  Partition key of the failing row contains (b) = (43).
 -- ok
 insert into part_ee_ff1 values ('ff', 1);
 insert into part_ee_ff2 values ('ff', 11);
+insert into part_default_p1 values ('cd', 25);
+insert into part_default_p2 values ('de', 35);
+insert into list_parted values ('ab', 21);
+insert into list_parted values ('xx', 1);
+insert into list_parted values ('yy', 2);
+select tableoid::regclass, * from list_parted;
+      tableoid      | a  | b  
+--------------------+----+----
+ part_cc_dd         | cC |  1
+ part_ee_ff1        | ff |  1
+ part_ee_ff2        | ff | 11
+ part_xx_yy_p1      | xx |  1
+ part_xx_yy_defpart | yy |  2
+ part_null          |    |  0
+ part_default_p1    | cd | 25
+ part_default_p1    | ab | 21
+ part_default_p2    | de | 35
+(9 rows)
+
+-- drop default partition, as we need to add some more partitions to test tuple
+-- routing
+drop table part_default;
 -- Check tuple routing for partitioned tables
 -- fail
 insert into range_parted values ('a', 0);
@@ -249,6 +298,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  
 ----------+---+----
@@ -258,7 +319,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);
@@ -274,17 +340,19 @@ DETAIL:  Partition key of the failing row contains (b) = (0).
 insert into list_parted values ('EE', 1);
 insert into part_ee_ff values ('EE', 10);
 select tableoid::regclass, * from list_parted;
-  tableoid   | a  | b  
--------------+----+----
- part_aa_bb  | aA |   
- part_cc_dd  | cC |  1
- part_ee_ff1 | ff |  1
- part_ee_ff1 | EE |  1
- part_ee_ff2 | ff | 11
- part_ee_ff2 | EE | 10
- part_null   |    |  0
- part_null   |    |  1
-(8 rows)
+      tableoid      | a  | b  
+--------------------+----+----
+ part_aa_bb         | aA |   
+ part_cc_dd         | cC |  1
+ part_ee_ff1        | ff |  1
+ part_ee_ff1        | EE |  1
+ part_ee_ff2        | ff | 11
+ part_ee_ff2        | EE | 10
+ part_xx_yy_p1      | xx |  1
+ part_xx_yy_defpart | yy |  2
+ part_null          |    |  0
+ part_null          |    |  1
+(10 rows)
 
 -- some more tests to exercise tuple-routing with multi-level partitioning
 create table part_gg partition of list_parted for values in ('gg') partition by range (b);
@@ -316,6 +384,23 @@ select tableoid::regclass::text, a, min(b) as min_b, max(b) as max_b from list_p
 
 -- cleanup
 drop table range_parted, list_parted;
+-- test that a default partition added as the first partition accepts any value
+-- including null
+create table list_parted (a int) partition by list (a);
+create table part_default partition of list_parted default;
+insert into part_default values (null);
+insert into part_default values (1);
+insert into part_default values (-1);
+select tableoid::regclass, a from list_parted;
+   tableoid   | a  
+--------------+----
+ part_default |   
+ part_default |  1
+ part_default | -1
+(3 rows)
+
+-- cleanup
+drop table list_parted;
 -- more tests for certain multi-level partitioning scenarios
 create table mlparted (a int, b int) partition by range (a, b);
 create table mlparted1 (b int not null, a int not null) partition by range ((b+0));
@@ -425,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/plancache.out b/src/test/regress/expected/plancache.out
index 3f3db33..fb90357 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -252,3 +252,25 @@ NOTICE:  3
  
 (1 row)
 
+-- Check that addition or removal of any partition is correctly dealt with by
+-- default partition table when it is being used in prepared statement.
+create table list_parted (a int) partition by list(a);
+create table list_part_null partition of list_parted for values in (null);
+create table list_part_1 partition of list_parted for values in (1);
+create table list_part_def partition of list_parted default;
+prepare pstmt_def_insert (int) as insert into list_part_def values($1);
+-- should fail
+execute pstmt_def_insert(null);
+ERROR:  new row for relation "list_part_def" violates partition constraint
+DETAIL:  Failing row contains (null).
+execute pstmt_def_insert(1);
+ERROR:  new row for relation "list_part_def" violates partition constraint
+DETAIL:  Failing row contains (1).
+alter table list_parted detach partition list_part_null;
+-- should be ok
+execute pstmt_def_insert(null);
+drop table list_part_1;
+-- should be ok
+execute pstmt_def_insert(1);
+drop table list_parted, list_part_null;
+deallocate pstmt_def_insert;
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 9366f04..cfb6aa8 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -218,5 +218,29 @@ 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
+) partition by list (a);
+create table list_part1  partition of list_parted for values in ('a', 'b');
+create table list_default partition of list_parted default;
+insert into list_part1 values ('a', 1);
+insert into list_default values ('d', 10);
+-- fail
+update list_default set a = 'a' where a = 'd';
+ERROR:  new row for relation "list_default" violates partition constraint
+DETAIL:  Failing row contains (a, 10).
+-- ok
+update list_default set a = 'x' where a = 'd';
 -- cleanup
 drop table range_parted;
+drop table list_parted;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index e6f6669..ac6f9d3 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2111,6 +2111,13 @@ SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::reg
 -- check that the new partition won't overlap with an existing partition
 CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+-- check that an existing table can be attached as a default partition
+CREATE TABLE def_part (LIKE list_parted INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION def_part DEFAULT;
+-- check attaching default partition fails if a default partition already
+-- exists
+CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT;
 
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
@@ -2127,6 +2134,15 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 
+-- check partition cannot be attached if default exists
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
+INSERT INTO list_parted2_def VALUES (11, 'z');
+CREATE TABLE part_3 (LIKE list_parted2);
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+-- should be ok after detaching a default partition
+ALTER TABLE list_parted2 DETACH PARTITION list_parted2_def;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
+
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
 CREATE TABLE part_3_4 (
@@ -2172,6 +2188,13 @@ 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;
+
+-- New partition cannot be attached if a default partition exists
+CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS);
+ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT;
+
 -- check that leaf partitions are scanned when attaching a partitioned
 -- table
 CREATE TABLE part_5 (
@@ -2327,6 +2350,7 @@ ALTER TABLE list_parted2 ALTER COLUMN b TYPE text;
 
 -- cleanup
 DROP TABLE list_parted, list_parted2, range_parted;
+DROP TABLE fail_def_part;
 
 -- more tests for certain multi-level partitioning scenarios
 create table p (a int, b int) partition by range (a, b);
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 1c0ce927..24f005e 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -447,6 +447,12 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
 -- trying to specify range for list partitioned table
 CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
 
+-- check default partition cannot be created more than once
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
+-- check that no partition can be created after default partition
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (3);
+
 -- specified literal can't be cast to the partition column data type
 CREATE TABLE bools (
 	a bool
@@ -546,6 +552,12 @@ 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;
+
+-- New partition cannot be created if a default partition exists
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90);
+
 -- now check for multi-column range partition key
 CREATE TABLE range_parted3 (
 	a int,
@@ -565,6 +577,9 @@ CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1,
 -- more specific ranges
 CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, minvalue) TO (1, maxvalue);
 
+-- check default partition can be added to multi-column range partitioned table
+CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
+
 -- check schema propagation from parent
 
 CREATE TABLE parted (
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 6f17872..7fc5688 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -132,13 +132,42 @@ create table part_ee_ff partition of list_parted for values in ('ee', 'ff') part
 create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
 create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
 
+-- test default partition
+create table part_default partition of list_parted default;
+-- Negative test: a row, which would fit in other partition, does not fit
+-- default partition, even when inserted directly
+insert into part_default values ('aa', 2);
+insert into part_default values (null, 2);
+-- ok
+insert into part_default values ('Zz', 2);
+-- test if default partition works as expected for multi-level partitioned
+-- table as well as when default partition itself is further partitioned
+drop table part_default;
+create table part_xx_yy partition of list_parted for values in ('xx', 'yy') partition by list (a);
+create table part_xx_yy_p1 partition of part_xx_yy for values in ('xx');
+create table part_xx_yy_defpart partition of part_xx_yy default;
+create table part_default partition of list_parted default partition by range(b);
+create table part_default_p1 partition of part_default for values from (20) to (30);
+create table part_default_p2 partition of part_default for values from (30) to (40);
+
 -- fail
 insert into part_ee_ff1 values ('EE', 11);
+insert into part_default_p2 values ('gg', 43);
 -- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
 insert into part_ee_ff1 values ('cc', 1);
+insert into part_default values ('gg', 43);
 -- ok
 insert into part_ee_ff1 values ('ff', 1);
 insert into part_ee_ff2 values ('ff', 11);
+insert into part_default_p1 values ('cd', 25);
+insert into part_default_p2 values ('de', 35);
+insert into list_parted values ('ab', 21);
+insert into list_parted values ('xx', 1);
+insert into list_parted values ('yy', 2);
+select tableoid::regclass, * from list_parted;
+-- drop default partition, as we need to add some more partitions to test tuple
+-- routing
+drop table part_default;
 
 -- Check tuple routing for partitioned tables
 
@@ -154,8 +183,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');
@@ -188,6 +228,17 @@ select tableoid::regclass::text, a, min(b) as min_b, max(b) as max_b from list_p
 -- cleanup
 drop table range_parted, list_parted;
 
+-- test that a default partition added as the first partition accepts any value
+-- including null
+create table list_parted (a int) partition by list (a);
+create table part_default partition of list_parted default;
+insert into part_default values (null);
+insert into part_default values (1);
+insert into part_default values (-1);
+select tableoid::regclass, a from list_parted;
+-- cleanup
+drop table list_parted;
+
 -- more tests for certain multi-level partitioning scenarios
 create table mlparted (a int, b int) partition by range (a, b);
 create table mlparted1 (b int not null, a int not null) partition by range ((b+0));
@@ -274,6 +325,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/plancache.sql b/src/test/regress/sql/plancache.sql
index bc20861..a8e357c 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -156,3 +156,22 @@ end$$ language plpgsql;
 
 select cachebug();
 select cachebug();
+
+-- Check that addition or removal of any partition is correctly dealt with by
+-- default partition table when it is being used in prepared statement.
+create table list_parted (a int) partition by list(a);
+create table list_part_null partition of list_parted for values in (null);
+create table list_part_1 partition of list_parted for values in (1);
+create table list_part_def partition of list_parted default;
+prepare pstmt_def_insert (int) as insert into list_part_def values($1);
+-- should fail
+execute pstmt_def_insert(null);
+execute pstmt_def_insert(1);
+alter table list_parted detach partition list_part_null;
+-- should be ok
+execute pstmt_def_insert(null);
+drop table list_part_1;
+-- should be ok
+execute pstmt_def_insert(1);
+drop table list_parted, list_part_null;
+deallocate pstmt_def_insert;
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 6637119..1ce7a94 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -125,5 +125,28 @@ 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
+) partition by list (a);
+create table list_part1  partition of list_parted for values in ('a', 'b');
+create table list_default partition of list_parted default;
+insert into list_part1 values ('a', 1);
+insert into list_default values ('d', 10);
+
+-- fail
+update list_default set a = 'a' where a = 'd';
+-- ok
+update list_default set a = 'x' where a = 'd';
+
 -- cleanup
 drop table range_parted;
+drop table list_parted;
-- 
2.7.4

0003-Default-partition-extended-for-create-and-attach.patch0000664000175000017500000012452113154520373023213 0ustar  jeevanjeevanFrom 58d31fd1e790413c9f477eb6a3f27c08e026c8cd Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Fri, 8 Sep 2017 17:09:28 +0530
Subject: [PATCH 3/5] Default partition extended for create and attach

1. This patch extends the previous patch in this series to allow a
new partition to be created or attached even when a default
partition exists.
2. Also, extends the regression tests to test this functionality.
in this series.
3. While adding a new partition we need to make sure that default
partition does not contain any rows that would fit in newly added
partition. So, in this patch adds a code to negate the partition
constraints of new partition so as these constraints form the part
of would be default partition constraints. Then the default
partition is scanned to check if there exist a row that does not
hold these negated partition constraints, if there exists such a
row error out.
4. While doing 3, to optimize the validation scan a check is done,
if the existing constraints on the default partition imply that it
will not contain any row that would belong to the new partition,
then the validation scan is skipped.

Jeevan Ladhe, some refactoring by Ashutosh bapat.
---
 src/backend/catalog/heap.c                 |  33 ++---
 src/backend/catalog/partition.c            | 187 ++++++++++++++++++++++++++++-
 src/backend/commands/tablecmds.c           | 112 +++++++++++++----
 src/include/catalog/partition.h            |   3 +
 src/include/commands/tablecmds.h           |   4 +
 src/test/regress/expected/alter_table.out  |  39 ++++--
 src/test/regress/expected/create_table.out |  22 ++--
 src/test/regress/expected/insert.out       |   8 +-
 src/test/regress/expected/plancache.out    |   4 +
 src/test/regress/sql/alter_table.sql       |  31 ++++-
 src/test/regress/sql/create_table.sql      |  17 ++-
 src/test/regress/sql/insert.sql            |   3 -
 src/test/regress/sql/plancache.sql         |   2 +
 13 files changed, 388 insertions(+), 77 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 53b61f7..54f667f 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1778,15 +1778,8 @@ heap_drop_with_catalog(Oid relid)
 		LockRelationOid(parentOid, AccessExclusiveLock);
 
 		/*
-		 * The partition constraint of the default partition depends on the
-		 * partition bounds of every other partition. It is possible that
-		 * other backend might be about to execute a query on the default
-		 * partition table and the query relies on previously cached default
-		 * partition constraints, which won't be correct after removal of a
-		 * partition. We must therefore take a table lock strong enough to
-		 * prevent all queries on the default partition from proceeding until
-		 * we commit and send out a shared-cache-inval notice that will make
-		 * them update their index lists. Note that we need not lock the
+		 * We must also lock the default partition, for the same reasons
+		 * explained in DefineRelation(). Note that we need not lock the
 		 * default partition if the table being dropped itself is the default
 		 * partition, because it is going to be locked further.
 		 */
@@ -1910,11 +1903,9 @@ heap_drop_with_catalog(Oid relid)
 	if (OidIsValid(parentOid))
 	{
 		/*
-		 * The partition constraint for the default partition depends on the
-		 * partition bounds of every other partition, so we must invalidate
-		 * the relcache entry for that partition every time a partition is
-		 * added or removed. If the default partition itself is dropped no
-		 * need to invalidate its relcache.
+		 * We must invalidate default partition's relcache, for the same
+		 * reasons explained in StorePartitionBound(). If the default
+		 * partition itself is dropped no need to invalidate its relcache.
 		 */
 		if (OidIsValid(defaultPartOid) && relid != defaultPartOid)
 			CacheInvalidateRelcacheByRelid(defaultPartOid);
@@ -3259,7 +3250,8 @@ RemovePartitionKeyByRelId(Oid relid)
  *		relispartition to true
  *
  * Also, invalidate the parent's relcache, so that the next rebuild will load
- * the new partition's info into its partition descriptor.
+ * the new partition's info into its partition descriptor.  If there is a
+ * default partition, we must invalidate its relcache entry as well.
  */
 void
 StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
@@ -3270,6 +3262,7 @@ StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
 	Datum		new_val[Natts_pg_class];
 	bool		new_null[Natts_pg_class],
 				new_repl[Natts_pg_class];
+	Oid			defaultPartOid;
 
 	/* Update pg_class tuple */
 	classRel = heap_open(RelationRelationId, RowExclusiveLock);
@@ -3307,5 +3300,15 @@ StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
 	heap_freetuple(newtuple);
 	heap_close(classRel, RowExclusiveLock);
 
+	/*
+	 * The partition constraint for the default partition depends on the
+	 * partition bounds of every other partition, so we must invalidate the
+	 * relcache entry for that partition every time a partition is added or
+	 * removed.
+	 */
+	defaultPartOid = partdesc_get_defpart_oid(RelationGetPartitionDesc(parent));
+	if (OidIsValid(defaultPartOid))
+		CacheInvalidateRelcacheByRelid(defaultPartOid);
+
 	CacheInvalidateRelcache(parent);
 }
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index f8cec69..0a5bf1f 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -29,6 +29,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_type.h"
+#include "commands/tablecmds.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -36,6 +37,7 @@
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planmain.h"
+#include "optimizer/prep.h"
 #include "optimizer/var.h"
 #include "rewrite/rewriteManip.h"
 #include "storage/lmgr.h"
@@ -708,12 +710,23 @@ check_new_partition_bound(char *relname, Relation parent,
 {
 	PartitionKey key = RelationGetPartitionKey(parent);
 	PartitionDesc partdesc = RelationGetPartitionDesc(parent);
+	PartitionBoundInfo boundinfo = partdesc->boundinfo;
 	ParseState *pstate = make_parsestate(NULL);
 	int			with = -1;
 	bool		overlap = false;
 
 	if (spec->is_default)
-		return;
+	{
+		if (boundinfo == NULL || !partition_bound_has_default(boundinfo))
+			return;
+
+		/* Default partition already exists, error out. */
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("partition \"%s\" conflicts with existing default partition \"%s\"",
+						relname, get_rel_name(partdesc->oids[boundinfo->default_index])),
+				 parser_errposition(pstate, spec->location)));
+	}
 
 	switch (key->strategy)
 	{
@@ -723,13 +736,13 @@ check_new_partition_bound(char *relname, Relation parent,
 
 				if (partdesc->nparts > 0)
 				{
-					PartitionBoundInfo boundinfo = partdesc->boundinfo;
 					ListCell   *cell;
 
 					Assert(boundinfo &&
 						   boundinfo->strategy == PARTITION_STRATEGY_LIST &&
 						   (boundinfo->ndatums > 0 ||
-							partition_bound_accepts_nulls(boundinfo)));
+							partition_bound_accepts_nulls(boundinfo) ||
+							partition_bound_has_default(boundinfo)));
 
 					foreach(cell, spec->listdatums)
 					{
@@ -794,8 +807,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
@@ -873,6 +888,139 @@ check_new_partition_bound(char *relname, Relation parent,
 }
 
 /*
+ * check_default_allows_bound
+ *
+ * This function checks if there exists a row in the default partition that
+ * fits in the new partition being added and throws an error if it finds one.
+ */
+void
+check_default_allows_bound(Relation parent, Relation default_rel,
+						   PartitionBoundSpec *new_spec)
+{
+	List	   *new_part_constraints;
+	List	   *def_part_constraints;
+	List	   *all_parts;
+	ListCell   *lc;
+
+	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);
+
+	/*
+	 * If the existing constraints on the default partition imply that it will
+	 * not contain any row that would belong to the new partition, we can
+	 * avoid scanning the default partition.
+	 */
+	if (PartConstraintImpliedByRelConstraint(default_rel, def_part_constraints))
+	{
+		ereport(INFO,
+				(errmsg("partition constraint for table \"%s\" is implied by existing constraints",
+						RelationGetRelationName(default_rel))));
+		return;
+	}
+
+	/*
+	 * Bad luck, scan the default partition and its subpartitions, and check
+	 * if any of the row does not satisfy the partition constraints that are
+	 * going to be imposed as additional constraints on default partition by
+	 * addition of the new partition.
+	 */
+	if (default_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		all_parts = find_all_inheritors(RelationGetRelid(default_rel),
+										AccessExclusiveLock, NULL);
+	else
+		all_parts = list_make1_oid(RelationGetRelid(default_rel));
+
+	foreach(lc, all_parts)
+	{
+		Oid			part_relid = lfirst_oid(lc);
+		Relation	part_rel;
+		Expr	   *constr;
+		Expr	   *partition_constraint;
+		EState	   *estate;
+		HeapTuple	tuple;
+		ExprState  *partqualstate = NULL;
+		Snapshot	snapshot;
+		TupleDesc	tupdesc;
+		ExprContext *econtext;
+		HeapScanDesc scan;
+		MemoryContext oldCxt;
+		TupleTableSlot *tupslot;
+
+		/* Lock already taken above. */
+		if (part_relid != RelationGetRelid(default_rel))
+			part_rel = heap_open(part_relid, NoLock);
+		else
+			part_rel = default_rel;
+
+		/*
+		 * Only RELKIND_RELATION relations (i.e. leaf partitions) need to be
+		 * scanned.
+		 */
+		if (part_rel->rd_rel->relkind != RELKIND_RELATION)
+		{
+			if (part_rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+				ereport(WARNING,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("skipped scanning foreign table \"%s\" which is a partition of default partition \"%s\"",
+								RelationGetRelationName(part_rel),
+								RelationGetRelationName(default_rel))));
+
+			if (RelationGetRelid(default_rel) != RelationGetRelid(part_rel))
+				heap_close(part_rel, NoLock);
+
+			continue;
+		}
+
+		tupdesc = CreateTupleDescCopy(RelationGetDescr(part_rel));
+		constr = linitial(def_part_constraints);
+		partition_constraint = (Expr *) map_partition_varattnos((List *) constr,
+																1, part_rel, parent, NULL);
+		estate = CreateExecutorState();
+
+		/* Build expression execution states for partition check quals */
+		partqualstate = ExecPrepareExpr(partition_constraint, estate);
+
+		econtext = GetPerTupleExprContext(estate);
+		snapshot = RegisterSnapshot(GetLatestSnapshot());
+		scan = heap_beginscan(part_rel, snapshot, 0, NULL);
+		tupslot = MakeSingleTupleTableSlot(tupdesc);
+
+		/*
+		 * Switch to per-tuple memory context and reset it for each tuple
+		 * produced, so we don't leak memory.
+		 */
+		oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		{
+			ExecStoreTuple(tuple, tupslot, InvalidBuffer, false);
+			econtext->ecxt_scantuple = tupslot;
+
+			if (!ExecCheck(partqualstate, econtext))
+				ereport(ERROR,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("updated partition constraint for default partition \"%s\" would be violated by some row",
+								RelationGetRelationName(default_rel))));
+
+			ResetExprContext(econtext);
+			CHECK_FOR_INTERRUPTS();
+		}
+
+		MemoryContextSwitchTo(oldCxt);
+		heap_endscan(scan);
+		UnregisterSnapshot(snapshot);
+		ExecDropSingleTupleTableSlot(tupslot);
+		FreeExecutorState(estate);
+
+		if (RelationGetRelid(default_rel) != RelationGetRelid(part_rel))
+			heap_close(part_rel, NoLock);	/* keep the lock until commit */
+	}
+}
+
+/*
  * get_partition_parent
  *
  * Returns inheritance parent of a partition by scanning pg_inherits
@@ -2662,3 +2810,32 @@ update_default_partition_oid(Oid parentId, Oid defaultPartId)
 	heap_freetuple(tuple);
 	heap_close(pg_partitioned_table, RowExclusiveLock);
 }
+
+/*
+ * get_default_part_validation_constraint
+ *
+ * This function returns negated constraint of new_part_constraints which
+ * would be an integral part of the default partition constraints after
+ * addition of the partition to which the new_part_constraints belongs.
+ */
+List *
+get_default_part_validation_constraint(List *new_part_constraints)
+{
+	Expr	   *defPartConstraint;
+
+	defPartConstraint = make_ands_explicit(new_part_constraints);
+
+	/*
+	 * Derieve the partition constraints of default partition by negating the
+	 * given partition constraints. The partition constraint never evaluates
+	 * to NULL, so negating it like this is safe.
+	 */
+	defPartConstraint = makeBoolExpr(NOT_EXPR,
+									 list_make1(defPartConstraint),
+									 -1);
+	defPartConstraint = (Expr *) eval_const_expressions(NULL,
+														(Node *) defPartConstraint);
+	defPartConstraint = canonicalize_qual(defPartConstraint);
+
+	return list_make1(defPartConstraint);
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index c83aa5f..d4bccd8 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -168,6 +168,8 @@ typedef struct AlteredTableInfo
 	bool		chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
 	char		newrelpersistence;	/* if above is true */
 	Expr	   *partition_constraint;	/* for attach partition validation */
+	/* true, if is a default partition or a child of default partition */
+	bool		is_default_partition;
 	/* Objects to rebuild after completing ALTER TYPE operations */
 	List	   *changedConstraintOids;	/* OIDs of constraints to rebuild */
 	List	   *changedConstraintDefs;	/* string definitions of same */
@@ -473,11 +475,10 @@ static void CreateInheritance(Relation child_rel, Relation parent_rel);
 static void RemoveInheritance(Relation child_rel, Relation parent_rel);
 static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
 					  PartitionCmd *cmd);
-static bool PartConstraintImpliedByRelConstraint(Relation scanrel,
-									 List *partConstraint);
 static void ValidatePartitionConstraints(List **wqueue, Relation scanrel,
 							 List *scanrel_children,
-							 List *partConstraint);
+							 List *partConstraint,
+							 bool scanrel_is_default);
 static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
 
 
@@ -776,7 +777,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		ParseState *pstate;
 		Oid			parentId = linitial_oid(inheritOids),
 					defaultPartOid;
-		Relation	parent;
+		Relation	parent,
+					defaultRel;
 
 		/* Already have strong enough lock on the parent */
 		parent = heap_open(parentId, NoLock);
@@ -792,15 +794,28 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 							RelationGetRelationName(parent))));
 
 		/*
-		 * A table cannot be created as a partition of a parent already having
-		 * a default partition.
+		 * The partition constraint of the default partition depends on the
+		 * partition bounds of every other partition. It is possible that
+		 * other backend might be about to execute a query on the default
+		 * partition table and the query relies on previously cached default
+		 * partition constraints, which won't be correct after removal of a
+		 * partition. We must therefore take a table lock strong enough to
+		 * prevent all queries on the default partition from proceeding until
+		 * we commit and send out a shared-cache-inval notice that will make
+		 * them update their index lists.
+		 *
+		 * Order of locking: The relation being added won't be visible to
+		 * other backends until it is committed, hence here in
+		 * DefineRelation() the order of locking the default partition and the
+		 * relation being added does not matter. But at all other places we
+		 * need to lock the default relation before we lock the relation being
+		 * added or removed i.e. we should take the lock in same order at all
+		 * the places such that lock parent, lock default partition and then
+		 * lock the partition so as to avoid a deadlock.
 		 */
 		defaultPartOid = partdesc_get_defpart_oid(RelationGetPartitionDesc(parent));
 		if (OidIsValid(defaultPartOid))
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-					 errmsg("cannot add a new partition to table \"%s\" having a default partition",
-							RelationGetRelationName(parent))));
+			defaultRel = heap_open(defaultPartOid, AccessExclusiveLock);
 
 		/* Tranform the bound values */
 		pstate = make_parsestate(NULL);
@@ -815,6 +830,20 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		 */
 		check_new_partition_bound(relname, parent, bound);
 
+		/*
+		 * If the default partition exists, its partition constraints will
+		 * change after the addition of this new partition such that it won't
+		 * allow any row that qualifies for this new partition. So, check that
+		 * the existing data in the default partition satisfies the constraint
+		 * as it will exist after adding this partition.
+		 */
+		if (OidIsValid(defaultPartOid))
+		{
+			check_default_allows_bound(parent, defaultRel, bound);
+			/* Keep the lock until commit. */
+			heap_close(defaultRel, NoLock);
+		}
+
 		/* Update the pg_class entry. */
 		StorePartitionBound(rel, parent, bound);
 
@@ -4611,9 +4640,16 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 			}
 
 			if (partqualstate && !ExecCheck(partqualstate, econtext))
-				ereport(ERROR,
-						(errcode(ERRCODE_CHECK_VIOLATION),
-						 errmsg("partition constraint is violated by some row")));
+			{
+				if (tab->is_default_partition)
+					ereport(ERROR,
+							(errcode(ERRCODE_CHECK_VIOLATION),
+							 errmsg("updated partition constraint for default partition would be violated by some row")));
+				else
+					ereport(ERROR,
+							(errcode(ERRCODE_CHECK_VIOLATION),
+							 errmsg("partition constraint is violated by some row")));
+			}
 
 			/* Write the tuple out to the new relation */
 			if (newrel)
@@ -13498,7 +13534,7 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
  * Existing constraints includes its check constraints and column-level
  * NOT NULL constraints and partConstraint describes the partition constraint.
  */
-static bool
+bool
 PartConstraintImpliedByRelConstraint(Relation scanrel,
 									 List *partConstraint)
 {
@@ -13585,7 +13621,8 @@ PartConstraintImpliedByRelConstraint(Relation scanrel,
 static void
 ValidatePartitionConstraints(List **wqueue, Relation scanrel,
 							 List *scanrel_children,
-							 List *partConstraint)
+							 List *partConstraint,
+							 bool scanrel_is_default)
 {
 	bool		found_whole_row;
 	ListCell   *lc;
@@ -13647,6 +13684,7 @@ ValidatePartitionConstraints(List **wqueue, Relation scanrel,
 		/* Grab a work queue entry. */
 		tab = ATGetQueueEntry(wqueue, part_rel);
 		tab->partition_constraint = (Expr *) linitial(my_partconstr);
+		tab->is_default_partition = scanrel_is_default;
 
 		/* keep our lock until commit */
 		if (part_rel != scanrel)
@@ -13675,14 +13713,15 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	const char *trigger_name;
 	bool		found_whole_row;
 	Oid			defaultPartOid;
+	List	   *partBoundConstraint;
 
-	/* A partition cannot be attached if there exists a default partition */
+	/*
+	 * We must lock the default partition, for the same reasons explained in
+	 * DefineRelation().
+	 */
 	defaultPartOid = partdesc_get_defpart_oid(RelationGetPartitionDesc(rel));
 	if (OidIsValid(defaultPartOid))
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-				 errmsg("cannot attach a new partition to table \"%s\" having a default partition",
-						RelationGetRelationName(rel))));
+		LockRelationOid(defaultPartOid, AccessExclusiveLock);
 
 	attachrel = heap_openrv(cmd->name, AccessExclusiveLock);
 
@@ -13860,8 +13899,8 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	 * If the parent itself is a partition, make sure to include its
 	 * constraint as well.
 	 */
-	partConstraint = list_concat(get_qual_from_partbound(attachrel, rel,
-														 cmd->bound),
+	partBoundConstraint = get_qual_from_partbound(attachrel, rel, cmd->bound);
+	partConstraint = list_concat(partBoundConstraint,
 								 RelationGetPartitionQual(rel));
 
 	/* Skip validation if there are no constraints to validate. */
@@ -13884,7 +13923,30 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 
 		/* Validate partition constraints against the table being attached. */
 		ValidatePartitionConstraints(wqueue, attachrel, attachrel_children,
-									 partConstraint);
+									 partConstraint, false);
+
+		/*
+		 * Check whether default partition has a row that would fit the
+		 * partition being attached.
+		 */
+		defaultPartOid = partdesc_get_defpart_oid(RelationGetPartitionDesc(rel));
+		if (OidIsValid(defaultPartOid))
+		{
+			Relation	defaultrel;
+			List	   *defaultrel_children;
+			List	   *defPartConstraint;
+
+			/* We already have taken a lock on default partition. */
+			defaultrel = heap_open(defaultPartOid, NoLock);
+			defPartConstraint = get_default_part_validation_constraint(partBoundConstraint);
+			defaultrel_children = find_all_inheritors(defaultPartOid,
+													  AccessExclusiveLock, NULL);
+			ValidatePartitionConstraints(wqueue, defaultrel, defaultrel_children,
+										 defPartConstraint, true);
+
+			/* keep our lock until commit. */
+			heap_close(defaultrel, NoLock);
+		}
 	}
 
 	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachrel));
@@ -13916,7 +13978,7 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 
 	/*
 	 * We must lock the default partition, for the same reasons explained in
-	 * heap_drop_with_catalog().
+	 * DefineRelation().
 	 */
 	defaultPartOid = partdesc_get_defpart_oid(RelationGetPartitionDesc(rel));
 	if (OidIsValid(defaultPartOid))
@@ -13972,7 +14034,7 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 		{
 			/*
 			 * We must invalidate default partition's relcache, for the same
-			 * reasons explained in heap_drop_with_catalog().
+			 * reasons explained in StorePartitionBound().
 			 */
 			CacheInvalidateRelcacheByRelid(defaultPartOid);
 		}
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 6798561..016dc45 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -102,5 +102,8 @@ extern int get_partition_for_tuple(PartitionDispatch *pd,
 extern Oid	partdesc_get_defpart_oid(PartitionDesc partdesc);
 extern Oid	partition_catalog_get_defpart_oid(Oid parentId);
 extern void update_default_partition_oid(Oid parentId, Oid defaultPartId);
+extern void check_default_allows_bound(Relation parent, Relation defaultRel,
+						   PartitionBoundSpec *new_spec);
+extern List *get_default_part_validation_constraint(List *new_part_constaints);
 
 #endif							/* PARTITION_H */
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index abd31b6..da3ff5d 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -18,6 +18,7 @@
 #include "catalog/dependency.h"
 #include "catalog/objectaddress.h"
 #include "nodes/parsenodes.h"
+#include "catalog/partition.h"
 #include "storage/lock.h"
 #include "utils/relcache.h"
 
@@ -87,4 +88,7 @@ extern void RangeVarCallbackOwnsTable(const RangeVar *relation,
 
 extern void RangeVarCallbackOwnsRelation(const RangeVar *relation,
 							 Oid relId, Oid oldRelId, void *noCatalogs);
+extern bool PartConstraintImpliedByRelConstraint(Relation scanrel,
+									 List *partConstraint);
+
 #endif							/* TABLECMDS_H */
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index c82c533..0d400d9 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3304,7 +3304,7 @@ ALTER TABLE list_parted ATTACH PARTITION def_part DEFAULT;
 -- exists
 CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS);
 ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT;
-ERROR:  cannot attach a new partition to table "list_parted" having a default partition
+ERROR:  partition "fail_def_part" conflicts with existing default partition "def_part"
 -- check validation when attaching list partitions
 CREATE TABLE list_parted2 (
 	a int,
@@ -3318,14 +3318,14 @@ ERROR:  partition constraint is violated by some row
 -- should be ok after deleting the bad row
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
--- check partition cannot be attached if default exists
+-- check partition cannot be attached if default has some row for its values
 CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 INSERT INTO list_parted2_def VALUES (11, 'z');
 CREATE TABLE part_3 (LIKE list_parted2);
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
-ERROR:  cannot attach a new partition to table "list_parted2" having a default partition
--- should be ok after detaching a default partition
-ALTER TABLE list_parted2 DETACH PARTITION list_parted2_def;
+ERROR:  updated partition constraint for default partition would be violated by some row
+-- should be ok after deleting the bad row
+DELETE FROM list_parted2_def WHERE a = 11;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
 -- adding constraints that describe the desired partition constraint
 -- (or more restrictive) will help skip the validation scan
@@ -3342,6 +3342,10 @@ ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
 ALTER TABLE part_3_4 ALTER a SET NOT NULL;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
 INFO:  partition constraint for table "part_3_4" is implied by existing constraints
+-- check if default partition scan skipped
+ALTER TABLE list_parted2_def ADD CONSTRAINT check_a CHECK (a IN (5, 6));
+CREATE TABLE part_55_66 PARTITION OF list_parted2 FOR VALUES IN (55, 66);
+INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
 -- check validation when attaching range partitions
 CREATE TABLE range_parted (
 	a int,
@@ -3369,10 +3373,17 @@ ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 2
 INFO:  partition constraint for table "part2" is implied by existing constraints
 -- Create default partition
 CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
--- New partition cannot be attached if a default partition exists
+-- 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:  cannot attach a new partition to table "range_parted" having a default partition
+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 (
@@ -3425,6 +3436,7 @@ ALTER TABLE part_7 ATTACH PARTITION part_7_a_null FOR VALUES IN ('a', null);
 INFO:  partition constraint for table "part_7_a_null" is implied by existing constraints
 ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
 INFO:  partition constraint for table "part_7" is implied by existing constraints
+INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
 -- Same example, but check this time that the constraint correctly detects
 -- violating rows
 ALTER TABLE list_parted2 DETACH PARTITION part_7;
@@ -3438,7 +3450,20 @@ SELECT tableoid::regclass, a, b FROM part_7 order by a;
 (2 rows)
 
 ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
+INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
 ERROR:  partition constraint is violated by some row
+-- check that leaf partitions of default partition are scanned when
+-- attaching a partitioned table.
+ALTER TABLE part_5 DROP CONSTRAINT check_a;
+CREATE TABLE part5_def PARTITION OF part_5 DEFAULT PARTITION BY LIST(a);
+CREATE TABLE part5_def_p1 PARTITION OF part5_def FOR VALUES IN (5);
+INSERT INTO part5_def_p1 VALUES (5, 'y');
+CREATE TABLE part5_p1 (LIKE part_5);
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+ERROR:  updated partition constraint for default partition would be violated by some row
+-- should be ok after deleting the bad row
+DELETE FROM part5_def_p1 WHERE b = 'y';
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
 -- check that the table being attached is not already a partition
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 ERROR:  "part_2" is already a partition
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 4f79612..58c755b 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -470,10 +470,7 @@ LINE 1: ...BLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) T...
 -- check default partition cannot be created more than once
 CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
 CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
-ERROR:  cannot add a new partition to table "list_parted" having a default partition
--- check that no partition can be created after default partition
-CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (3);
-ERROR:  cannot add a new partition to table "list_parted" having a default partition
+ERROR:  partition "fail_default_part" conflicts with existing default partition "part_default"
 -- specified literal can't be cast to the partition column data type
 CREATE TABLE bools (
 	a bool
@@ -565,10 +562,15 @@ CREATE TABLE list_parted2 (
 ) PARTITION BY LIST (a);
 CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
 CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
 ERROR:  partition "fail_part" would overlap partition "part_null_z"
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
 ERROR:  partition "fail_part" would overlap partition "part_ab"
+-- check default partition overlap
+INSERT INTO list_parted2 VALUES('X');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y');
+ERROR:  updated partition constraint for default partition "list_parted2_def" would be violated by some row
 CREATE TABLE range_parted2 (
 	a int
 ) PARTITION BY RANGE (a);
@@ -594,9 +596,14 @@ 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;
--- New partition cannot be created if a default partition exists
+-- 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:  cannot add a new partition to table "range_parted2" having a default partition
+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,
@@ -610,13 +617,12 @@ 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
 CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, minvalue) TO (1, maxvalue);
 ERROR:  partition "fail_part" would overlap partition "part10"
--- check default partition can be added to multi-column range partitioned table
-CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
 -- check schema propagation from parent
 CREATE TABLE parted (
 	a text,
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 48f5292..9872643 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -276,9 +276,6 @@ select tableoid::regclass, * from list_parted;
  part_default_p2    | de | 35
 (9 rows)
 
--- drop default partition, as we need to add some more partitions to test tuple
--- routing
-drop table part_default;
 -- Check tuple routing for partitioned tables
 -- fail
 insert into range_parted values ('a', 0);
@@ -352,7 +349,10 @@ select tableoid::regclass, * from list_parted;
  part_xx_yy_defpart | yy |  2
  part_null          |    |  0
  part_null          |    |  1
-(10 rows)
+ part_default_p1    | cd | 25
+ part_default_p1    | ab | 21
+ part_default_p2    | de | 35
+(13 rows)
 
 -- some more tests to exercise tuple-routing with multi-level partitioning
 create table part_gg partition of list_parted for values in ('gg') partition by range (b);
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
index fb90357..c2eeff1 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -266,6 +266,10 @@ DETAIL:  Failing row contains (null).
 execute pstmt_def_insert(1);
 ERROR:  new row for relation "list_part_def" violates partition constraint
 DETAIL:  Failing row contains (1).
+create table list_part_2 partition of list_parted for values in (2);
+execute pstmt_def_insert(2);
+ERROR:  new row for relation "list_part_def" violates partition constraint
+DETAIL:  Failing row contains (2).
 alter table list_parted detach partition list_part_null;
 -- should be ok
 execute pstmt_def_insert(null);
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index ac6f9d3..37cca72 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2134,13 +2134,13 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 DELETE FROM part_2;
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 
--- check partition cannot be attached if default exists
+-- check partition cannot be attached if default has some row for its values
 CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 INSERT INTO list_parted2_def VALUES (11, 'z');
 CREATE TABLE part_3 (LIKE list_parted2);
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
--- should be ok after detaching a default partition
-ALTER TABLE list_parted2 DETACH PARTITION list_parted2_def;
+-- should be ok after deleting the bad row
+DELETE FROM list_parted2_def WHERE a = 11;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11);
 
 -- adding constraints that describe the desired partition constraint
@@ -2160,6 +2160,9 @@ ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
 ALTER TABLE part_3_4 ALTER a SET NOT NULL;
 ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
 
+-- check if default partition scan skipped
+ALTER TABLE list_parted2_def ADD CONSTRAINT check_a CHECK (a IN (5, 6));
+CREATE TABLE part_55_66 PARTITION OF list_parted2 FOR VALUES IN (55, 66);
 
 -- check validation when attaching range partitions
 CREATE TABLE range_parted (
@@ -2191,10 +2194,18 @@ ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 2
 -- Create default partition
 CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
 
--- New partition cannot be attached if a default partition exists
+-- 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 (
@@ -2255,6 +2266,18 @@ INSERT INTO part_7 (a, b) VALUES (8, null), (9, 'a');
 SELECT tableoid::regclass, a, b FROM part_7 order by a;
 ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
 
+-- check that leaf partitions of default partition are scanned when
+-- attaching a partitioned table.
+ALTER TABLE part_5 DROP CONSTRAINT check_a;
+CREATE TABLE part5_def PARTITION OF part_5 DEFAULT PARTITION BY LIST(a);
+CREATE TABLE part5_def_p1 PARTITION OF part5_def FOR VALUES IN (5);
+INSERT INTO part5_def_p1 VALUES (5, 'y');
+CREATE TABLE part5_p1 (LIKE part_5);
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+-- should be ok after deleting the bad row
+DELETE FROM part5_def_p1 WHERE b = 'y';
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+
 -- check that the table being attached is not already a partition
 ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
 
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 24f005e..eeab5d9 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -450,8 +450,6 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
 -- check default partition cannot be created more than once
 CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
 CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
--- check that no partition can be created after default partition
-CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (3);
 
 -- specified literal can't be cast to the partition column data type
 CREATE TABLE bools (
@@ -530,9 +528,13 @@ CREATE TABLE list_parted2 (
 ) PARTITION BY LIST (a);
 CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
 CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT;
 
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
 CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+-- check default partition overlap
+INSERT INTO list_parted2 VALUES('X');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y');
 
 CREATE TABLE range_parted2 (
 	a int
@@ -555,8 +557,13 @@ 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;
 
--- New partition cannot be created if a default partition exists
+-- 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 (
@@ -571,15 +578,13 @@ 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
 -- more specific ranges
 CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, minvalue) TO (1, maxvalue);
 
--- check default partition can be added to multi-column range partitioned table
-CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
-
 -- check schema propagation from parent
 
 CREATE TABLE parted (
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 7fc5688..0e68480 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -165,9 +165,6 @@ insert into list_parted values ('ab', 21);
 insert into list_parted values ('xx', 1);
 insert into list_parted values ('yy', 2);
 select tableoid::regclass, * from list_parted;
--- drop default partition, as we need to add some more partitions to test tuple
--- routing
-drop table part_default;
 
 -- Check tuple routing for partitioned tables
 
diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql
index a8e357c..cb2a551 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -167,6 +167,8 @@ prepare pstmt_def_insert (int) as insert into list_part_def values($1);
 -- should fail
 execute pstmt_def_insert(null);
 execute pstmt_def_insert(1);
+create table list_part_2 partition of list_parted for values in (2);
+execute pstmt_def_insert(2);
 alter table list_parted detach partition list_part_null;
 -- should be ok
 execute pstmt_def_insert(null);
-- 
2.7.4

0004-Check-default-partitition-child-validation-scan-is.patch0000664000175000017500000001277313154520373023550 0ustar  jeevanjeevanFrom 5d2cba087884aad8f10bae1c77dfaf226d7cb7ec Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Fri, 8 Sep 2017 19:12:17 +0530
Subject: [PATCH 4/5] Check default partitition child validation scan is
 skippable

Add code to check_default_allows_bound() such that the default
partition children constraints are checked against new partition
constraints for implication and avoid scan of the child of which
existing constraints are implied by new default partition
constraints. Also, added testcase for the same.

Jeevan Ladhe.
---
 src/backend/catalog/partition.c           | 18 ++++++++++++++++++
 src/test/regress/expected/alter_table.out | 14 ++++++++++++--
 src/test/regress/sql/alter_table.sql      |  9 +++++++++
 3 files changed, 39 insertions(+), 2 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 0a5bf1f..931117c 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -951,7 +951,25 @@ check_default_allows_bound(Relation parent, Relation default_rel,
 
 		/* Lock already taken above. */
 		if (part_relid != RelationGetRelid(default_rel))
+		{
 			part_rel = heap_open(part_relid, NoLock);
+
+			/*
+			 * If the partition constraints on default partition child imply
+			 * that it will not contain any row that would belong to the new
+			 * partition, we can avoid scanning the child table.
+			 */
+			if (PartConstraintImpliedByRelConstraint(part_rel,
+													 def_part_constraints))
+			{
+				ereport(INFO,
+						(errmsg("partition constraint for table \"%s\" is implied by existing constraints",
+								RelationGetRelationName(part_rel))));
+
+				heap_close(part_rel, NoLock);
+				continue;
+			}
+		}
 		else
 			part_rel = default_rel;
 
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 0d400d9..f1abf9a 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3346,6 +3346,18 @@ INFO:  partition constraint for table "part_3_4" is implied by existing constrai
 ALTER TABLE list_parted2_def ADD CONSTRAINT check_a CHECK (a IN (5, 6));
 CREATE TABLE part_55_66 PARTITION OF list_parted2 FOR VALUES IN (55, 66);
 INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
+-- check if default partition child relation scan skipped
+DROP TABLE list_parted2_def;
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT PARTITION BY RANGE (a);
+CREATE TABLE list_parted2_def_p1 PARTITION OF list_parted2_def FOR VALUES FROM (21) TO (30);
+CREATE TABLE list_parted2_def_p2 PARTITION OF list_parted2_def FOR VALUES FROM (31) TO (40);
+ALTER TABLE list_parted2_def_p1 ADD CONSTRAINT check_a CHECK (a IN (21, 22));
+ALTER TABLE list_parted2_def_p2 ADD CONSTRAINT check_a CHECK (a IN (31, 32));
+CREATE TABLE part_9 PARTITION OF list_parted2 FOR VALUES IN (9);
+INFO:  partition constraint for table "list_parted2_def_p1" is implied by existing constraints
+INFO:  partition constraint for table "list_parted2_def_p2" is implied by existing constraints
+CREATE TABLE part_31 PARTITION OF list_parted2 FOR VALUES IN (31);
+INFO:  partition constraint for table "list_parted2_def_p1" is implied by existing constraints
 -- check validation when attaching range partitions
 CREATE TABLE range_parted (
 	a int,
@@ -3436,7 +3448,6 @@ ALTER TABLE part_7 ATTACH PARTITION part_7_a_null FOR VALUES IN ('a', null);
 INFO:  partition constraint for table "part_7_a_null" is implied by existing constraints
 ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
 INFO:  partition constraint for table "part_7" is implied by existing constraints
-INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
 -- Same example, but check this time that the constraint correctly detects
 -- violating rows
 ALTER TABLE list_parted2 DETACH PARTITION part_7;
@@ -3450,7 +3461,6 @@ SELECT tableoid::regclass, a, b FROM part_7 order by a;
 (2 rows)
 
 ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
-INFO:  partition constraint for table "list_parted2_def" is implied by existing constraints
 ERROR:  partition constraint is violated by some row
 -- check that leaf partitions of default partition are scanned when
 -- attaching a partitioned table.
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 37cca72..3ad6302 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2163,6 +2163,15 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
 -- check if default partition scan skipped
 ALTER TABLE list_parted2_def ADD CONSTRAINT check_a CHECK (a IN (5, 6));
 CREATE TABLE part_55_66 PARTITION OF list_parted2 FOR VALUES IN (55, 66);
+-- check if default partition child relation scan skipped
+DROP TABLE list_parted2_def;
+CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT PARTITION BY RANGE (a);
+CREATE TABLE list_parted2_def_p1 PARTITION OF list_parted2_def FOR VALUES FROM (21) TO (30);
+CREATE TABLE list_parted2_def_p2 PARTITION OF list_parted2_def FOR VALUES FROM (31) TO (40);
+ALTER TABLE list_parted2_def_p1 ADD CONSTRAINT check_a CHECK (a IN (21, 22));
+ALTER TABLE list_parted2_def_p2 ADD CONSTRAINT check_a CHECK (a IN (31, 32));
+CREATE TABLE part_9 PARTITION OF list_parted2 FOR VALUES IN (9);
+CREATE TABLE part_31 PARTITION OF list_parted2 FOR VALUES IN (31);
 
 -- check validation when attaching range partitions
 CREATE TABLE range_parted (
-- 
2.7.4

0005-Documentation-for-default-partition.patch0000664000175000017500000002245313154520373021024 0ustar  jeevanjeevanFrom ed498814d646e5fc70b177ce3e3c6c64030973cb Mon Sep 17 00:00:00 2001
From: Jeevan Ladhe <jeevan.ladhe@enterprisedb.com>
Date: Fri, 8 Sep 2017 19:13:35 +0530
Subject: [PATCH 5/5] Documentation for default partition.

This patch adds documentation for default partition for
CREATE TABLE and ALTER TABLE commands with example.
Also, added documentation for pg_partitioned_table new
field partdefid.

Jeevan Ladhe.
---
 doc/src/sgml/catalogs.sgml         | 11 +++++++++++
 doc/src/sgml/ref/alter_table.sgml  | 35 ++++++++++++++++++++++++++++++++---
 doc/src/sgml/ref/create_table.sgml | 32 +++++++++++++++++++++++++++++---
 3 files changed, 72 insertions(+), 6 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 4f56188..4978b47 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -4739,6 +4739,17 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</>:<replaceable>&lt;salt&gt;<
      </row>
 
      <row>
+      <entry><structfield>partdefid</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+      <entry>
+       The OID of the <structname>pg_class</> entry for the default partition
+       of this partitioned table, or zero if this partitioned table does not
+       have a default partition.
+     </entry>
+     </row>
+
+     <row>
       <entry><structfield>partattrs</structfield></entry>
       <entry><type>int2vector</type></entry>
       <entry><literal><link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.attnum</literal></entry>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index dae6307..f27d487 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -34,7 +34,7 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
 ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable> [ OWNED BY <replaceable class="PARAMETER">role_name</replaceable> [, ... ] ]
     SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable> [ NOWAIT ]
 ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
-    ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+    ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> { FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> | DEFAULT }
 ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
     DETACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable>
 
@@ -765,11 +765,18 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
    </varlistentry>
 
    <varlistentry>
-    <term><literal>ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable></literal></term>
+    <term><literal>ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> { FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> | DEFAULT }</literal></term>
     <listitem>
      <para>
       This form attaches an existing table (which might itself be partitioned)
-      as a partition of the target table using the same syntax for
+      as a partition of the target table. The table can be attached
+      as a partition for specific values using <literal>FOR VALUES
+      </literal> or as a default partition by using <literal>DEFAULT
+      </literal>.
+     </para>
+
+     <para>
+      A partition using <literal>FOR VALUES</literal> uses same syntax for
       <replaceable class="PARAMETER">partition_bound_spec</replaceable> as
       <xref linkend="sql-createtable">.  The partition bound specification
       must correspond to the partitioning strategy and partition key of the
@@ -798,6 +805,10 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       a list partition that will not accept <literal>NULL</literal> values,
       also add <literal>NOT NULL</literal> constraint to the partition key
       column, unless it's an expression.
+      Also, if there exists a default partition table for the parent table,
+      then the default partition (if it is a regular table) is scanned to
+      check that no existing row in default partition would fit in the
+      partition that is being attached.
      </para>
 
      <para>
@@ -806,6 +817,17 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       (See the discussion in <xref linkend="SQL-CREATEFOREIGNTABLE"> about
       constraints on the foreign table.)
      </para>
+
+     <para>
+      When a table has a default partition, defining a new partition changes
+      the partition constraint for the default partition. The default
+      partition can't contain any rows that would need to be moved to the new
+      partition, and will be scanned to verify that none are present. This
+      scan, like the scan of the new partition, can be avoided if an
+      appropriate <literal>CHECK</literal> constraint is present. Also like
+      the scan of the new partition, it is always skipped when the default
+      partition is a foreign table.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -1396,6 +1418,13 @@ ALTER TABLE cities
 </programlisting></para>
 
   <para>
+   Attach a default partition to a partitioned table:
+<programlisting>
+ALTER TABLE cities
+    ATTACH PARTITION cities_partdef DEFAULT;
+</programlisting></para>
+
+  <para>
    Detach a partition from partitioned table:
 <programlisting>
 ALTER TABLE measurement
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index a6ca590..84d43c6 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -49,7 +49,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
   { <replaceable class="PARAMETER">column_name</replaceable> [ WITH OPTIONS ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
     | <replaceable>table_constraint</replaceable> }
     [, ... ]
-) ] FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+) ] { FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> | DEFAULT }
 [ PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
 [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
 [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
@@ -250,11 +250,13 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
    </varlistentry>
 
    <varlistentry id="SQL-CREATETABLE-PARTITION">
-    <term><literal>PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable> FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable></literal></term>
+    <term><literal>PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable> { FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> | DEFAULT }</literal></term>
     <listitem>
      <para>
       Creates the table as a <firstterm>partition</firstterm> of the specified
-      parent table.
+      parent table. The table can be created either as a partition for specific
+      values using <literal>FOR VALUES</literal> or as a default partition
+      using <literal>DEFAULT</literal>.
      </para>
 
      <para>
@@ -343,6 +345,23 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
      </para>
 
      <para>
+     If <literal>DEFAULT</literal> is specified, the table will be
+     created as a default partition of the parent table. The parent can
+     either be a list or range partitioned table. A partition key value
+     not fitting into any other partition of the given parent will be
+     routed to the default partition. There can be only one default
+     partition for a given parent table.
+     </para>
+
+     <para>
+     If the given parent is already having a default partition then
+     adding a new partition would result in an error if the default
+     partition contains a record that would fit in the new partition
+     being added. This check is not performed if the default partition
+     is a foreign table.
+     </para>
+
+     <para>
       A partition must have the same column names and types as the partitioned
       table to which it belongs.  If the parent is specified <literal>WITH
       OIDS</literal> then all partitions must have OIDs; the parent's OID
@@ -1679,6 +1698,13 @@ CREATE TABLE cities_ab
 CREATE TABLE cities_ab_10000_to_100000
     PARTITION OF cities_ab FOR VALUES FROM (10000) TO (100000);
 </programlisting></para>
+
+  <para>
+   Create a default partition of partitioned table:
+<programlisting>
+CREATE TABLE cities_partdef
+    PARTITION OF cities DEFAULT;
+</programlisting></para>
  </refsect1>
 
  <refsect1 id="SQL-CREATETABLE-compatibility">
-- 
2.7.4

#185Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Robert Haas (#183)
Re: Adding support for Default partition in partitioning

On Fri, Sep 8, 2017 at 6:46 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Thu, Sep 7, 2017 at 8:13 AM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

The fix would be much easier if the refactoring patch 0001 by Amul in

hash

partitioning thread[2] is committed.

Done.

Thanks Robert for taking care of this.
My V29 patch series[1]/messages/by-id/CAOgcT0PCM5mJPCOyj3c0D1mLxwaVz6DJLO=nMZ5j-5jgYwX28A@mail.gmail.com is based on this commit now.

[1]: /messages/by-id/CAOgcT0PCM5mJPCOyj3c0D1mLxwaVz6DJLO=nMZ5j-5jgYwX28A@mail.gmail.com
/messages/by-id/CAOgcT0PCM5mJPCOyj3c0D1mLxwaVz6DJLO=nMZ5j-5jgYwX28A@mail.gmail.com

Regards,
Jeevan Ladhe

#186Robert Haas
robertmhaas@gmail.com
In reply to: Jeevan Ladhe (#185)
Re: Adding support for Default partition in partitioning

On Fri, Sep 8, 2017 at 10:08 AM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

Thanks Robert for taking care of this.
My V29 patch series[1] is based on this commit now.

Committed 0001-0003, 0005 with assorted modifications, mostly
cosmetic, but with some actual changes to describeOneTableDetails and
ATExecAttachPartition and minor additions to the regression tests.

--
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

#187Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Robert Haas (#186)
Re: Adding support for Default partition in partitioning

On Sat, Sep 9, 2017 at 3:05 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Fri, Sep 8, 2017 at 10:08 AM, Jeevan Ladhe
<jeevan.ladhe@enterprisedb.com> wrote:

Thanks Robert for taking care of this.
My V29 patch series[1] is based on this commit now.

Committed 0001-0003, 0005 with assorted modifications, mostly
cosmetic, but with some actual changes to describeOneTableDetails and
ATExecAttachPartition and minor additions to the regression tests.

Thanks Robert!!