From 558d1f9514997084de3c4c551bb2738b71c0c23c Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 3 Mar 2016 18:29:15 +0900
Subject: [PATCH 3/5] Add syntax to create partitions.

As follows:

CREATE TABLE name PARTITION OF parent
  [ ( { column_name WITH OPTION column_constraint | table_constraint } [, ...] ) ]
  partition_bound_spec [ PARTITION BY partition_key_spec ]

ALTER TABLE parent ATTACH PARTITION name
  partition_bound_spec [ VALIDATE | NO VALIDATE ]

ALTER TABLE parent DETACH PARTITION name

partition_bound_spec refers to either list partition bound of form:

  FOR VALUES IN (list-of-expressions)

or range partition bound of form:

  FOR VALUES range_spec

where range_spec is of one of:

  START (lower-bound) [ INCLUSIVE | EXCLUSIVE ]
  END   (upper-bound) [ INCLUSIVE | EXCLUSIVE ]
  START (lower-bound) [ INCLUSIVE | EXCLUSIVE ] END (upper-bound) [ INCLUSIVE | EXCLUSIVE ]

In the above, each of lower-bound and upper-bound is either a single
expression for a single column key or list of expressions for a multi-
column key or empty.  Note that in the absence of explicit specification,
lower-bound is inclusive and upper-bound is exclusive.

A partition is created as inheritance child of the parent with certain
differences from regular inheritance behavior.  For example, when a new
partition is created, it cannot contain its own column definitions or
constraints (unlike CREATE TABLE child(...) INHERITS(parent)).  When
"attaching" a table as partition of parent, it cannot contain columns
that are not contained in the parent (unlike ALTER TABLE child INHERIT
parent).  No dependency is created between a partition and the parent.
One cannot create regular inheritance children of a partitioned table.

Whereas this commit enables inheritance, it does not yet store partition
bound info anywhere, IOW, the fact that a given inheritance child is a
"partition" is not persisted.
---
 doc/src/sgml/ref/alter_table.sgml  |   57 +++++
 doc/src/sgml/ref/create_table.sgml |   77 ++++++-
 src/backend/commands/createas.c    |    1 +
 src/backend/commands/sequence.c    |    1 +
 src/backend/commands/tablecmds.c   |  490 +++++++++++++++++++++++++++---------
 src/backend/commands/view.c        |    1 +
 src/backend/nodes/copyfuncs.c      |   48 ++++
 src/backend/nodes/equalfuncs.c     |   42 +++
 src/backend/nodes/outfuncs.c       |   27 ++
 src/backend/parser/gram.y          |  167 ++++++++++++-
 src/backend/parser/parse_agg.c     |    7 +-
 src/backend/parser/parse_expr.c    |   23 ++
 src/backend/parser/parse_utilcmd.c |  318 +++++++++++++++++++++++
 src/include/catalog/partition.h    |    1 +
 src/include/nodes/nodes.h          |    3 +
 src/include/nodes/parsenodes.h     |   43 +++-
 src/include/parser/kwlist.h        |    3 +
 src/include/parser/parse_node.h    |    3 +-
 18 files changed, 1182 insertions(+), 130 deletions(-)

diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 5ca211e..97744c9 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -77,12 +77,15 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     NOT OF
     OWNER TO { <replaceable class="PARAMETER">new_owner</replaceable> | CURRENT_USER | SESSION_USER }
     REPLICA IDENTITY { DEFAULT | USING INDEX <replaceable class="PARAMETER">index_name</replaceable> | FULL | NOTHING }
+    ATTACH PARTITION <replaceable class="PARAMETER">name</replaceable> <replaceable class="PARAMETER">partition_bound_spec</replaceable> [ VALIDATE | NO VALIDATE ]
+    DETACH PARTITION <replaceable class="PARAMETER">name</replaceable>
 
 <phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
 
     [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
     { UNIQUE | PRIMARY KEY } USING INDEX <replaceable class="PARAMETER">index_name</replaceable>
     [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
+
 </synopsis>
  </refsynopsisdiv>
 
@@ -704,6 +707,51 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>ATTACH PARTITION</literal></term>
+    <listitem>
+     <para>
+      This form attaches an existing table as partition of the target table,
+      provided the target table is partitioned. The partition bound
+      specification must correspond with partition method and key of the
+      partitioned table.  The table being attached must have all the columns
+      as the target table and no more.  Also, it must have all the matching
+      constraints as the target table.  That includes <literal>NOT NULL</>
+      and <literal>CHECK</> constraints.  <literal>UNIQUE</literal>,
+      <literal>PRIMARY KEY</literal>, and <literal>FOREIGN KEY</literal>
+      constraints are not considered, but this might change in the future.
+     </para>
+
+     <para>
+      If <literal>NO VALIDATE</literal> clause is specified, existing rows in
+      source table are are not checked to see if they violate the partition
+      bound specification.  This allows to quickly load a large amount of data
+      into the partitioned table.  The default behavior is to perform the
+      check, as if <literal>VALIDATE</literal> clause were specified.
+     </para>
+
+     <para>
+      Note that once the table becomes a partition, it will be dropped or
+      truncated when the parent table is dropped or truncated.  Dropping
+      it directly using <literal>DROP TABLE</literal> will fail; it must
+      first be detached from the parent.  However, truncating a partition
+      directly works.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>DETACH PARTITION</literal></term>
+    <listitem>
+     <para>
+      This form detaches specified partition of the target table, provided the
+      target table is partitioned and the specified partition exists.  The
+      resulting table continues to exist as a standalone table with no ties
+      remaining with the parent partitioned table.
+     </para>
+    </listitem>
+   </varlistentry>
+
   </variablelist>
   </para>
 
@@ -936,6 +984,15 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><replaceable class="PARAMETER">partition_bound_spec</replaceable></term>
+      <listitem>
+       <para>
+        The partition bound specification for a new partition.
+       </para>
+      </listitem>
+     </varlistentry>
+
     </variablelist>
  </refsect1>
 
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 3da79a8..5e6a758 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -44,6 +44,17 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
 [ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
 
+CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable>
+    PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable> [ (
+  { <replaceable class="PARAMETER">column_name</replaceable> WITH OPTIONS [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
+    | <replaceable>table_constraint</replaceable> }
+    [, ... ]
+) ] <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+[ PARTITION BY {RANGE | LIST} ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</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 } ]
+[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
+
 <phrase>where <replaceable class="PARAMETER">column_constraint</replaceable> is:</phrase>
 
 [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
@@ -72,6 +83,10 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 
 { INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES | STORAGE | COMMENTS | ALL }
 
+<phrase>and <replaceable class="PARAMETER">partition_bound_spec</replaceable> is:</phrase>
+
+FOR VALUES { <replaceable class="PARAMETER">list_spec</replaceable> | <replaceable class="PARAMETER">range_spec</replaceable> }
+
 <phrase><replaceable class="PARAMETER">index_parameters</replaceable> in <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and <literal>EXCLUDE</literal> constraints are:</phrase>
 
 [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) ]
@@ -80,8 +95,20 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 <phrase><replaceable class="PARAMETER">exclude_element</replaceable> in an <literal>EXCLUDE</literal> constraint is:</phrase>
 
 { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ]
-</synopsis>
 
+<phrase><replaceable class="PARAMETER">list_spec</replaceable> in <literal>FOR VALUES</literal> is:</phrase>
+
+{ IN ( <replaceable class="PARAMETER">expression</replaceable> [, ...] ) }
+
+<phrase><replaceable class="PARAMETER">range_spec</replaceable> in <literal>FOR VALUES</literal> is:</phrase>
+
+{ START (<replaceable class="PARAMETER">lower-bound</replaceable>) [ INCLUSIVE | EXCLUSIVE ] | END (<replaceable class="PARAMETER">upper-bound</replaceable>) [ INCLUSIVE | EXCLUSIVE ] | START (<replaceable class="PARAMETER">lower-bound</replaceable>) [ INCLUSIVE | EXCLUSIVE ] END (<replaceable class="PARAMETER">upper-bound</replaceable>) [ INCLUSIVE | EXCLUSIVE ] }
+
+<phrase>where <replaceable class="PARAMETER">lower-bound</replaceable> and <replaceable class="PARAMETER">upper-bound</replaceable> are:</phrase>
+
+{ <replaceable class="PARAMETER">expression</replaceable> [, ...] }
+
+</synopsis>
  </refsynopsisdiv>
 
  <refsect1 id="SQL-CREATETABLE-description">
@@ -232,6 +259,49 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
    </varlistentry>
 
    <varlistentry>
+    <term><literal>PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+    <listitem>
+     <para>
+      Creates the table as <firstterm>partition</firstterm> of the specified
+      parent table (name optionally schema-qualified).
+     </para>
+
+     <para>
+      A partition bound specification must be present and must correspond with
+      partition method and key of the parent table.  It is checked using the
+      specification that the new partition does not overlap with any existing
+      partitions of the parent.
+     </para>
+
+     <para>
+      A partition cannot have columns other than those inherited from the
+      parent.  That includes the <structfield>oid</> column, which can be
+      specified using the <literal>WITH (OIDS)</literal> clause.  On the other
+      hand, if parent has the <structfield>oid</> column, the partition
+      inherits the same, overriding the <literal>WITH (OIDS=FALSE)</literal>
+      clause, if any.  Defaults and constraints can optionally be specified
+      for each of the inherited columns, which override those in the parent.
+      One can also specify table constraints, in addition to those inherited
+      from the parent.  Note that all subsequent schema modifications to the
+      parent propagate to partition.
+     </para>
+
+     <para>
+      Any data row subsequently inserted into the parent table is mapped to
+      and stored in the partition, provided partition key of the row falls
+      within the partition bounds.
+     </para>
+
+     <para>
+      A partition is dropped or truncated when the parent table is dropped or
+      truncated.  Dropping it directly using <literal>DROP TABLE</literal>
+      will fail; it must first be <firstterm>detached</> from the parent.
+      However, truncating a partition directly works.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><replaceable class="PARAMETER">column_name</replaceable></term>
     <listitem>
      <para>
@@ -340,6 +410,11 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
      </para>
 
      <para>
+      When specifying for a table being created as partition, one needs to
+      use column names from the parent table as part of the key.
+     </para>
+
+     <para>
       Currently, there are following limitations on definition of partitioned
       tables: one cannot specify any UNIQUE, PRIMARY KEY, EXCLUDE and/or
       FOREIGN KEY constraints.
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index cb7a145..427470c 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -342,6 +342,7 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 		col->is_local = true;
 		col->is_not_null = false;
 		col->is_from_type = false;
+		col->is_for_partition = false;
 		col->storage = 0;
 		col->raw_default = NULL;
 		col->cooked_default = NULL;
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 5ecad82..d039dbd 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -159,6 +159,7 @@ DefineSequence(CreateSeqStmt *seq)
 		coldef->is_local = true;
 		coldef->is_not_null = true;
 		coldef->is_from_type = false;
+		coldef->is_for_partition = false;
 		coldef->storage = 0;
 		coldef->raw_default = NULL;
 		coldef->cooked_default = NULL;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 4976bbe..b7d230c 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -279,7 +279,8 @@ typedef struct
 
 static void truncate_check_rel(Relation rel);
 static List *MergeAttributes(List *schema, List *supers, char relpersistence,
-				List **supOids, List **supconstr, int *supOidCount);
+				List **supOids, List **supconstr, int *supOidCount,
+				bool is_partition);
 static bool MergeCheckConstraint(List *constraints, char *name, Node *expr);
 static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel);
 static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel);
@@ -448,6 +449,11 @@ static void ComputePartitionAttrs(Oid relid, List *partParams,
 static bool find_attr_reference_walker(Node *node,
 								find_attr_reference_context *context);
 static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
+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 ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
 
 
 /* ----------------------------------------------------------------
@@ -590,10 +596,16 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Look up inheritance ancestors and generate relation schema, including
 	 * inherited attributes.
+	 *
+	 * The last parameter implicitly specifies that the table is being created
+	 * as partition and schema consists of columns definitions corresponding
+	 * to non-dropped columns of the parent constructed such that each
+	 * attribute of the table is created as inherited and non-local.
 	 */
 	schema = MergeAttributes(schema, stmt->inhRelations,
 							 stmt->relation->relpersistence,
-							 &inheritOids, &old_constraints, &parentOidCount);
+							 &inheritOids, &old_constraints, &parentOidCount,
+							 stmt->partbound != NULL);
 
 	/*
 	 * Create a tuple descriptor from the relation schema.  Note that this
@@ -1452,7 +1464,8 @@ storage_name(char c)
  */
 static List *
 MergeAttributes(List *schema, List *supers, char relpersistence,
-				List **supOids, List **supconstr, int *supOidCount)
+				List **supOids, List **supconstr, int *supOidCount,
+				bool is_partition)
 {
 	ListCell   *entry;
 	List	   *inhSchema = NIL;
@@ -1497,8 +1510,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 
 			/*
 			 * Typed table column option that does not belong to a column from
-			 * the type.  This works because the columns from the type come
-			 * first in the list.
+			 * the type or the partition parent.  This works because the columns
+			 * from the type or the partition parent come first in the list.
 			 */
 			ereport(ERROR,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
@@ -1525,6 +1538,18 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 					coldef->is_from_type = false;
 					list_delete_cell(schema, rest, prev);
 				}
+				else if (coldef->is_for_partition)
+				{
+					/*
+					 * merge the column options into the column from the parent
+					 */
+					coldef->is_not_null = restdef->is_not_null;
+					coldef->raw_default = restdef->raw_default;
+					coldef->cooked_default = restdef->cooked_default;
+					coldef->constraints = restdef->constraints;
+					coldef->is_for_partition = false;	/* job is done */
+					list_delete_cell(schema, rest, prev);
+				}
 				else
 					ereport(ERROR,
 							(errcode(ERRCODE_DUPLICATE_COLUMN),
@@ -1563,8 +1588,12 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		 */
 		relation = heap_openrv(parent, ShareUpdateExclusiveLock);
 
-		/* Cannot inherit from partitioned tables */
-		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+		/*
+		 * Cannot inherit from a partitioned table unless being created as
+		 * partition.
+		 */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL &&
+			!is_partition)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot inherit from partitioned table \"%s\"",
@@ -1710,6 +1739,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 				def->is_local = false;
 				def->is_not_null = attribute->attnotnull;
 				def->is_from_type = false;
+				def->is_for_partition = false;
 				def->storage = attribute->attstorage;
 				def->raw_default = NULL;
 				def->cooked_default = NULL;
@@ -1838,6 +1868,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	 * If we had no inherited attributes, the result schema is just the
 	 * explicitly declared columns.  Otherwise, we need to merge the declared
 	 * columns into the inherited schema list.
+	 *
+	 * Note: In case of a partition, there are only inherited attributes that
+	 * we copied from the parent as so, in transformPartitionOf().
 	 */
 	if (inhSchema != NIL)
 	{
@@ -1867,16 +1900,20 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 
 				/*
 				 * Yes, try to merge the two column definitions. They must
-				 * have the same type, typmod, and collation.
+				 * have the same type, typmod, and collation.  Don't output
+				 * the notices, if partition, per note above.
 				 */
-				if (exist_attno == schema_attno)
-					ereport(NOTICE,
-					(errmsg("merging column \"%s\" with inherited definition",
-							attributeName)));
-				else
-					ereport(NOTICE,
-							(errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
-							 errdetail("User-specified column moved to the position of the inherited column.")));
+				if (!is_partition)
+				{
+					if (exist_attno == schema_attno)
+						ereport(NOTICE,
+						(errmsg("merging column \"%s\" with inherited definition",
+								attributeName)));
+					else
+						ereport(NOTICE,
+								(errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
+								 errdetail("User-specified column moved to the position of the inherited column.")));
+				}
 				def = (ColumnDef *) list_nth(inhSchema, exist_attno - 1);
 				typenameTypeIdAndMod(NULL, def->typeName, &defTypeId, &deftypmod);
 				typenameTypeIdAndMod(NULL, newdef->typeName, &newTypeId, &newtypmod);
@@ -1913,8 +1950,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 									   storage_name(def->storage),
 									   storage_name(newdef->storage))));
 
-				/* Mark the column as locally defined */
-				def->is_local = true;
+				/* Mark the column as locally defined, unless partition */
+				if (!is_partition)
+					def->is_local = true;
 				/* Merge of NOT NULL constraints = OR 'em together */
 				def->is_not_null |= newdef->is_not_null;
 				/* If new def has a default, override previous default */
@@ -2093,16 +2131,20 @@ StoreCatalogInheritance1(Oid relationId, Oid parentOid,
 	heap_freetuple(tuple);
 
 	/*
-	 * Store a dependency too
+	 * Store a dependency too, iff a regular inheritance relationship, which
+	 * it's not, if parent is a partitioned table.
 	 */
-	parentobject.classId = RelationRelationId;
-	parentobject.objectId = parentOid;
-	parentobject.objectSubId = 0;
-	childobject.classId = RelationRelationId;
-	childobject.objectId = relationId;
-	childobject.objectSubId = 0;
+	if (!relid_is_partitioned(parentOid))
+	{
+		parentobject.classId = RelationRelationId;
+		parentobject.objectId = parentOid;
+		parentobject.objectSubId = 0;
+		childobject.classId = RelationRelationId;
+		childobject.objectId = relationId;
+		childobject.objectSubId = 0;
 
-	recordDependencyOn(&childobject, &parentobject, DEPENDENCY_NORMAL);
+		recordDependencyOn(&childobject, &parentobject, DEPENDENCY_NORMAL);
+	}
 
 	/*
 	 * Post creation hook of this inheritance. Since object_access_hook
@@ -3108,6 +3150,11 @@ AlterTableGetLockLevel(List *cmds)
 				cmd_lockmode = AlterTableGetRelOptionsLockLevel((List *) cmd->def);
 				break;
 
+			case AT_AttachPartition:
+			case AT_DetachPartition:
+				cmd_lockmode = AccessExclusiveLock;
+				break;
+
 			default:			/* oops */
 				elog(ERROR, "unrecognized alter table type: %d",
 					 (int) cmd->subtype);
@@ -3425,6 +3472,12 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 			/* No command-specific prep needed */
 			pass = AT_PASS_MISC;
 			break;
+		case AT_AttachPartition:
+		case AT_DetachPartition:
+			ATSimplePermissions(rel, ATT_TABLE);
+			/* No command-specific prep needed */
+			pass = AT_PASS_MISC;
+			break;
 		default:				/* oops */
 			elog(ERROR, "unrecognized alter table type: %d",
 				 (int) cmd->subtype);
@@ -3746,6 +3799,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		case AT_GenericOptions:
 			ATExecGenericOptions(rel, (List *) cmd->def);
 			break;
+		case AT_AttachPartition:
+			ATExecAttachPartition(wqueue, rel, (PartitionCmd *) cmd->def);
+			break;
+		case AT_DetachPartition:
+			ATExecDetachPartition(rel, ((PartitionCmd *) cmd->def)->name);
+			break;
 		default:				/* oops */
 			elog(ERROR, "unrecognized alter table type: %d",
 				 (int) cmd->subtype);
@@ -10190,12 +10249,7 @@ ATPrepAddInherit(Relation child_rel)
 static ObjectAddress
 ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 {
-	Relation	parent_rel,
-				catalogRelation;
-	SysScanDesc scan;
-	ScanKeyData key;
-	HeapTuple	inheritsTuple;
-	int32		inhseqno;
+	Relation	parent_rel;
 	List	   *children;
 	ObjectAddress address;
 
@@ -10240,38 +10294,6 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 				errmsg("cannot inherit from partitioned table \"%s\"", parent->relname)));
 
 	/*
-	 * Check for duplicates in the list of parents, and determine the highest
-	 * inhseqno already present; we'll use the next one for the new parent.
-	 * (Note: get RowExclusiveLock because we will write pg_inherits below.)
-	 *
-	 * Note: we do not reject the case where the child already inherits from
-	 * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
-	 */
-	catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
-	ScanKeyInit(&key,
-				Anum_pg_inherits_inhrelid,
-				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(RelationGetRelid(child_rel)));
-	scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
-							  true, NULL, 1, &key);
-
-	/* inhseqno sequences start at 1 */
-	inhseqno = 0;
-	while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
-	{
-		Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
-
-		if (inh->inhparent == RelationGetRelid(parent_rel))
-			ereport(ERROR,
-					(errcode(ERRCODE_DUPLICATE_TABLE),
-			 errmsg("relation \"%s\" would be inherited from more than once",
-					RelationGetRelationName(parent_rel))));
-		if (inh->inhseqno > inhseqno)
-			inhseqno = inh->inhseqno;
-	}
-	systable_endscan(scan);
-
-	/*
 	 * Prevent circularity by seeing if proposed parent inherits from child.
 	 * (In particular, this disallows making a rel inherit from itself.)
 	 *
@@ -10304,6 +10326,69 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 						RelationGetRelationName(child_rel),
 						RelationGetRelationName(parent_rel))));
 
+	/* OK to create inheritance */
+	CreateInheritance(child_rel, parent_rel);
+
+	ObjectAddressSet(address, RelationRelationId,
+					 RelationGetRelid(parent_rel));
+
+	/* keep our lock on the parent relation until commit */
+	heap_close(parent_rel, NoLock);
+
+	return address;
+}
+
+/*
+ * CreateInheritance
+ *		Catalog manipulation portion of creating inheritance between a child
+ *		table and a parent table.
+ *
+ * Common to ATExecAddInherit() and ATExecAttachPartition().
+ */
+static void
+CreateInheritance(Relation child_rel, Relation parent_rel)
+{
+	Relation	catalogRelation;
+	SysScanDesc scan;
+	ScanKeyData key;
+	HeapTuple	inheritsTuple;
+	int32		inhseqno;
+
+	/* Note: get RowExclusiveLock because we will write pg_inherits below. */
+	catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
+
+	/*
+	 * Check for duplicates in the list of parents, and determine the highest
+	 * inhseqno already present; we'll use the next one for the new parent.
+	 * Also, if proposed child is a partition, it cannot already be inheriting.
+	 *
+	 * Note: we do not reject the case where the child already inherits from
+	 * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
+	 */
+	ScanKeyInit(&key,
+				Anum_pg_inherits_inhrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(child_rel)));
+	scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
+							  true, NULL, 1, &key);
+
+	/* inhseqno sequences start at 1 */
+	inhseqno = 0;
+	while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
+	{
+		Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
+
+		if (inh->inhparent == RelationGetRelid(parent_rel))
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_TABLE),
+			 errmsg("relation \"%s\" would be inherited from more than once",
+					RelationGetRelationName(parent_rel))));
+
+		if (inh->inhseqno > inhseqno)
+			inhseqno = inh->inhseqno;
+	}
+	systable_endscan(scan);
+
 	/* Match up the columns and bump attinhcount as needed */
 	MergeAttributesIntoExisting(child_rel, parent_rel);
 
@@ -10318,16 +10403,8 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 							 inhseqno + 1,
 							 catalogRelation);
 
-	ObjectAddressSet(address, RelationRelationId,
-					 RelationGetRelid(parent_rel));
-
 	/* Now we're done with pg_inherits */
 	heap_close(catalogRelation, RowExclusiveLock);
-
-	/* keep our lock on the parent relation until commit */
-	heap_close(parent_rel, NoLock);
-
-	return address;
 }
 
 /*
@@ -10378,7 +10455,7 @@ constraints_equivalent(HeapTuple a, HeapTuple b, TupleDesc tupleDesc)
  * Check columns in child table match up with columns in parent, and increment
  * their attinhcount.
  *
- * Called by ATExecAddInherit
+ * Called by CreateInheritance
  *
  * Currently all parent columns must be found in child. Missing columns are an
  * error.  One day we might consider creating new columns like CREATE TABLE
@@ -10396,11 +10473,13 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 	int			parent_natts;
 	TupleDesc	tupleDesc;
 	HeapTuple	tuple;
+	bool		is_attach_partition;
 
 	attrrel = heap_open(AttributeRelationId, RowExclusiveLock);
 
 	tupleDesc = RelationGetDescr(parent_rel);
 	parent_natts = tupleDesc->natts;
+	is_attach_partition = relid_is_partitioned(RelationGetRelid(parent_rel));
 
 	for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++)
 	{
@@ -10423,14 +10502,16 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 				attribute->atttypmod != childatt->atttypmod)
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("child table \"%s\" has different type for column \"%s\"",
+						 errmsg("%s table \"%s\" has different type for column \"%s\"",
+								is_attach_partition ? "source" : "child",
 								RelationGetRelationName(child_rel),
 								attributeName)));
 
 			if (attribute->attcollation != childatt->attcollation)
 				ereport(ERROR,
 						(errcode(ERRCODE_COLLATION_MISMATCH),
-						 errmsg("child table \"%s\" has different collation for column \"%s\"",
+						 errmsg("%s table \"%s\" has different collation for column \"%s\"",
+								is_attach_partition ? "source" : "child",
 								RelationGetRelationName(child_rel),
 								attributeName)));
 
@@ -10441,8 +10522,9 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 			if (attribute->attnotnull && !childatt->attnotnull)
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
-				errmsg("column \"%s\" in child table must be marked NOT NULL",
-					   attributeName)));
+				errmsg("column \"%s\" in %s table must be marked NOT NULL",
+					   attributeName,
+					   is_attach_partition ? "source" : "child")));
 
 			/*
 			 * OK, bump the child column's inheritance count.  (If we fail
@@ -10457,7 +10539,8 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 		{
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("child table is missing column \"%s\"",
+					 errmsg("%s table is missing column \"%s\"",
+							is_attach_partition ? "source" : "child",
 							attributeName)));
 		}
 	}
@@ -10471,7 +10554,7 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
  *
  * Constraints that are marked ONLY in the parent are ignored.
  *
- * Called by ATExecAddInherit
+ * Called by CreateInheritance
  *
  * Currently all constraints in parent must be present in the child. One day we
  * may consider adding new constraints like CREATE TABLE does.
@@ -10490,9 +10573,11 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 	SysScanDesc parent_scan;
 	ScanKeyData parent_key;
 	HeapTuple	parent_tuple;
+	bool		is_attach_partition;
 
 	catalog_relation = heap_open(ConstraintRelationId, RowExclusiveLock);
 	tuple_desc = RelationGetDescr(catalog_relation);
+	is_attach_partition = relid_is_partitioned(RelationGetRelid(parent_rel));
 
 	/* Outer loop scans through the parent's constraint definitions */
 	ScanKeyInit(&parent_key,
@@ -10540,7 +10625,8 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 			if (!constraints_equivalent(parent_tuple, child_tuple, tuple_desc))
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("child table \"%s\" has different definition for check constraint \"%s\"",
+						 errmsg("%s table \"%s\" has different definition for check constraint \"%s\"",
+								is_attach_partition ? "source" : "child",
 								RelationGetRelationName(child_rel),
 								NameStr(parent_con->conname))));
 
@@ -10548,8 +10634,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 			if (child_con->connoinherit)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-						 errmsg("constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
+						 errmsg("constraint \"%s\" conflicts with non-inherited constraint on %s table \"%s\"",
 								NameStr(child_con->conname),
+								is_attach_partition ? "source" : "child",
 								RelationGetRelationName(child_rel))));
 
 			/*
@@ -10572,7 +10659,8 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 		if (!found)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("child table is missing constraint \"%s\"",
+					 errmsg("%s table is missing constraint \"%s\"",
+							is_attach_partition ? "source" : "child",
 							NameStr(parent_con->conname))));
 	}
 
@@ -10583,6 +10671,41 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 /*
  * ALTER TABLE NO INHERIT
  *
+ * Return value is the address of the relation that is no longer parent.
+ */
+static ObjectAddress
+ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+{
+	ObjectAddress	address;
+	Relation		parent_rel;
+
+	/*
+	 * AccessShareLock on the parent is probably enough, seeing that DROP
+	 * TABLE doesn't lock parent tables at all.  We need some lock since we'll
+	 * be inspecting the parent's schema.
+	 */
+	parent_rel = heap_openrv(parent, AccessShareLock);
+
+	/*
+	 * We don't bother to check ownership of the parent table --- ownership of
+	 * the child is presumed enough rights.
+	 */
+
+	/* Off to RemoveInheritance() where most of the work happens */
+	RemoveInheritance(rel, parent_rel);
+
+	/* keep our lock on the parent relation until commit */
+	heap_close(parent_rel, NoLock);
+
+	ObjectAddressSet(address, RelationRelationId,
+					 RelationGetRelid(parent_rel));
+
+	return address;
+}
+
+/*
+ * RemoveInheritance
+ *
  * Drop a parent from the child's parents. This just adjusts the attinhcount
  * and attislocal of the columns and removes the pg_inherit and pg_depend
  * entries.
@@ -10596,13 +10719,11 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
  * coninhcount and conislocal for inherited constraints are adjusted in
  * exactly the same way.
  *
- * Return value is the address of the relation that is no longer parent.
+ * Common to ATExecDropInherit() and ATExecDetachPartition().
  */
-static ObjectAddress
-ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+static void
+RemoveInheritance(Relation child_rel, Relation parent_rel)
 {
-	Relation	parent_rel;
-	Oid			parent_oid;
 	Relation	catalogRelation;
 	SysScanDesc scan;
 	ScanKeyData key[3];
@@ -10611,19 +10732,9 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 				constraintTuple;
 	List	   *connames;
 	bool		found = false;
-	ObjectAddress address;
+	bool		is_detach_partition;
 
-	/*
-	 * AccessShareLock on the parent is probably enough, seeing that DROP
-	 * TABLE doesn't lock parent tables at all.  We need some lock since we'll
-	 * be inspecting the parent's schema.
-	 */
-	parent_rel = heap_openrv(parent, AccessShareLock);
-
-	/*
-	 * We don't bother to check ownership of the parent table --- ownership of
-	 * the child is presumed enough rights.
-	 */
+	is_detach_partition = relid_is_partitioned(RelationGetRelid(parent_rel));
 
 	/*
 	 * Find and destroy the pg_inherits entry linking the two, or error out if
@@ -10633,7 +10744,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 	ScanKeyInit(&key[0],
 				Anum_pg_inherits_inhrelid,
 				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(RelationGetRelid(rel)));
+				ObjectIdGetDatum(RelationGetRelid(child_rel)));
 	scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
 							  true, NULL, 1, key);
 
@@ -10654,11 +10765,20 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 	heap_close(catalogRelation, RowExclusiveLock);
 
 	if (!found)
-		ereport(ERROR,
-				(errcode(ERRCODE_UNDEFINED_TABLE),
-				 errmsg("relation \"%s\" is not a parent of relation \"%s\"",
-						RelationGetRelationName(parent_rel),
-						RelationGetRelationName(rel))));
+	{
+		if (!is_detach_partition)
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_TABLE),
+					 errmsg("relation \"%s\" is not a parent of relation \"%s\"",
+							RelationGetRelationName(parent_rel),
+							RelationGetRelationName(child_rel))));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_TABLE),
+					 errmsg("relation \"%s\" is not a partition of relation \"%s\"",
+							RelationGetRelationName(child_rel),
+							RelationGetRelationName(parent_rel))));
+	}
 
 	/*
 	 * Search through child columns looking for ones matching parent rel
@@ -10667,7 +10787,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 	ScanKeyInit(&key[0],
 				Anum_pg_attribute_attrelid,
 				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(RelationGetRelid(rel)));
+				ObjectIdGetDatum(RelationGetRelid(child_rel)));
 	scan = systable_beginscan(catalogRelation, AttributeRelidNumIndexId,
 							  true, NULL, 1, key);
 	while (HeapTupleIsValid(attributeTuple = systable_getnext(scan)))
@@ -10729,7 +10849,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 	ScanKeyInit(&key[0],
 				Anum_pg_constraint_conrelid,
 				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(RelationGetRelid(rel)));
+				ObjectIdGetDatum(RelationGetRelid(child_rel)));
 	scan = systable_beginscan(catalogRelation, ConstraintRelidIndexId,
 							  true, NULL, 1, key);
 
@@ -10760,7 +10880,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 
 			if (copy_con->coninhcount <= 0)		/* shouldn't happen */
 				elog(ERROR, "relation %u has non-inherited constraint \"%s\"",
-					 RelationGetRelid(rel), NameStr(copy_con->conname));
+					 RelationGetRelid(child_rel), NameStr(copy_con->conname));
 
 			copy_con->coninhcount--;
 			if (copy_con->coninhcount == 0)
@@ -10772,30 +10892,22 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 		}
 	}
 
-	parent_oid = RelationGetRelid(parent_rel);
-
 	systable_endscan(scan);
 	heap_close(catalogRelation, RowExclusiveLock);
 
-	drop_parent_dependency(RelationGetRelid(rel),
-						   RelationRelationId,
-						   RelationGetRelid(parent_rel));
-
+	/* Remember that dependency exists iff regular inheritance. */
+	if (!is_detach_partition)
+		drop_parent_dependency(RelationGetRelid(child_rel),
+							   RelationRelationId,
+							   RelationGetRelid(parent_rel));
 	/*
 	 * Post alter hook of this inherits. Since object_access_hook doesn't take
 	 * multiple object identifiers, we relay oid of parent relation using
 	 * auxiliary_id argument.
 	 */
 	InvokeObjectPostAlterHookArg(InheritsRelationId,
-								 RelationGetRelid(rel), 0,
+								 RelationGetRelid(child_rel), 0,
 								 RelationGetRelid(parent_rel), false);
-
-	/* keep our lock on the parent relation until commit */
-	heap_close(parent_rel, NoLock);
-
-	ObjectAddressSet(address, RelationRelationId, parent_oid);
-
-	return address;
 }
 
 /*
@@ -12418,3 +12530,137 @@ ComputePartitionAttrs(Oid relid, List *partParams, AttrNumber *partattrs,
 		partopclass[attn++] = opclassOid;
 	}
 }
+
+
+/*
+ * ALTER TABLE <name> ATTACH PARTITION <partition-name> FOR VALUES
+ *
+ * Return the address of the newly attached partition.
+ */
+static ObjectAddress
+ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
+{
+	Relation	attachRel,
+				inheritsRelation;
+	SysScanDesc scan;
+	ScanKeyData key;
+	HeapTuple	tuple;
+	AttrNumber	attno;
+	int			natts;
+	TupleDesc	tupleDesc;
+	ObjectAddress address;
+
+	attachRel = heap_openrv(cmd->name, AccessShareLock);
+
+	/*
+	 * Must be owner of both parent and source table -- parent was checked by
+	 * ATSimplePermissions call in ATPrepCmd
+	 */
+	ATSimplePermissions(attachRel, ATT_TABLE);
+
+	if (attachRel->rd_rel->reloftype)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach a typed table as partition")));
+
+	if (attachRel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach a partitioned table as partition")));
+
+	/* attachRel should not already be a inheritance child of some relation */
+	inheritsRelation = heap_open(InheritsRelationId, AccessShareLock);
+	ScanKeyInit(&key,
+				Anum_pg_inherits_inhrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(attachRel)));
+	scan = systable_beginscan(inheritsRelation, InheritsRelidSeqnoIndexId,
+							  true, NULL, 1, &key);
+	if (HeapTupleIsValid(systable_getnext(scan)))
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach table that inherits as partition")));
+	systable_endscan(scan);
+	heap_close(inheritsRelation, AccessShareLock);
+
+	/* If attachRel is temp, it must belong to this session */
+	if (attachRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+		!attachRel->rd_islocaltemp)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+		errmsg("cannot attach a temporary relation of another session as partition ")));
+
+	/* If parent has OIDs, attachRel must have OIDs */
+	if (rel->rd_rel->relhasoids && !attachRel->rd_rel->relhasoids)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach table \"%s\" without OIDs as partition of"
+						" table \"%s\" with OIDs", RelationGetRelationName(attachRel),
+						RelationGetRelationName(rel))));
+
+	/* OTOH, if parent doesn't have them, do not allow in attachRel either */
+	if (attachRel->rd_rel->relhasoids && !rel->rd_rel->relhasoids)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach table \"%s\" with OIDs as partition of table"
+						" \"%s\" without OIDs", RelationGetRelationName(attachRel),
+						RelationGetRelationName(rel))));
+
+	/* Check if there are any columns in attachRel that aren't in the parent */
+	tupleDesc = RelationGetDescr(attachRel);
+	natts = tupleDesc->natts;
+	for (attno = 1; attno <= natts; attno++)
+	{
+		Form_pg_attribute attribute = tupleDesc->attrs[attno - 1];
+		char	   *attributeName = NameStr(attribute->attname);
+
+		/* Ignore dropped  */
+		if (attribute->attisdropped)
+			continue;
+
+		/* Find same column in parent (matching on column name). */
+		tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), attributeName);
+		if (!HeapTupleIsValid(tuple))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("table \"%s\" contains column \"%s\" not found in parent \"%s\"",
+							RelationGetRelationName(attachRel), attributeName,
+							RelationGetRelationName(rel)),
+					 errdetail("Table being attached should contain only the columns"
+							   " present in parent.")));
+	}
+
+	/* OK to create inheritance.  Rest of the checks performed there */
+	CreateInheritance(attachRel, rel);
+
+	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
+
+	/* keep our lock until commit */
+	heap_close(attachRel, NoLock);
+
+	return address;
+}
+
+/*
+ * ALTER TABLE DETACH PARTITION
+ *
+ * Return the address of the relation that is no longer a partition of rel.
+ */
+static ObjectAddress
+ATExecDetachPartition(Relation rel, RangeVar *name)
+{
+	Relation	partRel;
+	ObjectAddress address;
+
+	partRel = heap_openrv(name, AccessShareLock);
+
+	/* All inheritance related checks are performed within the function */
+	RemoveInheritance(partRel, rel);
+
+	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
+
+	/* keep our lock until commit */
+	heap_close(partRel, NoLock);
+
+	return address;
+}
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index e9d9ba2..c9c72d9 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -95,6 +95,7 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
 			def->is_local = true;
 			def->is_not_null = false;
 			def->is_from_type = false;
+			def->is_for_partition = false;
 			def->storage = 0;
 			def->raw_default = NULL;
 			def->cooked_default = NULL;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 5f4f66f..e924ddb 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2604,6 +2604,7 @@ _copyColumnDef(const ColumnDef *from)
 	COPY_SCALAR_FIELD(is_local);
 	COPY_SCALAR_FIELD(is_not_null);
 	COPY_SCALAR_FIELD(is_from_type);
+	COPY_SCALAR_FIELD(is_for_partition);
 	COPY_SCALAR_FIELD(storage);
 	COPY_NODE_FIELD(raw_default);
 	COPY_NODE_FIELD(cooked_default);
@@ -2999,6 +3000,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_NODE_FIELD(relation);
 	COPY_NODE_FIELD(tableElts);
 	COPY_NODE_FIELD(inhRelations);
+	COPY_NODE_FIELD(partbound);
 	COPY_NODE_FIELD(partby);
 	COPY_NODE_FIELD(ofTypename);
 	COPY_NODE_FIELD(constraints);
@@ -4181,6 +4183,43 @@ _copyPartitionElem(const PartitionElem *from)
 	return newnode;
 }
 
+static PartitionListSpec *
+_copyPartitionListSpec(const PartitionListSpec *from)
+{
+	PartitionListSpec *newnode = makeNode(PartitionListSpec);
+
+	COPY_NODE_FIELD(values);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static PartitionRangeSpec *
+_copyPartitionRangeSpec(const PartitionRangeSpec *from)
+{
+	PartitionRangeSpec *newnode = makeNode(PartitionRangeSpec);
+
+	COPY_SCALAR_FIELD(lowerinc);
+	COPY_NODE_FIELD(lower);
+	COPY_SCALAR_FIELD(upperinc);
+	COPY_NODE_FIELD(upper);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static PartitionCmd *
+_copyPartitionCmd(const PartitionCmd *from)
+{
+	PartitionCmd *newnode = makeNode(PartitionCmd);
+
+	COPY_NODE_FIELD(name);
+	COPY_NODE_FIELD(bound);
+	COPY_SCALAR_FIELD(skip_validate);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					pg_list.h copy functions
  * ****************************************************************
@@ -5080,6 +5119,15 @@ copyObject(const void *from)
 		case T_PartitionElem:
 			retval = _copyPartitionElem(from);
 			break;
+		case T_PartitionListSpec:
+			retval = _copyPartitionListSpec(from);
+			break;
+		case T_PartitionRangeSpec:
+			retval = _copyPartitionRangeSpec(from);
+			break;
+		case T_PartitionCmd:
+			retval = _copyPartitionCmd(from);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(from));
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 001a468..51b1e86 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1154,6 +1154,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_NODE_FIELD(relation);
 	COMPARE_NODE_FIELD(tableElts);
 	COMPARE_NODE_FIELD(inhRelations);
+	COMPARE_NODE_FIELD(partbound);
 	COMPARE_NODE_FIELD(partby);
 	COMPARE_NODE_FIELD(ofTypename);
 	COMPARE_NODE_FIELD(constraints);
@@ -2361,6 +2362,7 @@ _equalColumnDef(const ColumnDef *a, const ColumnDef *b)
 	COMPARE_SCALAR_FIELD(is_local);
 	COMPARE_SCALAR_FIELD(is_not_null);
 	COMPARE_SCALAR_FIELD(is_from_type);
+	COMPARE_SCALAR_FIELD(is_for_partition);
 	COMPARE_SCALAR_FIELD(storage);
 	COMPARE_NODE_FIELD(raw_default);
 	COMPARE_NODE_FIELD(cooked_default);
@@ -2639,6 +2641,37 @@ _equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
 	return true;
 }
 
+static bool
+_equalPartitionListSpec(const PartitionListSpec *a, const PartitionListSpec *b)
+{
+	COMPARE_NODE_FIELD(values);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalPartitionRangeSpec(const PartitionRangeSpec *a, const PartitionRangeSpec *b)
+{
+	COMPARE_SCALAR_FIELD(lowerinc);
+	COMPARE_NODE_FIELD(lower);
+	COMPARE_SCALAR_FIELD(upperinc);
+	COMPARE_NODE_FIELD(upper);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
+{
+	COMPARE_NODE_FIELD(name);
+	COMPARE_NODE_FIELD(bound);
+	COMPARE_SCALAR_FIELD(skip_validate);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3395,6 +3428,15 @@ equal(const void *a, const void *b)
 		case T_PartitionElem:
 			retval = _equalPartitionElem(a, b);
 			break;
+		case T_PartitionListSpec:
+			retval = _equalPartitionListSpec(a, b);
+			break;
+		case T_PartitionRangeSpec:
+			retval = _equalPartitionRangeSpec(a, b);
+			break;
+		case T_PartitionCmd:
+			retval = _equalPartitionCmd(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 4e70a69..b531af3 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2367,6 +2367,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_NODE_FIELD(relation);
 	WRITE_NODE_FIELD(tableElts);
 	WRITE_NODE_FIELD(inhRelations);
+	WRITE_NODE_FIELD(partbound);
 	WRITE_NODE_FIELD(partby);
 	WRITE_NODE_FIELD(ofTypename);
 	WRITE_NODE_FIELD(constraints);
@@ -2547,6 +2548,7 @@ _outColumnDef(StringInfo str, const ColumnDef *node)
 	WRITE_BOOL_FIELD(is_local);
 	WRITE_BOOL_FIELD(is_not_null);
 	WRITE_BOOL_FIELD(is_from_type);
+	WRITE_BOOL_FIELD(is_for_partition);
 	WRITE_CHAR_FIELD(storage);
 	WRITE_NODE_FIELD(raw_default);
 	WRITE_NODE_FIELD(cooked_default);
@@ -3237,6 +3239,25 @@ _outPartitionElem(StringInfo str, const PartitionElem *node)
 	WRITE_LOCATION_FIELD(location);
 }
 
+static void
+_outPartitionListSpec(StringInfo str, const PartitionListSpec *node)
+{
+	WRITE_NODE_TYPE("PARTITIONLISTVALUES");
+
+	WRITE_NODE_FIELD(values);
+}
+
+static void
+_outPartitionRangeSpec(StringInfo str, const PartitionRangeSpec *node)
+{
+	WRITE_NODE_TYPE("PARTITIONRANGE");
+
+	WRITE_BOOL_FIELD(lowerinc);
+	WRITE_NODE_FIELD(lower);
+	WRITE_BOOL_FIELD(upperinc);
+	WRITE_NODE_FIELD(upper);
+}
+
 /*
  * outNode -
  *	  converts a Node into ascii string and append it to 'str'
@@ -3821,6 +3842,12 @@ outNode(StringInfo str, const void *obj)
 			case T_PartitionElem:
 				_outPartitionElem(str, obj);
 				break;
+			case T_PartitionListSpec:
+				_outPartitionListSpec(str, obj);
+				break;
+			case T_PartitionRangeSpec:
+				_outPartitionRangeSpec(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0607439..79fbb32 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	VariableSetStmt		*vsetstmt;
 	PartitionElem		*partelem;
 	PartitionBy			*partby;
+	PartitionRangeSpec  *partrange;
 }
 
 %type <node>	stmt schema_stmt
@@ -544,6 +545,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <partby>		PartitionBy OptPartitionBy
 %type <partelem>	part_elem
 %type <list>		part_params
+%type <list>		OptPartitionElementList PartitionElementList
+%type <node>		PartitionElement
+%type <node>		ForValues
+%type <node>		partvalue
+%type <list>		partvalue_list
+%type <boolean>		lb_inc ub_inc
+%type <list>		RangeBound
+%type <boolean>		opt_validate_spec
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -569,7 +578,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 /* ordinary key words in alphabetical order */
 %token <keyword> ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
 	AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
-	ASSERTION ASSIGNMENT ASYMMETRIC AT ATTRIBUTE AUTHORIZATION
+	ASSERTION ASSIGNMENT ASYMMETRIC AT ATTACH ATTRIBUTE AUTHORIZATION
 
 	BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT
 	BOOLEAN_P BOTH BY
@@ -585,7 +594,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 	DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
 	DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DESC
-	DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP
+	DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
+	DOUBLE_P DROP
 
 	EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
 	EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
@@ -599,7 +609,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	HANDLER HAVING HEADER_P HOLD HOUR_P
 
 	IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P
-	INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
+	INCLUDING INCLUSIVE INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
 	INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
 	INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
 
@@ -2373,6 +2383,38 @@ alter_table_cmd:
 					n->def = (Node *)$1;
 					$$ = (Node *) n;
 				}
+			/* ALTER TABLE <name> ATTACH PARTITION <table_name> FOR VALUES */
+			| ATTACH PARTITION qualified_name ForValues opt_validate_spec
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					PartitionCmd *cmd = makeNode(PartitionCmd);
+
+					n->subtype = AT_AttachPartition;
+					cmd->name = $3;
+					cmd->bound = (Node *) $4;
+					cmd->skip_validate = $5;
+					n->def = (Node *) cmd;
+
+					$$ = (Node *) n;
+				}
+			/* ALTER TABLE <name> DETACH PARTITION <partition_name> */
+			| DETACH PARTITION qualified_name
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					PartitionCmd *cmd = makeNode(PartitionCmd);
+
+					n->subtype = AT_DetachPartition;
+					cmd->name = $3;
+					n->def = (Node *) cmd;
+
+					$$ = (Node *) n;
+				}
+		;
+
+opt_validate_spec:
+			NO VALIDATE			{ $$ = true; }
+			| VALIDATE			{ $$ = false; }
+			| /* EMPTY */		{ $$ = false; }
 		;
 
 alter_column_default:
@@ -2468,6 +2510,60 @@ reloption_elem:
 				}
 		;
 
+ForValues:
+			/* a LIST partition */
+			FOR VALUES IN_P '(' partvalue_list ')'
+				{
+					PartitionListSpec *n = makeNode(PartitionListSpec);
+
+					n->values = $5;
+					n->location = @1;
+
+					$$ = (Node *) n;
+				}
+
+			/* a RANGE partition */
+			| FOR VALUES START RangeBound lb_inc END_P RangeBound ub_inc
+				{
+					PartitionRangeSpec *n = makeNode(PartitionRangeSpec);
+
+					n->lowerinc = $5;
+					n->lower = $4;
+					n->upperinc = $8;
+					n->upper = $7;
+					n->location = @1;
+
+					$$ = (Node *) n;
+				}
+		;
+
+RangeBound:
+			UNBOUNDED					{ $$ = NIL; }
+			| '(' partvalue_list ')'	{ $$ = $2; }
+		;
+
+lb_inc:
+			EXCLUSIVE		{ $$ = false;}
+			| INCLUSIVE		{ $$ = true; }
+			| /* EMPTY */	{ $$ = true; }
+		;
+
+ub_inc:
+			INCLUSIVE		{ $$ = true; }
+			| EXCLUSIVE		{ $$ = false; }
+			| /* EMPTY */	{ $$ = false; }
+		;
+
+partvalue:
+			Sconst			{ $$ = makeStringConst($1, @1); }
+			| NumericOnly	{ $$ = makeAConst($1, @1); }
+			| NULL_P		{ $$ = makeNullAConst(@1); }
+		;
+
+partvalue_list:
+			partvalue						{ $$ = list_make1($1); }
+			| partvalue_list ',' partvalue	{ $$ = lappend($1, $3); }
+		;
 
 /*****************************************************************************
  *
@@ -2885,6 +2981,44 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
+		| CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
+			OptPartitionElementList ForValues OptPartitionBy OptWith
+			OnCommitOption OptTableSpace
+				{
+					CreateStmt *n = makeNode(CreateStmt);
+					$4->relpersistence = $2;
+					n->relation = $4;
+					n->tableElts = $8;
+					n->inhRelations = list_make1($7);
+					n->partbound = (Node *) $9;
+					n->partby = $10;
+					n->ofTypename = NULL;
+					n->constraints = NIL;
+					n->options = $11;
+					n->oncommit = $12;
+					n->tablespacename = $13;
+					n->if_not_exists = false;
+					$$ = (Node *)n;
+				}
+		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
+			qualified_name OptPartitionElementList ForValues OptPartitionBy
+			OptWith OnCommitOption OptTableSpace
+				{
+					CreateStmt *n = makeNode(CreateStmt);
+					$7->relpersistence = $2;
+					n->relation = $7;
+					n->tableElts = $11;
+					n->inhRelations = list_make1($10);
+					n->partbound = (Node *) $12;
+					n->partby = $13;
+					n->ofTypename = NULL;
+					n->constraints = NIL;
+					n->options = $14;
+					n->oncommit = $15;
+					n->tablespacename = $16;
+					n->if_not_exists = true;
+					$$ = (Node *)n;
+				}
 		;
 
 /*
@@ -2930,6 +3064,11 @@ OptTypedTableElementList:
 			| /*EMPTY*/							{ $$ = NIL; }
 		;
 
+OptPartitionElementList:
+			'(' PartitionElementList ')'		{ $$ = $2; }
+			| /*EMPTY*/							{ $$ = NIL; }
+		;
+
 TableElementList:
 			TableElement
 				{
@@ -2952,6 +3091,17 @@ TypedTableElementList:
 				}
 		;
 
+PartitionElementList:
+			PartitionElement
+				{
+					$$ = list_make1($1);
+				}
+			| PartitionElementList ',' PartitionElement
+				{
+					$$ = lappend($1, $3);
+				}
+		;
+
 TableElement:
 			columnDef							{ $$ = $1; }
 			| TableLikeClause					{ $$ = $1; }
@@ -2963,6 +3113,11 @@ TypedTableElement:
 			| TableConstraint					{ $$ = $1; }
 		;
 
+PartitionElement:
+			columnOptions						{ $$ = $1; }
+			| TableConstraint					{ $$ = $1; }
+		;
+
 columnDef:	ColId Typename create_generic_options ColQualList
 				{
 					ColumnDef *n = makeNode(ColumnDef);
@@ -2972,6 +3127,7 @@ columnDef:	ColId Typename create_generic_options ColQualList
 					n->is_local = true;
 					n->is_not_null = false;
 					n->is_from_type = false;
+					n->is_for_partition = false;
 					n->storage = 0;
 					n->raw_default = NULL;
 					n->cooked_default = NULL;
@@ -2993,6 +3149,7 @@ columnOptions:	ColId WITH OPTIONS ColQualList
 					n->is_local = true;
 					n->is_not_null = false;
 					n->is_from_type = false;
+					n->is_for_partition = false;
 					n->storage = 0;
 					n->raw_default = NULL;
 					n->cooked_default = NULL;
@@ -11122,6 +11279,7 @@ TableFuncElement:	ColId Typename opt_collate_clause
 					n->is_local = true;
 					n->is_not_null = false;
 					n->is_from_type = false;
+					n->is_for_partition = false;
 					n->storage = 0;
 					n->raw_default = NULL;
 					n->cooked_default = NULL;
@@ -13805,6 +13963,7 @@ unreserved_keyword:
 			| ASSERTION
 			| ASSIGNMENT
 			| AT
+			| ATTACH
 			| ATTRIBUTE
 			| BACKWARD
 			| BEFORE
@@ -13851,6 +14010,7 @@ unreserved_keyword:
 			| DELIMITER
 			| DELIMITERS
 			| DEPENDS
+			| DETACH
 			| DICTIONARY
 			| DISABLE_P
 			| DISCARD
@@ -13893,6 +14053,7 @@ unreserved_keyword:
 			| IMPLICIT_P
 			| IMPORT_P
 			| INCLUDING
+			| INCLUSIVE
 			| INCREMENT
 			| INDEX
 			| INDEXES
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 77ffd84..d71fdfe 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -493,7 +493,9 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 				err = _("grouping operations are not allowed in partition key expression");
 
 			break;
-
+		case EXPR_KIND_PARTITION_FOR_VALUES:
+			errkind = true;
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -854,6 +856,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_PARTITION_KEY:
 			err = _("window functions are not allowed in partition key expression");
 			break;
+		case EXPR_KIND_PARTITION_FOR_VALUES:
+			errkind = true;
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 6a74c28..85d7828 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -352,6 +352,26 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 				result = (Node *) expr;
 				break;
 			}
+		case T_PartitionListSpec:
+			{
+				PartitionListSpec *list = (PartitionListSpec *) expr;
+
+				list->values = transformExpressionList(pstate, list->values,
+														pstate->p_expr_kind);
+				result = expr;
+				break;
+			}
+		case T_PartitionRangeSpec:
+			{
+				PartitionRangeSpec *range = (PartitionRangeSpec *) expr;
+
+				range->lower = transformExpressionList(pstate, range->lower,
+														pstate->p_expr_kind);
+				range->upper = transformExpressionList(pstate, range->upper,
+														pstate->p_expr_kind);
+				result = expr;
+				break;
+			}
 
 		default:
 			/* should not reach here */
@@ -1690,6 +1710,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_OFFSET:
 		case EXPR_KIND_RETURNING:
 		case EXPR_KIND_VALUES:
+		case EXPR_KIND_PARTITION_FOR_VALUES:
 			/* okay */
 			break;
 		case EXPR_KIND_CHECK_CONSTRAINT:
@@ -3230,6 +3251,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "WHEN";
 		case EXPR_KIND_PARTITION_KEY:
 			return "partition key expression";
+		case EXPR_KIND_PARTITION_FOR_VALUES:
+			return "FOR VALUES";
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 465597d..e75add7 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -48,8 +48,10 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
+#include "optimizer/planner.h"
 #include "parser/analyze.h"
 #include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
 #include "parser/parse_collate.h"
 #include "parser/parse_expr.h"
 #include "parser/parse_relation.h"
@@ -89,6 +91,7 @@ typedef struct
 								 * the table */
 	IndexStmt  *pkey;			/* PRIMARY KEY index, if any */
 	bool		ispartitioned;	/* true if table is partitioned */
+	Node	   *partbound;		/* transformed FOR VALUES */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -131,6 +134,10 @@ static void transformConstraintAttrs(CreateStmtContext *cxt,
 						 List *constraintList);
 static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
 static void setSchemaName(char *context_schema, char **stmt_schema_name);
+static void transformPartitionOf(CreateStmtContext *cxt, Node *bound);
+static void transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd);
+static Node *transformPartitionBound(CreateStmtContext *cxt, Relation parent,
+						Node *bound);
 
 
 /*
@@ -232,6 +239,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
 	cxt.ispartitioned = stmt->partby != NULL;
+	cxt.partbound = NULL;
 
 	/*
 	 * Notice that we allow OIDs here only for plain tables, even though
@@ -250,6 +258,9 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	if (stmt->ofTypename)
 		transformOfType(&cxt, stmt->ofTypename);
 
+	if (stmt->partbound)
+		transformPartitionOf(&cxt, stmt->partbound);
+
 	if (stmt->partby)
 	{
 		int		partnatts = list_length(stmt->partby->partParams);
@@ -355,6 +366,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	 */
 	stmt->tableElts = cxt.columns;
 	stmt->constraints = cxt.ckconstraints;
+	stmt->partbound = cxt.partbound;
 
 	result = lappend(cxt.blist, stmt);
 	result = list_concat(result, cxt.alist);
@@ -894,6 +906,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 		def->is_local = true;
 		def->is_not_null = attribute->attnotnull;
 		def->is_from_type = false;
+		def->is_for_partition = false;
 		def->storage = 0;
 		def->raw_default = NULL;
 		def->cooked_default = NULL;
@@ -1113,6 +1126,7 @@ transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
 		n->is_local = true;
 		n->is_not_null = false;
 		n->is_from_type = true;
+		n->is_for_partition = false;
 		n->storage = 0;
 		n->raw_default = NULL;
 		n->cooked_default = NULL;
@@ -2580,6 +2594,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
 	cxt.ispartitioned = relid_is_partitioned(relid);
+	cxt.partbound = NULL;
 
 	/*
 	 * The only subtypes that currently require parse transformation handling
@@ -2668,6 +2683,22 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 					break;
 				}
 
+			case AT_AttachPartition:
+				{
+					PartitionCmd	*partcmd = (PartitionCmd *) cmd->def;
+
+					transformAttachPartition(&cxt, partcmd);
+
+					/* assign transformed values */
+					partcmd->bound = cxt.partbound;
+				}
+
+				newcmds = lappend(newcmds, cmd);
+				break;
+			case AT_DetachPartition:
+				newcmds = lappend(newcmds, cmd);
+				break;
+
 			default:
 				newcmds = lappend(newcmds, cmd);
 				break;
@@ -3032,3 +3063,290 @@ setSchemaName(char *context_schema, char **stmt_schema_name)
 						"different from the one being created (%s)",
 						*stmt_schema_name, context_schema)));
 }
+
+/*
+ * transformPartitionOf
+ *		Analyze PARTITION OF ... FOR VALUES ...
+ */
+static void
+transformPartitionOf(CreateStmtContext *cxt, Node *bound)
+{
+	TupleDesc	tupdesc;
+	int			i;
+	Relation	parentRel;
+	RangeVar   *partof = linitial(cxt->inhRelations);
+
+	parentRel = heap_openrv(partof, AccessShareLock);
+
+	/* Check if the target table is partitioned at all */
+	if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("\"%s\" is not partitioned",
+						RelationGetRelationName(parentRel))));
+	Assert(parentRel->rd_partkey != NULL);
+
+	/* If parent rel is temp, it must belong to this session */
+	if (parentRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+		!parentRel->rd_islocaltemp)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot create as partition of temporary relation of another session")));
+
+	/*
+	 * Do not allow OIDs in a partition, if not present in the parent. But
+	 * force them in partition, if they are present in the parent.
+	 */
+	if (cxt->hasoids && !parentRel->rd_rel->relhasoids)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot create table with OIDs as partition of table without OIDs")));
+
+	if (parentRel->rd_rel->relhasoids);
+		cxt->hasoids = true;
+
+	tupdesc = RelationGetDescr(parentRel);
+	for (i = 0; i < tupdesc->natts; i++)
+	{
+		Form_pg_attribute attribute = tupdesc->attrs[i];
+		ColumnDef  *def;
+
+		if (attribute->attisdropped)
+			continue;
+
+		def = makeNode(ColumnDef);
+		def->colname = pstrdup(NameStr(attribute->attname));
+		def->typeName = makeTypeNameFromOid(attribute->atttypid,
+											attribute->atttypmod);
+		def->inhcount = 1;
+		def->is_local = false;
+		def->is_not_null = attribute->attnotnull;
+		def->is_from_type = false;
+		def->is_for_partition = true;
+		def->storage = attribute->attstorage;
+		def->raw_default = NULL;
+		def->cooked_default = NULL;
+		def->collClause = NULL;
+		def->collOid = attribute->attcollation;
+		def->constraints = NIL;
+		def->location = -1;
+
+		cxt->columns = lappend(cxt->columns, def);
+	}
+
+	/* tranform the values */
+	cxt->partbound = transformPartitionBound(cxt, parentRel, bound);
+
+	heap_close(parentRel, AccessShareLock);
+}
+
+/*
+ * transformAttachPartition
+ *		Analyze ATTACH PARTITION ... FOR VALUES ...
+ */
+static void
+transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd)
+{
+	Relation	parentRel = cxt->rel;
+
+	/* Check if the target table is partitioned at all */
+	if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("\"%s\" is not partitioned", cxt->relation->relname)));
+	Assert(parentRel->rd_partkey != NULL);
+
+	/* tranform the values */
+	cxt->partbound = transformPartitionBound(cxt, parentRel, cmd->bound);
+}
+
+/*
+ * transformPartitionBound
+ *
+ * Transform partition bound per the partition key
+ */
+static Node *
+transformPartitionBound(CreateStmtContext *cxt, Relation parent, Node *bound)
+{
+	int			i;
+	ListCell   *cell;
+	Oid			parentId = RelationGetRelid(parent);
+	PartitionKey		key = parent->rd_partkey;
+	PartitionListSpec  *list, *result_list;
+	PartitionRangeSpec *range, *result_range;
+
+	switch (key->strategy)
+	{
+		case PARTITION_STRAT_LIST:
+			if (!IsA(bound, PartitionListSpec))
+				ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("invalid bound specification for a list partition"),
+					 parser_errposition(cxt->pstate, exprLocation(bound))));
+
+			list = (PartitionListSpec *) transformExpr(cxt->pstate, bound,
+											EXPR_KIND_PARTITION_FOR_VALUES);
+
+			result_list = makeNode(PartitionListSpec);
+
+			foreach(cell, list->values)
+			{
+				Node   *value = (Node *) lfirst(cell);
+				Node   *orig_value = value;
+				Oid		valuetype;
+
+				valuetype = exprType(value);
+				value = coerce_to_target_type(cxt->pstate,
+											value, valuetype,
+											key->tcinfo->typid[0],
+											key->tcinfo->typmod[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 key column \"%s\"",
+					 format_type_be(key->tcinfo->typid[0]),
+					 get_relid_attribute_name(parentId, key->partattrs[0])),
+					 parser_errposition(cxt->pstate, exprLocation(orig_value))));
+
+				/* Simplify the expression */
+				value = (Node *) expression_planner((Expr *) value);
+
+				result_list->values = lappend(result_list->values, value);
+			}
+			return (Node *) result_list;
+
+		case PARTITION_STRAT_RANGE:
+			if (!IsA(bound, PartitionRangeSpec))
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("invalid bound specification for a range partition"),
+					 parser_errposition(cxt->pstate, exprLocation(bound))));
+
+			range = (PartitionRangeSpec *) transformExpr(cxt->pstate, bound,
+											EXPR_KIND_PARTITION_FOR_VALUES);
+
+			if (!range->lower && !range->upper)
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("both START and END cannot be UNBOUNDED"),
+					 parser_errposition(cxt->pstate, range->location)));
+
+			result_range = makeNode(PartitionRangeSpec);
+			result_range->lowerinc = range->lowerinc;
+			result_range->upperinc = range->upperinc;
+
+			if (range->lower)
+			{
+				if (list_length(range->lower) > key->partnatts)
+					ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("START has more values specified than number"
+								" of columns in the partition key"),
+								parser_errposition(cxt->pstate,
+										exprLocation(list_nth(range->lower,
+										 list_length(range->lower) - 1)))));
+				else if (list_length(range->lower) < key->partnatts)
+					ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("START has fewer values specified than number"
+								" of columns in the partition key"),
+								parser_errposition(cxt->pstate,
+										exprLocation(list_nth(range->lower,
+										 list_length(range->lower) - 1)))));
+
+				i = 0;
+				foreach (cell, range->lower)
+				{
+					Node   *value = (Node *) lfirst(cell);
+					Node   *orig_value = value;
+					Oid		valuetype;
+
+					valuetype = exprType(value);
+					value = coerce_to_target_type(cxt->pstate,
+												value, valuetype,
+												key->tcinfo->typid[i],
+												key->tcinfo->typmod[i],
+												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 key column \"%s\"",
+									format_type_be(key->tcinfo->typid[i]),
+									get_relid_attribute_name(parentId, key->partattrs[i])),
+							 parser_errposition(cxt->pstate, exprLocation(orig_value))));
+
+					/* Simplify the expression */
+					value = (Node *) expression_planner((Expr *) value);
+
+					result_range->lower = lappend(result_range->lower, value);
+					++i;
+				}
+			}
+			else
+				result_range->lowerinc = false;
+
+			if (range->upper)
+			{
+				if (list_length(range->upper) > key->partnatts)
+					ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("END has more values specified than number"
+								" of columns in the partition key"),
+								parser_errposition(cxt->pstate,
+										exprLocation(list_nth(range->upper,
+										 list_length(range->upper) - 1)))));
+				else if (list_length(range->upper) < key->partnatts)
+					ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("END has fewer values specified than number"
+								" of columns in the partition key"),
+								parser_errposition(cxt->pstate,
+										exprLocation(list_nth(range->upper,
+										 list_length(range->upper) - 1)))));
+
+				i = 0;
+				foreach (cell, range->upper)
+				{
+					Node   *value = (Node *) lfirst(cell);
+					Node   *orig_value = value;
+					Oid		valuetype;
+
+					valuetype = exprType(value);
+					value = coerce_to_target_type(cxt->pstate,
+												value, valuetype,
+												key->tcinfo->typid[i],
+												key->tcinfo->typmod[i],
+												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 key column \"%s\"",
+									format_type_be(key->tcinfo->typid[i]),
+									get_relid_attribute_name(parentId, key->partattrs[i])),
+							 parser_errposition(cxt->pstate, exprLocation(orig_value))));
+
+					/* Simplify the expression */
+					value = (Node *) expression_planner((Expr *) value);
+
+					result_range->upper = lappend(result_range->upper, value);
+					++i;
+				}
+			}
+			else
+				result_range->upperinc = false;
+
+			return (Node *) result_range;
+	}
+
+	return NULL;
+}
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 2c876c4..31dbe17 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -14,6 +14,7 @@
 #define PARTITION_H
 
 #include "fmgr.h"
+#include "parser/parse_node.h"
 #include "utils/relcache.h"
 
 /* Type and collation information for partition key columns */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 9e582a5..56bbe43 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -405,6 +405,7 @@ typedef enum NodeTag
 	T_AlterPolicyStmt,
 	T_CreateTransformStmt,
 	T_CreateAmStmt,
+	T_PartitionCmd,
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -454,6 +455,8 @@ typedef enum NodeTag
 	T_RoleSpec,
 	T_PartitionElem,
 	T_PartitionBy,
+	T_PartitionListSpec,
+	T_PartitionRangeSpec,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 4f697b8..d9dff96 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -591,6 +591,7 @@ typedef struct ColumnDef
 	bool		is_local;		/* column has local (non-inherited) def'n */
 	bool		is_not_null;	/* NOT NULL constraint specified? */
 	bool		is_from_type;	/* column definition came from table type */
+	bool		is_for_partition;	/* column definition is for a partition */
 	char		storage;		/* attstorage setting, or 0 for default */
 	Node	   *raw_default;	/* default value (untransformed parse tree) */
 	Node	   *cooked_default; /* default value (transformed expr tree) */
@@ -731,6 +732,40 @@ typedef struct PartitionBy
 	int			location;	/* token location, or -1 if unknown */
 } PartitionBy;
 
+/*
+ * PartitionRangeSpec - range type syntax for range partition bound
+ */
+typedef struct PartitionListSpec
+{
+	NodeTag		type;
+	List	   *values;
+	int			location;
+} PartitionListSpec;
+
+/*
+ * PartitionRangeSpec - range type syntax for range partition bound
+ */
+typedef struct PartitionRangeSpec
+{
+	NodeTag		type;
+	bool		lowerinc;
+	List	   *lower;
+	bool		upperinc;
+	List	   *upper;
+	int			location;   /* token location, or -1 if unknown */
+} PartitionRangeSpec;
+
+/*
+ * PartitionCmd -  ALTER TABLE partition commands
+ */
+typedef struct PartitionCmd
+{
+	NodeTag		type;
+	RangeVar   *name;
+	Node	   *bound;
+	bool		skip_validate;
+} PartitionCmd;
+
 /****************************************************************************
  *	Nodes for a Query tree
  ****************************************************************************/
@@ -1558,7 +1593,9 @@ typedef enum AlterTableType
 	AT_DisableRowSecurity,		/* DISABLE ROW SECURITY */
 	AT_ForceRowSecurity,		/* FORCE ROW SECURITY */
 	AT_NoForceRowSecurity,		/* NO FORCE ROW SECURITY */
-	AT_GenericOptions			/* OPTIONS (...) */
+	AT_GenericOptions,			/* OPTIONS (...) */
+	AT_AttachPartition,			/* ATTACH PARTITION */
+	AT_DetachPartition			/* DETACH PARTITION */
 } AlterTableType;
 
 typedef struct ReplicaIdentityStmt
@@ -1783,7 +1820,9 @@ typedef struct CreateStmt
 	RangeVar   *relation;		/* relation to create */
 	List	   *tableElts;		/* column definitions (list of ColumnDef) */
 	List	   *inhRelations;	/* relations to inherit from (list of
-								 * inhRelation) */
+								 * inhRelation); (ab)used also as partition
+								 * parent */
+	Node	   *partbound;		/* FOR VALUES clause */
 	PartitionBy *partby;		/* PARTITION BY clause */
 	TypeName   *ofTypename;		/* OF typename */
 	List	   *constraints;	/* constraints (list of Constraint nodes) */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 40da67a..70c264c 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -49,6 +49,7 @@ PG_KEYWORD("assertion", ASSERTION, UNRESERVED_KEYWORD)
 PG_KEYWORD("assignment", ASSIGNMENT, UNRESERVED_KEYWORD)
 PG_KEYWORD("asymmetric", ASYMMETRIC, RESERVED_KEYWORD)
 PG_KEYWORD("at", AT, UNRESERVED_KEYWORD)
+PG_KEYWORD("attach", ATTACH, UNRESERVED_KEYWORD)
 PG_KEYWORD("attribute", ATTRIBUTE, UNRESERVED_KEYWORD)
 PG_KEYWORD("authorization", AUTHORIZATION, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("backward", BACKWARD, UNRESERVED_KEYWORD)
@@ -127,6 +128,7 @@ PG_KEYWORD("delimiter", DELIMITER, UNRESERVED_KEYWORD)
 PG_KEYWORD("delimiters", DELIMITERS, UNRESERVED_KEYWORD)
 PG_KEYWORD("depends", DEPENDS, UNRESERVED_KEYWORD)
 PG_KEYWORD("desc", DESC, RESERVED_KEYWORD)
+PG_KEYWORD("detach", DETACH, UNRESERVED_KEYWORD)
 PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD)
 PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD)
@@ -191,6 +193,7 @@ PG_KEYWORD("implicit", IMPLICIT_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("import", IMPORT_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("in", IN_P, RESERVED_KEYWORD)
 PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD)
+PG_KEYWORD("inclusive", INCLUSIVE, UNRESERVED_KEYWORD)
 PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD)
 PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD)
 PG_KEYWORD("indexes", INDEXES, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index a13c6fb..3d45663 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -65,7 +65,8 @@ typedef enum ParseExprKind
 	EXPR_KIND_EXECUTE_PARAMETER,	/* parameter value in EXECUTE */
 	EXPR_KIND_TRIGGER_WHEN,		/* WHEN condition in CREATE TRIGGER */
 	EXPR_KIND_POLICY,			/* USING or WITH CHECK expr in policy */
-	EXPR_KIND_PARTITION_KEY		/* partition key expression */
+	EXPR_KIND_PARTITION_KEY,	/* partition key expression */
+	EXPR_KIND_PARTITION_FOR_VALUES	/* partition bound */
 } ParseExprKind;
 
 
-- 
1.7.1

