From 69c6f52cd82d97743d5e493260510192b4a777aa Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 14:38:08 +0900
Subject: [PATCH 3/8] Catalog and DDL for partitions.

pg_class gets new fields: relispartition and relpartbound (a pg_node_tree).

Parent-child relationships of a partitioned table and its partitions are
managed with inheritance, so not much here about that.

DDL includes both a way to create new partition and "attach" an existing table
as partition. An existing partition can be "detached" from a table, which
preserves it as a regular (or partitioned) table.

Add a field to relcache for storing a "partition descriptor" of a partitioned
table which has hopefully all the information about a table's partitions that
someone might want to do something with.
---
 doc/src/sgml/catalogs.sgml                 |   17 +
 doc/src/sgml/ref/alter_table.sgml          |  105 ++-
 doc/src/sgml/ref/create_foreign_table.sgml |   26 +
 doc/src/sgml/ref/create_table.sgml         |   92 ++-
 src/backend/catalog/Makefile               |    2 +-
 src/backend/catalog/heap.c                 |  105 ++-
 src/backend/catalog/partition.c            | 1646 ++++++++++++++++++++++++++++
 src/backend/commands/createas.c            |    2 +-
 src/backend/commands/sequence.c            |    2 +-
 src/backend/commands/tablecmds.c           | 1027 ++++++++++++++++--
 src/backend/commands/typecmds.c            |    3 +-
 src/backend/commands/view.c                |    3 +-
 src/backend/nodes/copyfuncs.c              |   47 +
 src/backend/nodes/equalfuncs.c             |   41 +
 src/backend/nodes/nodeFuncs.c              |    6 +
 src/backend/nodes/outfuncs.c               |   27 +
 src/backend/nodes/readfuncs.c              |   34 +
 src/backend/parser/gram.y                  |  208 ++++-
 src/backend/parser/parse_utilcmd.c         |  260 +++++-
 src/backend/tcop/utility.c                 |    6 +-
 src/backend/utils/cache/relcache.c         |   93 ++-
 src/include/catalog/heap.h                 |    1 +
 src/include/catalog/partition.h            |   48 +
 src/include/catalog/pg_class.h             |   22 +-
 src/include/commands/tablecmds.h           |    2 +-
 src/include/nodes/nodes.h                  |    3 +
 src/include/nodes/parsenodes.h             |   52 +-
 src/include/parser/kwlist.h                |    2 +
 src/include/parser/parse_utilcmd.h         |    2 +
 src/include/utils/rel.h                    |   21 +
 src/test/regress/expected/alter_table.out  |  298 +++++
 src/test/regress/expected/create_table.out |  187 ++++
 src/test/regress/sql/alter_table.sql       |  266 +++++
 src/test/regress/sql/create_table.sql      |  153 +++
 34 files changed, 4670 insertions(+), 139 deletions(-)
 create mode 100644 src/backend/catalog/partition.c
 create mode 100644 src/include/catalog/partition.h

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 6139ab1..31352a2 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1846,6 +1846,13 @@
      </row>
 
      <row>
+      <entry><structfield>relispartition</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>True if table is a partition</entry>
+     </row>
+
+     <row>
       <entry><structfield>relfrozenxid</structfield></entry>
       <entry><type>xid</type></entry>
       <entry></entry>
@@ -1891,6 +1898,16 @@
        Access-method-specific options, as <quote>keyword=value</> strings
       </entry>
      </row>
+
+     <row>
+      <entry><structfield>relpartbound</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>
+       If table is a partition (see <structfield>relispartition</structfield>),
+       internal representation of the partition bound
+      </entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index e48ccf2..5949837 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -77,6 +77,8 @@ 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">partition_name</replaceable> <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+    DETACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable>
 
 <phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
 
@@ -166,6 +168,12 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
       values or to reject null values.  You can only use <literal>SET
       NOT NULL</> when the column contains no null values.
      </para>
+
+     <para>
+      If this table is a partition, one cannot perform <literal>DROP NOT NULL</>
+      on a column if it is marked <literal>NOT NULL</literal> in the parent
+      table.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -704,6 +712,52 @@ 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 (which might itself be partitioned)
+      as a partition of the target table.  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 present in the target table.
+      If some <literal>CHECK</literal> constraint of the table being attached
+      is marked <literal>NO INHERIT</literal>, the command will fail; such
+      constraints must be recreated without the <literal>NO INHERIT</literal>
+      clause.  Currently <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>,
+      and <literal>FOREIGN KEY</literal> constraints are not considered.
+     </para>
+
+     <para>
+      A full table scan is performed on the table being attached to check that
+      no existing row in the table violates the partition constraint.  It is
+      possible to avoid this potentially expensive scan by adding a valid
+      <literal>CHECK</literal> constraint to the table that only allows rows
+      satisfying the desired partition constraint before trying to attach.
+      It will be determined using such a constraint that existing rows in the
+      table satisfy the partition constraint, so the table scan to check the
+      same will be skipped.  When adding a range partition or a list partition
+      that does not accept <literal>NULL</literal> values, also add
+      <literal>NOT NULL</literal> constraint to the partition key columns,
+      otherwise, the scan will be performed regardless of the existing
+      constraints.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>DETACH PARTITION</literal></term>
+    <listitem>
+     <para>
+      This form detaches specified partition of the target table.  The detached
+      partition continues to exist as a standalone table, but no longer has any
+      ties to the table from which it was detached.
+     </para>
+    </listitem>
+   </varlistentry>
+
   </variablelist>
   </para>
 
@@ -721,8 +775,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
    You must own the table to use <command>ALTER TABLE</>.
    To change the schema or tablespace of a table, you must also have
    <literal>CREATE</literal> privilege on the new schema or tablespace.
-   To add the table as a new child of a parent table, you must own the
-   parent table as well.
+   To add the table as a new child of a parent table, or as a new partition
+   of a partitioned table, you must own the parent table as well.
    To alter the owner, you must also be a direct or indirect member of the new
    owning role, and that role must have <literal>CREATE</literal> privilege on
    the table's schema.  (These restrictions enforce that altering the owner
@@ -938,6 +992,24 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><replaceable class="PARAMETER">partition_name</replaceable></term>
+      <listitem>
+       <para>
+        The name of the table to attach as a new partition or to detach from this table.
+       </para>
+      </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>
 
@@ -978,6 +1050,11 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
    </para>
 
    <para>
+    Similarly, when attaching a new partition it is scanned to verify that
+    existing rows meet the partition constraint.
+   </para>
+
+   <para>
     The main reason for providing the option to specify multiple changes
     in a single <command>ALTER TABLE</> is that multiple table scans or
     rewrites can thereby be combined into a single pass over the table.
@@ -1047,6 +1124,9 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     COLUMN</literal> (i.e., <command>ALTER TABLE ONLY ... DROP
     COLUMN</command>) never removes any descendant columns, but
     instead marks them as independently defined rather than inherited.
+    A nonrecursive <literal>DROP COLUMN</literal> command will fail for a
+    partitioned table, because all partitions of a table must have the same
+    columns as the partitioning root.
    </para>
 
    <para>
@@ -1233,6 +1313,27 @@ ALTER TABLE distributors DROP CONSTRAINT distributors_pkey,
     ADD CONSTRAINT distributors_pkey PRIMARY KEY USING INDEX dist_id_temp_idx;
 </programlisting></para>
 
+  <para>
+   Attach a partition to range partitioned table:
+<programlisting>
+ALTER TABLE measurement
+    ATTACH PARTITION measurement_y2016m07 FOR VALUES FROM ('2016-07-01') TO ('2016-08-01');
+</programlisting></para>
+
+  <para>
+   Attach a partition to list partitioned table:
+<programlisting>
+ALTER TABLE cities
+    ATTACH PARTITION cities_west FOR VALUES IN ('Los Angeles', 'San Francisco');
+</programlisting></para>
+
+  <para>
+   Detach a partition from partitioned table:
+<programlisting>
+ALTER TABLE cities
+    DETACH PARTITION measurement_y2015m12;
+</programlisting></para>
+
  </refsect1>
 
  <refsect1>
diff --git a/doc/src/sgml/ref/create_foreign_table.sgml b/doc/src/sgml/ref/create_foreign_table.sgml
index 413b033..5d0dcf5 100644
--- a/doc/src/sgml/ref/create_foreign_table.sgml
+++ b/doc/src/sgml/ref/create_foreign_table.sgml
@@ -27,6 +27,15 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
   SERVER <replaceable class="parameter">server_name</replaceable>
 [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
 
+CREATE FOREIGN 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>
+  SERVER <replaceable class="parameter">server_name</replaceable>
+[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
+
 <phrase>where <replaceable class="PARAMETER">column_constraint</replaceable> is:</phrase>
 
 [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
@@ -68,6 +77,12 @@ CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) [ NO INHERIT ]
   </para>
 
   <para>
+   If <literal>PARTITION OF</literal> clause is specified then the table is
+   created as a partition of <literal>parent_table</literal> with specified
+   bounds.
+  </para>
+
+  <para>
    To be able to create a foreign table, you must have <literal>USAGE</literal>
    privilege on the foreign server, as well as <literal>USAGE</literal>
    privilege on all column types used in the table.
@@ -314,6 +329,17 @@ CREATE FOREIGN TABLE films (
 SERVER film_server;
 </programlisting></para>
 
+  <para>
+   Create foreign table <structname>measurement_y2016m07</>, which will be
+   accessed through the server <structname>server_07</>, as a partition
+   of the range partitioned table <structname>measurement</>:
+
+<programlisting>
+CREATE FOREIGN TABLE measurement_y2016m07
+    PARTITION OF measurement FOR VALUES FROM ('2016-07-01') TO ('2016-08-01')
+    SERVER server_07;
+</programlisting></para>
+
  </refsect1>
 
  <refsect1 id="SQL-CREATEFOREIGNTABLE-compatibility">
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 1a95219..54c8495 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 { IN ( <replaceable class="PARAMETER">expression</replaceable> [, ...] ) | FROM ( { <replaceable class="PARAMETER">expression</replaceable> | UNBOUNDED } [, ...] ) TO ( { <replaceable class="PARAMETER">expression</replaceable> | UNBOUNDED } [, ...] ) }
+
 <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>] [, ... ] ) ]
@@ -232,6 +247,50 @@ 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.
+     </para>
+
+     <para>
+      The partition bound specification must correspond to the partitioning
+      method and partition key of the parent table, and must not overlap with
+      any existing partition of that 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.
+      Defaults and constraints can optionally be specified for each of the
+      inherited columns.  One can also specify table constraints in addition
+      to those inherited from the parent.  If a check constraint with the name
+      matching one of the parent's constraint is specified, it is merged with
+      the latter, provided the specified condition is same.
+     </para>
+
+     <para>
+      Rows inserted into a partitioned table will be automatically routed to
+      the correct partition.  If no suitable partition exists, an error will
+      occur.
+     </para>
+
+     <para>
+      A partition must have the same column names and types as the table of
+      which it is a partition.  Therefore, modifications to the column names
+      or types of the partitioned table will automatically propagate to all
+      children, as will operations such as TRUNCATE which normally affect a
+      table and all of its inheritance children.  It is also possible to
+      TRUNCATE a partition individually, just as for an inheritance child.
+      Dropping a partition directly using <literal>DROP TABLE</literal>
+      will fail; it must be <firstterm>detached</> from the parent first.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><replaceable class="PARAMETER">column_name</replaceable></term>
     <listitem>
      <para>
@@ -1424,7 +1483,38 @@ CREATE TABLE measurement (
 CREATE TABLE cities (
     name         text not null,
     population   int,
-) PARTITION BY LIST (name);
+) PARTITION BY LIST (initcap(name));
+</programlisting></para>
+
+  <para>
+   Create partition of a range partitioned table:
+<programlisting>
+CREATE TABLE measurement_y2016m07
+    PARTITION OF measurement (
+    unitsales WITH OPTIONS DEFAULT 0
+) FOR VALUES FROM ('2016-07-01') TO ('2016-08-01');
+</programlisting></para>
+
+  <para>
+   Create partition of a list partitioned table:
+<programlisting>
+CREATE TABLE cities_west
+    PARTITION OF cities (
+    CONSTRAINT city_id_nonzero CHECK (city_id != 0)
+) FOR VALUES IN ('Los Angeles', 'San Francisco');
+</programlisting></para>
+
+  <para>
+   Create partition of a list partitioned table that is itself further
+   partitioned and then add a partition to it:
+<programlisting>
+CREATE TABLE cities_west
+    PARTITION OF cities (
+    CONSTRAINT city_id_nonzero CHECK (city_id != 0)
+) FOR VALUES IN ('Los Angeles', 'San Francisco') PARTITION BY RANGE (population);
+
+CREATE TABLE cities_west_10000_to_100000
+    PARTITION OF cities_west FOR VALUES FROM (10000) TO (100000);
 </programlisting></para>
  </refsect1>
 
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 362deca..2d5ac09 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -11,7 +11,7 @@ top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
-       objectaccess.o objectaddress.o pg_aggregate.o pg_collation.o \
+       objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.o \
        pg_constraint.o pg_conversion.o \
        pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
        pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 754a08b..64fc283 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -41,6 +41,7 @@
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/objectaccess.h"
+#include "catalog/partition.h"
 #include "catalog/pg_attrdef.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -810,6 +811,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
 	values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
 	values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
+	values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
 	values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
 	values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
 	if (relacl != (Datum) 0)
@@ -821,6 +823,9 @@ InsertPgClassTuple(Relation pg_class_desc,
 	else
 		nulls[Anum_pg_class_reloptions - 1] = true;
 
+	/* relpartbound is set by updating this tuple, if necessary */
+	nulls[Anum_pg_class_relpartbound - 1] = true;
+
 	tup = heap_form_tuple(RelationGetDescr(pg_class_desc), values, nulls);
 
 	/*
@@ -926,6 +931,9 @@ AddNewRelationTuple(Relation pg_class_desc,
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
 
+	/* relispartition is always set by updating this tuple later */
+	new_rel_reltup->relispartition = false;
+
 	new_rel_desc->rd_att->tdtypeid = new_type_oid;
 
 	/* Now build and insert the tuple */
@@ -1765,6 +1773,8 @@ void
 heap_drop_with_catalog(Oid relid)
 {
 	Relation	rel;
+	Oid			parentOid;
+	Relation	parent = NULL;
 
 	/*
 	 * Open and lock the relation.
@@ -1772,6 +1782,21 @@ heap_drop_with_catalog(Oid relid)
 	rel = relation_open(relid, AccessExclusiveLock);
 
 	/*
+	 * If the relation is a partition, we must grab exclusive lock on its
+	 * parent because we need to update its partition descriptor. We must
+	 * take a table lock strong enough to prevent all queries on the parent
+	 * from proceeding until we commit and send out a shared-cache-inval
+	 * notice that will make them update their partition descriptor.
+	 * Sometimes, doing this is cycles spent uselessly, especially if the
+	 * parent will be dropped as part of the same command anyway.
+	 */
+	if (rel->rd_rel->relispartition)
+	{
+		parentOid = get_partition_parent(relid);
+		parent = heap_open(parentOid, AccessExclusiveLock);
+	}
+
+	/*
 	 * There can no longer be anyone *else* touching the relation, but we
 	 * might still have open queries or cursors, or pending trigger events, in
 	 * our own session.
@@ -1862,6 +1887,12 @@ heap_drop_with_catalog(Oid relid)
 	 * delete relation tuple
 	 */
 	DeleteRelationTuple(relid);
+
+	if (parent)
+	{
+		CacheInvalidateRelcache(parent);
+		heap_close(parent, NoLock);		/* keep the lock */
+	}
 }
 
 
@@ -2468,8 +2499,11 @@ MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
 			 * definition) then interpret addition of a local constraint as a
 			 * legal merge.  This allows ALTER ADD CONSTRAINT on parent and
 			 * child tables to be given in either order with same end state.
+			 * However if the relation is a partition, all inherited
+			 * constraints are always non-local, including those that were
+			 * merged.
 			 */
-			if (is_local && !con->conislocal)
+			if (is_local && !con->conislocal && !rel->rd_rel->relispartition)
 				allow_merge = true;
 
 			if (!found || !allow_merge)
@@ -2514,10 +2548,24 @@ MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
 			tup = heap_copytuple(tup);
 			con = (Form_pg_constraint) GETSTRUCT(tup);
 
-			if (is_local)
-				con->conislocal = true;
+			/*
+			 * In case of partitions, an inherited constraint must be
+			 * inherited only once since it cannot have multiple parents and
+			 * it is never considered local.
+			 */
+			if (rel->rd_rel->relispartition)
+			{
+				con->coninhcount = 1;
+				con->conislocal = false;
+			}
 			else
-				con->coninhcount++;
+			{
+				if (is_local)
+					con->conislocal = true;
+				else
+					con->coninhcount++;
+			}
+
 			if (is_no_inherit)
 			{
 				Assert(is_local);
@@ -3176,3 +3224,52 @@ RemovePartitionKeyByRelId(Oid relid)
 	ReleaseSysCache(tuple);
 	heap_close(rel, RowExclusiveLock);
 }
+
+/*
+ * StorePartitionBound
+ *		Update pg_class tuple of rel to store the partition bound and set
+ *		relispartition to true
+ */
+void
+StorePartitionBound(Relation rel, Node *bound)
+{
+	Relation	classRel;
+	HeapTuple	tuple,
+				newtuple;
+	Datum	new_val[Natts_pg_class];
+	bool	new_null[Natts_pg_class],
+			new_repl[Natts_pg_class];
+
+	/* Update pg_class tuple */
+	classRel = heap_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(RelationGetRelid(rel)));
+#ifdef USE_ASSERT_CHECKING
+	{
+		Form_pg_class	classForm;
+		bool	isnull;
+
+		classForm = (Form_pg_class) GETSTRUCT(tuple);
+		Assert(!classForm->relispartition);
+		(void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+							   &isnull);
+		Assert(isnull);
+	}
+#endif
+
+	/* Fill in relpartbound value */
+	memset(new_val, 0, sizeof(new_val));
+	memset(new_null, false, sizeof(new_null));
+	memset(new_repl, false, sizeof(new_repl));
+	new_val[Anum_pg_class_relpartbound - 1] = CStringGetTextDatum(nodeToString(bound));
+	new_null[Anum_pg_class_relpartbound - 1] = false;
+	new_repl[Anum_pg_class_relpartbound - 1] = true;
+	newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+								 new_val, new_null, new_repl);
+	/* Also set the flag */
+	((Form_pg_class) GETSTRUCT(newtuple))->relispartition = true;
+	simple_heap_update(classRel, &newtuple->t_self, newtuple);
+	CatalogUpdateIndexes(classRel, newtuple);
+	heap_freetuple(newtuple);
+	heap_close(classRel, RowExclusiveLock);
+}
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
new file mode 100644
index 0000000..e21e7ad
--- /dev/null
+++ b/src/backend/catalog/partition.c
@@ -0,0 +1,1646 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.c
+ *        Partitioning related data structures and functions.
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *        src/backend/catalog/partition.c
+ *
+ *-------------------------------------------------------------------------
+*/
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/nbtree.h"
+#include "access/sysattr.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/partition.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_inherits_fn.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_type.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/parsenodes.h"
+#include "optimizer/clauses.h"
+#include "optimizer/planmain.h"
+#include "optimizer/var.h"
+#include "rewrite/rewriteManip.h"
+#include "storage/lmgr.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/memutils.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+/*
+ * List bound collection - collection of values of all partitions of a list
+ * partitioned relation
+ *
+ * The values are put in a single array to be able to use binary search
+ * over them.  Note that null values are never put into the array.
+ */
+typedef struct PartitionListInfo
+{
+	int		nvalues;	/* number of values in the following array */
+	Datum  *values;		/* values contained in lists of all the partitions */
+	int	   *indexes;	/* partition index of each individiual value; has
+						 * same length as the values array above */
+	bool	has_null;	/* Is there a partition defined to accept nulls? */
+	int		null_index;	/* Index of the null-accepting partition */
+} PartitionListInfo;
+
+/* One bound of a range partition */
+typedef struct PartitionRangeBound
+{
+	int		index;
+	Datum  *datums;			/* range bound datums */
+	bool   *infinite;		/* infinite flag for each datum */
+	bool	lower;			/* this is the lower (vs upper) bound */
+} PartitionRangeBound;
+
+/*
+ * Range bound collection - sorted array of range bounds of the partitions
+ * of a range partitioned table
+ *
+ * In most cases, nbounds is far less than 2 * nparts, because a partition's
+ * upper bound and the next partition's lower bound are same in common cases,
+ * and we only store one of them.
+ *
+ * There are nbound members in the bounds array and nbounds+1 in the indexes
+ * array.
+ */
+typedef struct PartitionRangeInfo
+{
+	PartitionRangeBound **bounds;
+	int		nbounds;
+	int	   *indexes;
+} PartitionRangeInfo;
+
+/*
+ * Collection of bounds of a partitioned relation
+ */
+typedef struct BoundCollectionData
+{
+	char	strategy;	/* list or range bounds? */
+
+	union
+	{
+		struct PartitionListInfo	lists;
+		struct PartitionRangeInfo	ranges;
+	} bounds;
+} BoundCollectionData;
+
+/* One value coming from some (index'th) list partition */
+typedef struct PartitionListValue
+{
+	Datum	value;
+	int		index;
+} PartitionListValue;
+
+static int32 qsort_partition_list_value_cmp(const void *a, const void *b, void *arg);
+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_range(PartitionKey key, PartitionBoundSpec *spec);
+static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
+					   bool *need_relabel);
+
+static List *generate_partition_qual(Relation rel, bool recurse);
+
+/* List partition related support functions */
+static bool equal_list_info(PartitionKey key,
+				PartitionListInfo *l1, PartitionListInfo *l2);
+static int32 partition_list_values_cmp(PartitionKey key, Datum val1, Datum val2);
+static int partition_list_values_bsearch(PartitionKey key, const Datum *values, int n,
+							  const Datum probe);
+
+/* Range partition related support functions */
+static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index, List *datums, bool lower);
+static PartitionRangeBound *copy_range_bound(PartitionKey key, PartitionRangeBound *src);
+static bool equal_range_info(PartitionKey key,
+				 PartitionRangeInfo *r1, PartitionRangeInfo *r2);
+static int32 partition_rbound_cmp(PartitionKey key, PartitionRangeBound *b1, void *arg);
+static bool partition_rbound_eq(PartitionKey key,
+					PartitionRangeBound *b1, PartitionRangeBound *b2);
+typedef int32 (*partition_rbound_bsearch_cmp_fn) (PartitionKey,
+												  PartitionRangeBound *,
+												  void *);
+static int partition_rbound_bsearch(PartitionKey key, PartitionRangeBound **bounds, int n,
+						 void *probe, partition_rbound_bsearch_cmp_fn cmp,
+						 bool equal_allowed, bool *is_equal);
+
+/*
+ * RelationBuildPartitionDesc
+ *		Form rel's partition descriptor
+ *
+ * Not flushed from the cache by RelationClearRelation() unless changed because
+ * of addition or removal of partition.
+ */
+void
+RelationBuildPartitionDesc(Relation rel)
+{
+	List	   *inhoids,
+			   *partoids;
+	Oid		   *oids = NULL;
+	List	   *boundspecs = NIL;
+	ListCell   *cell;
+	int			i,
+				nparts;
+	PartitionKey	key = RelationGetPartitionKey(rel);
+	PartitionDesc	result;
+	MemoryContext	oldcxt;
+
+	/* List partitioning */
+	PartitionListValue **all_values = NULL;
+	int			all_values_count = 0;
+	bool		found_null_partition = false;
+	int			null_partition_index = -1;
+
+	/* Range partitioning */
+	PartitionRangeBound **distinct_bounds = NULL;
+	int			num_distinct_bounds = 0;
+
+	/*
+	 * The following could happen in situations where rel has a pg_class
+	 * entry but not the pg_partitioned_table entry yet.
+	 */
+	if (key == NULL)
+		return;
+
+	/* Get partition oids from pg_inherits */
+	inhoids = find_inheritance_children(RelationGetRelid(rel), NoLock);
+
+	/* Collect bound spec nodes in a list */
+	i = 0;
+	partoids = NIL;
+	foreach(cell, inhoids)
+	{
+		Oid 		inhrelid = lfirst_oid(cell);
+		HeapTuple	tuple;
+		Datum		datum;
+		bool		isnull;
+		Node	   *boundspec;
+
+		tuple = SearchSysCache1(RELOID, 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 = stringToNode(TextDatumGetCString(datum));
+		boundspecs = lappend(boundspecs, boundspec);
+		ReleaseSysCache(tuple);
+		partoids = lappend_oid(partoids, inhrelid);
+	}
+
+	nparts = list_length(partoids);
+
+	if (nparts > 0)
+	{
+		oids = (Oid *) palloc(nparts * sizeof(Oid));
+		i = 0;
+		foreach (cell, partoids)
+			oids[i++] = lfirst_oid(cell);
+
+		/* Convert from node to the internal representation */
+		switch (key->strategy)
+		{
+			case PARTITION_STRATEGY_LIST:
+			{
+				List   *non_null_values = NIL;
+
+				/*
+				 * Create a unified list of non-null values across all
+				 * partitions.
+				 */
+				i = 0;
+				found_null_partition = false;
+				null_partition_index = -1;
+				foreach(cell, boundspecs)
+				{
+					ListCell   *c;
+					PartitionBoundSpec  *spec = lfirst(cell);
+
+					if (spec->strategy != PARTITION_STRATEGY_LIST)
+						elog(ERROR, "invalid strategy in partition bound spec");
+
+					foreach (c, spec->listdatums)
+					{
+						Const	*val = lfirst(c);
+						PartitionListValue *list_value = NULL;
+
+						if (!val->constisnull)
+						{
+							list_value = (PartitionListValue *)
+										palloc0(sizeof(PartitionListValue));
+							list_value->index = i;
+							list_value->value = val->constvalue;
+						}
+						else
+						{
+							/*
+							 * Never put a null into the values array, flag
+							 * instead for the code further down below where
+							 * we construct the actual relcache struct.
+							 */
+							if (found_null_partition)
+								elog(ERROR, "found null more than once");
+							found_null_partition = true;
+							null_partition_index = i;
+						}
+
+						if (list_value)
+							non_null_values = lappend(non_null_values,
+													  list_value);
+					}
+
+					i++;
+				}
+
+				all_values_count = list_length(non_null_values);
+
+				/*
+				 * Collect all list values in one array. Alongside the value,
+				 * we also save the index of partition the value comes from.
+				 */
+				all_values = (PartitionListValue **)
+								  palloc(all_values_count *
+										sizeof(PartitionListValue *));
+				i = 0;
+				foreach(cell, non_null_values)
+				{
+					PartitionListValue	*src = lfirst(cell);
+
+					all_values[i] = (PartitionListValue *)
+										palloc(sizeof(PartitionListValue));
+					all_values[i]->value = src->value;
+					all_values[i]->index = src->index;
+					i++;
+				}
+
+				qsort_arg(all_values, all_values_count,
+						  sizeof(PartitionListValue *),
+						  qsort_partition_list_value_cmp,
+						  (void *) key);
+				break;
+			}
+
+			case PARTITION_STRATEGY_RANGE:
+			{
+				int		j, 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;
+				foreach(cell, boundspecs)
+				{
+					PartitionBoundSpec  *spec = lfirst(cell);
+					PartitionRangeBound *lower, *upper;
+
+					if (spec->strategy != PARTITION_STRATEGY_RANGE)
+						elog(ERROR, "invalid strategy in partition bound spec");
+
+					lower = make_one_range_bound(key, i, spec->lowerdatums,
+												 true);
+					upper = make_one_range_bound(key, i, spec->upperdatums,
+												 false);
+					all_bounds[j] = lower;
+					all_bounds[j+1] = upper;
+					j += 2;
+					i++;
+				}
+				Assert(j == 2 * nparts);
+
+				/* Sort all the bounds in ascending order */
+				qsort_arg(all_bounds, 2 * nparts,
+						  sizeof(PartitionRangeBound *),
+						  qsort_partition_rbound_cmp,
+						  (void *) key);
+				/*
+				 * Count the number of distinct bounds to allocate an array
+				 * of that size.
+				 */
+				num_distinct_bounds = 0;
+				prev = NULL;
+				for (i = 0; i < 2 * nparts; i++)
+				{
+					PartitionRangeBound *cur = all_bounds[i];
+
+					/*
+					 * Count the current bound if it is distinct from the
+					 * previous one.  Also, store if the index i contains
+					 * a distinct bound that we'd like put in the relcache
+					 * array.
+					 */
+					if (prev == NULL || !partition_rbound_eq(key, cur, prev))
+					{
+						distinct_indexes[i] = true;
+						num_distinct_bounds++;
+					}
+					else
+						distinct_indexes[i] = false;
+
+					prev = cur;
+				}
+
+				/*
+				 * Finally save them in an array from where they will be
+				 * copied into the relcache.
+				 */
+				distinct_bounds = (PartitionRangeBound **)
+											palloc0(num_distinct_bounds *
+												sizeof(PartitionRangeBound *));
+				k = 0;
+				for (i = 0; i < 2 * nparts; i++)
+				{
+					if (distinct_indexes[i])
+						distinct_bounds[k++] = all_bounds[i];
+				}
+				Assert(k == num_distinct_bounds);
+				break;
+			}
+		}
+	}
+
+	/* Now build the actual relcache partition descriptor */
+	rel->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
+										RelationGetRelationName(rel),
+										ALLOCSET_DEFAULT_SIZES);
+	oldcxt = MemoryContextSwitchTo(rel->rd_pdcxt);
+
+	result = (PartitionDescData *) palloc0(sizeof(PartitionDescData));
+	result->nparts = nparts;
+	if (nparts > 0)
+	{
+		int		   *mapping;
+		int			next_index = 0;
+
+		result->oids = (Oid *) palloc0(nparts * sizeof(Oid));
+		result->boundinfo = (BoundCollectionData *)
+										palloc0(sizeof(BoundCollectionData));
+		result->boundinfo->strategy = key->strategy;
+
+		/* Initialize mapping array with invalid values */
+		mapping = (int *) palloc(sizeof(int) * nparts);
+		for (i = 0; i < nparts; i++)
+			mapping[i] = -1;
+
+		switch (key->strategy)
+		{
+			case PARTITION_STRATEGY_LIST:
+			{
+				PartitionListInfo	listinfo;
+
+				listinfo.nvalues = all_values_count;
+				listinfo.has_null = found_null_partition;
+				listinfo.values = (Datum *)
+									palloc0(all_values_count * sizeof(Datum));
+				listinfo.indexes = (int *)
+									palloc0(all_values_count * sizeof(int));
+
+				/*
+				 * Copy values.  Indexes of individual values are mapped to
+				 * canonical values so that they match for any two list
+				 * partitioned tables with same number of partitions and same
+				 * lists per partition.  One way to canonicalize is to assign
+				 * the index in all_values[] of the smallest value of each
+				 * partition as the index of all of the partition's values.
+				 */
+				for (i = 0; i < all_values_count; i++)
+				{
+					listinfo.values[i] = datumCopy(all_values[i]->value,
+													key->parttypbyval[0],
+													key->parttyplen[0]);
+
+					/* If the old index is has no mapping, assign one */
+					if (mapping[all_values[i]->index] == -1)
+						mapping[all_values[i]->index] = next_index++;
+
+					listinfo.indexes[i] = mapping[all_values[i]->index];
+				}
+
+				/*
+				 * If null-accepting partition has no mapped index yet, assign
+				 * one.  This could happen if such partition accepts only null
+				 * and hence not covered in the above loop which only handled
+				 * non-null values.
+				 */
+				if (found_null_partition)
+				{
+					Assert(null_partition_index >= 0);
+					if (mapping[null_partition_index] == -1)
+						mapping[null_partition_index] = next_index++;
+				}
+
+				/* All partition must now have a valid mapping */
+				Assert(next_index == nparts);
+
+				if (found_null_partition)
+					listinfo.null_index = mapping[null_partition_index];
+				else
+					listinfo.null_index = -1;
+
+				result->boundinfo->bounds.lists = listinfo;
+				break;
+			}
+
+			case PARTITION_STRATEGY_RANGE:
+			{
+				PartitionRangeInfo	rangeinfo;
+
+				rangeinfo.bounds = (PartitionRangeBound **)
+										palloc(num_distinct_bounds *
+											sizeof(PartitionRangeBound *));
+				rangeinfo.indexes = (int *)
+										palloc((num_distinct_bounds + 1) *
+													sizeof(int));
+
+				for (i = 0; i < num_distinct_bounds; i++)
+				{
+					rangeinfo.bounds[i] =
+								copy_range_bound(key, distinct_bounds[i]);
+
+					/*
+					 * There is no mapping for invalid indexes.
+					 *
+					 * Any lower bounds in the distinct_bounds array have
+					 * invalid indexes assigned, because the values between
+					 * the previous bound (if there is one) and this (lower)
+					 * bound are not part of the range of any existing
+					 * partition.
+					 */
+					if (rangeinfo.bounds[i]->lower)
+						rangeinfo.indexes[i] = -1;
+					else
+					{
+						int		orig_index = rangeinfo.bounds[i]->index;
+
+						/* If the old index is has no mapping, assign one */
+						if (mapping[orig_index] == -1)
+							mapping[orig_index] = next_index++;
+
+						rangeinfo.indexes[i] = mapping[orig_index];
+					}
+				}
+				rangeinfo.indexes[i] = -1;
+				rangeinfo.nbounds = num_distinct_bounds;
+
+				result->boundinfo->bounds.ranges = rangeinfo;
+				break;
+			}
+		}
+
+		/*
+		 * Now assign OIDs from the original array into mapped indexes
+		 * of the result array.  Order of OIDs in the former is defined
+		 * by the catalog scan that retrived them, whereas that in the
+		 * latter is defined by canonicalized representation of the
+		 * list values or the range bounds.
+		 */
+		for (i = 0; i < nparts; i++)
+			result->oids[mapping[i]] = oids[i];
+		pfree(mapping);
+	}
+
+	MemoryContextSwitchTo(oldcxt);
+	rel->rd_partdesc = result;
+}
+
+/*
+ * Are two partition bound collections logically equal?
+ *
+ * Used in the keep logic of relcache.c (ie, in RelationClearRelation()).
+ * This is also useful when b1 and b2 are bound collections of two separate
+ * relations, respectively, because BoundCollection is a canonical
+ * representation of partition bounds.
+ */
+bool
+partition_bounds_equal(PartitionKey key,
+					   BoundCollection b1, BoundCollection b2)
+{
+	if (b1->strategy != b2->strategy)
+		return false;
+
+	switch (key->strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+			if (!equal_list_info(key,
+								 &b1->bounds.lists, &b2->bounds.lists))
+				return false;
+			break;
+
+		case PARTITION_STRATEGY_RANGE:
+			if (!equal_range_info(key,
+								  &b1->bounds.ranges, &b2->bounds.ranges))
+				return false;
+			break;
+	}
+
+	return true;
+}
+
+/*
+ * check_new_partition_bound
+ *
+ * Checks if the new partition's bound overlaps any of the existing partitions
+ * of parent.  Also performs additional checks as necessary per strategy.
+ */
+void
+check_new_partition_bound(char *relname, Oid parentId, Node *bound)
+{
+	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
+	Relation		parent;
+	PartitionKey	key;
+	PartitionDesc	pdesc;
+	ParseState	   *pstate = make_parsestate(NULL);
+	int				with = -1;
+	bool			overlap = false;
+
+	parent = heap_open(parentId, NoLock); /* already locked */
+	key = RelationGetPartitionKey(parent);
+	pdesc = RelationGetPartitionDesc(parent);
+	switch (key->strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+		{
+			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
+
+			if (pdesc->nparts > 0)
+			{
+				ListCell   *cell;
+				bool		new_has_null = false;
+				PartitionListInfo	listinfo;
+
+				Assert(pdesc->boundinfo &&
+					   pdesc->boundinfo->strategy == PARTITION_STRATEGY_LIST);
+				listinfo = pdesc->boundinfo->bounds.lists;
+				Assert(listinfo.nvalues > 0 || listinfo.has_null);
+
+				/* If the new partition's list contains null, set a flag. */
+				foreach (cell, spec->listdatums)
+				{
+					Const *val = lfirst(cell);
+
+					if (val->constisnull)
+					{
+						new_has_null = true;
+						break;
+					}
+				}
+
+				/*
+				 * Now check against listinfo whether the new partition's
+				 * values are not already taken by existing partitions.
+				 */
+				if (new_has_null && listinfo.has_null)
+				{
+					with = listinfo.null_index;
+					overlap = true;
+				}
+				else
+				{
+					foreach (cell, spec->listdatums)
+					{
+						Const  *val = lfirst(cell);
+						int		found = -1;
+
+						/* bsearch a new list's value in listinfo->values */
+						if (!val->constisnull)
+							found = partition_list_values_bsearch(key,
+														listinfo.values,
+														listinfo.nvalues,
+														val->constvalue);
+						if (found >= 0)
+						{
+							overlap = true;
+							with = listinfo.indexes[found];
+						}
+					}
+				}
+			}
+
+			break;
+		}
+
+		case PARTITION_STRATEGY_RANGE:
+		{
+			PartitionRangeBound *lower,
+								*upper;
+
+			Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
+			lower = make_one_range_bound(key, -1, spec->lowerdatums, true);
+			upper = make_one_range_bound(key, -1, spec->upperdatums, false);
+
+			/*
+			 * First check if the resulting range would be empty with
+			 * specified bounds
+			 */
+			if (partition_rbound_cmp(key, lower, upper) >= 0)
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("cannot create range partition with empty range"),
+					 parser_errposition(pstate, spec->location)));
+
+			if (pdesc->nparts > 0)
+			{
+				PartitionRangeInfo rangeinfo;
+				int		  idx1, idx2;
+				bool	  equal = false;
+
+				Assert(pdesc->boundinfo &&
+					   pdesc->boundinfo->strategy == PARTITION_STRATEGY_RANGE);
+				rangeinfo = pdesc->boundinfo->bounds.ranges;
+
+				/*
+				 * Find the greatest index of a range bound that is less
+				 * than the new lower bound.
+				 */
+				idx1 = partition_rbound_bsearch(key, rangeinfo.bounds,
+												rangeinfo.nbounds, lower,
+												partition_rbound_cmp, false,
+												&equal);
+
+				/*
+				 * If equal has been set to true, that means the new bound is
+				 * found to be equal to some existing bound.  offset1 == -1
+				 * means that all existing range bounds are greater than the
+				 * new bound.  If offset >= 0, and there exists a gap between
+				 * the corresponding bound and the next, new partition can
+				 * still manage to fit within the same. To confirm whether it
+				 * is so, check the new upper bound.
+				 */
+				if (!equal && (idx1 < 0 || rangeinfo.indexes[idx1+1] < 0))
+				{
+					idx2 = partition_rbound_bsearch(key, rangeinfo.bounds,
+													rangeinfo.nbounds, upper,
+													partition_rbound_cmp,
+													false, &equal);
+
+					if (equal || idx1 != idx2)
+					{
+						overlap = true;
+						with = rangeinfo.indexes[idx2+1];
+					}
+				}
+				else
+				{
+					overlap = true;
+					if (idx1 == -1)
+					{
+						Assert(equal);
+						idx1 = 0;
+					}
+					with = rangeinfo.indexes[idx1+1];
+				}
+			}
+
+			break;
+		}
+	}
+
+	if (overlap)
+	{
+		Assert(with >= 0);
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("partition \"%s\" would overlap partition \"%s\"",
+						relname, get_rel_name(pdesc->oids[with])),
+				 parser_errposition(pstate, spec->location)));
+	}
+
+	heap_close(parent, NoLock);
+}
+
+/*
+ * get_partition_parent
+ *
+ * Returns inheritance parent of a partition by scanning pg_inherits
+ *
+ * Note: Because this function assumes that the relation whose OID is passed
+ * as an argument will have precisely one parent, it should only be called
+ * when it is known that the relation is a partition.
+ */
+Oid
+get_partition_parent(Oid relid)
+{
+	Form_pg_inherits form;
+	Relation	catalogRelation;
+	SysScanDesc scan;
+	ScanKeyData key[2];
+	HeapTuple	tuple;
+	Oid			result;
+
+	catalogRelation = heap_open(InheritsRelationId, AccessShareLock);
+
+	ScanKeyInit(&key[0],
+				Anum_pg_inherits_inhrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(relid));
+	ScanKeyInit(&key[1],
+				Anum_pg_inherits_inhseqno,
+				BTEqualStrategyNumber, F_INT4EQ,
+				Int32GetDatum(1));
+
+	scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true,
+							  NULL, 2, key);
+
+	tuple = systable_getnext(scan);
+	Assert(HeapTupleIsValid(tuple));
+
+	form = (Form_pg_inherits) GETSTRUCT(tuple);
+	result = form->inhparent;
+
+	systable_endscan(scan);
+	heap_close(catalogRelation, AccessShareLock);
+
+	return result;
+}
+
+/*
+ * get_qual_from_partbound
+ *		Given a parser node for partition bound, return the list of executable
+ *		expressions as partition constraint
+ */
+List *
+get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
+{
+	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
+	PartitionKey key = RelationGetPartitionKey(parent);
+	List	   *my_qual = NIL;
+	TupleDesc	parent_tupdesc = RelationGetDescr(parent);
+	AttrNumber	parent_attno;
+	AttrNumber *partition_attnos;
+	bool		found_whole_row;
+
+	Assert(key != NULL);
+
+	switch (key->strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+			Assert(spec->strategy == PARTITION_STRATEGY_LIST);
+			my_qual = get_qual_for_list(key, spec);
+			break;
+
+		case PARTITION_STRATEGY_RANGE:
+			Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
+			my_qual = get_qual_for_range(key, spec);
+			break;
+	}
+
+	/*
+	 * Translate vars in the generated expression to have correct attnos.
+	 * Note that the vars in my_qual bear attnos dictated by key which carries
+	 * physical attnos of the parent.  We must allow for a case where physical
+	 * attnos of a partition can be different from the parent.
+	 */
+	partition_attnos = (AttrNumber *)
+						palloc0(parent_tupdesc->natts * sizeof(AttrNumber));
+	for (parent_attno = 1; parent_attno <= parent_tupdesc->natts;
+		 parent_attno++)
+	{
+		Form_pg_attribute attribute = parent_tupdesc->attrs[parent_attno - 1];
+		char			 *attname = NameStr(attribute->attname);
+		AttrNumber		  partition_attno;
+
+		if (attribute->attisdropped)
+			continue;
+
+		partition_attno = get_attnum(RelationGetRelid(rel), attname);
+		partition_attnos[parent_attno - 1] = partition_attno;
+	}
+
+	my_qual = (List *) map_variable_attnos((Node *) my_qual,
+										   1, 0,
+										   partition_attnos,
+										   parent_tupdesc->natts,
+										   &found_whole_row);
+	/* there can never be a whole-row reference in there*/
+	Assert(!found_whole_row);
+
+	return my_qual;
+}
+
+/*
+ * RelationGetPartitionQual
+ *
+ * Returns a list of partition quals
+ */
+List *
+RelationGetPartitionQual(Relation rel, bool recurse)
+{
+	/* Quick exit */
+	if (!rel->rd_rel->relispartition)
+		return NIL;
+
+	return generate_partition_qual(rel, recurse);
+}
+
+/* Module-local functions */
+
+/*
+ * get_qual_for_list
+ *
+ * Returns a list of expressions to use as a list partition's constraint.
+ */
+static List *
+get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
+{
+	List		*result;
+	ArrayExpr	*arr;
+	ScalarArrayOpExpr  *opexpr;
+	ListCell	*cell,
+				*prev,
+				*next;
+	Node   *key_col;
+	Oid		operoid;
+	bool	need_relabel,
+			list_has_null = false;
+	NullTest *nulltest1 = NULL,
+			 *nulltest2 = NULL;
+
+	/* Left operand is either a simple Var or arbitrary expression */
+	if (key->partattrs[0] != 0)
+		key_col = (Node *) makeVar(1,
+								   key->partattrs[0],
+								   key->parttypid[0],
+								   key->parttypmod[0],
+								   key->parttypcoll[0],
+								   0);
+	else
+		key_col = (Node *) copyObject(linitial(key->partexprs));
+
+	/*
+	 * If the list does not accept nulls, we must add a IS NOT NULL test.
+	 * If it does, remove the null Const from the list and create a IS NULL
+	 * test and create an OR expression along with ScalarArrayOpExpr. We do
+	 * it this way because the null-valued expression does not have the
+	 * desired behavior when used within ScalarArrayOpExpr or OpExpr.
+	 */
+	prev = NULL;
+	for (cell = list_head(spec->listdatums); cell; cell = next)
+	{
+		Const	*val = (Const *) lfirst(cell);
+
+		next = lnext(cell);
+
+		if (val->constisnull)
+		{
+			list_has_null = true;
+			spec->listdatums = list_delete_cell(spec->listdatums,
+												 cell, prev);
+		}
+		else
+			prev = cell;
+	}
+
+	if (!list_has_null)
+	{
+		/* Gin up a col IS NOT NULL test */
+		nulltest1 = makeNode(NullTest);
+		nulltest1->arg = (Expr *) key_col;
+		nulltest1->nulltesttype = IS_NOT_NULL;
+		nulltest1->argisrow = false;
+		nulltest1->location = -1;
+	}
+	else
+	{
+		/* Gin up a col IS NULL test */
+		nulltest2 = makeNode(NullTest);
+		nulltest2->arg = (Expr *) key_col;
+		nulltest2->nulltesttype = IS_NULL;
+		nulltest2->argisrow = false;
+		nulltest2->location = -1;
+	}
+
+	/* Right operand is an ArrayExpr */
+	arr = makeNode(ArrayExpr);
+	arr->array_typeid = !type_is_array(key->parttypid[0])
+							? get_array_type(key->parttypid[0])
+							: key->parttypid[0];
+	arr->array_collid = key->parttypcoll[0];
+	arr->element_typeid = key->parttypid[0];
+	arr->elements = spec->listdatums;
+	arr->multidims = false;
+	arr->location = -1;
+
+	/* Get the correct btree equality operator */
+	operoid = get_partition_operator(key, 0, BTEqualStrategyNumber,
+									 &need_relabel);
+	if (need_relabel || key->partcollation[0] != key->parttypcoll[0])
+		key_col = (Node *) makeRelabelType((Expr *) key_col,
+										   key->partopcintype[0],
+										   -1,
+										   key->partcollation[0],
+										   COERCE_EXPLICIT_CAST);
+
+	/* 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(key_col, arr);
+	opexpr->location = -1;
+
+	if (nulltest1)
+		result = list_make2(nulltest1, opexpr);
+	else if (nulltest2)
+	{
+		Expr *or;
+
+		or = makeBoolExpr(OR_EXPR, list_make2(nulltest2, opexpr), -1);
+		result = list_make1(or);
+	}
+	else
+		result = list_make1(opexpr);
+
+	return result;
+}
+
+/*
+ * get_qual_for_range
+ *
+ * Get a list of OpExpr's to use as a range partition's constraint.
+ *
+ * For each column, we also emit a IS NOT NULL test since we do not
+ * allow null values in range partition key.
+ */
+static List *
+get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
+{
+	List	   *result = NIL;
+	ListCell   *cell1,
+			   *cell2,
+			   *partexprs_item;
+	int			i;
+
+	/*
+	 * Iterate over columns of the key, emitting an OpExpr for each using
+	 * the corresponding lower and upper datums as constant operands.
+	 */
+	i = 0;
+	partexprs_item = list_head(key->partexprs);
+	forboth (cell1, spec->lowerdatums, cell2, spec->upperdatums)
+	{
+		PartitionRangeDatum *ldatum = lfirst(cell1),
+							*udatum = lfirst(cell2);
+		Node		   *key_col;
+		Const		   *lower_val = NULL,
+					   *upper_val = NULL;
+		EState		   *estate;
+		MemoryContext	oldcxt;
+		Expr		   *test_expr;
+		ExprState	   *test_exprstate;
+		Datum			test_result;
+		bool 			isNull;
+		bool			need_relabel = false;
+		NullTest	   *nulltest;
+		Oid			operoid;
+
+		/* Left operand */
+		if (key->partattrs[i] != 0)
+		{
+			key_col = (Node *) makeVar(1,
+									   key->partattrs[i],
+									   key->parttypid[i],
+									   key->parttypmod[i],
+									   key->parttypcoll[i],
+									   0);
+		}
+		else
+		{
+			key_col = (Node *) copyObject(lfirst(partexprs_item));
+			partexprs_item = lnext(partexprs_item);
+		}
+
+		/* Gin up a col IS NOT NULL test */
+		nulltest = makeNode(NullTest);
+		nulltest->arg = (Expr *) key_col;
+		nulltest->nulltesttype = IS_NOT_NULL;
+		nulltest->argisrow = false;
+		nulltest->location = -1;
+		result = lappend(result, nulltest);
+
+		/*
+		 * Stop at this column if either of lower or upper datum is infinite,
+		 * but do emit an OpExpr for the non-infinite datum.
+		 */
+		if (!ldatum->infinite)
+			lower_val = (Const *) ldatum->value;
+		if (!udatum->infinite)
+			upper_val = (Const *) udatum->value;
+
+		/*
+		 * If lower_val and upper_val are both finite and happen to be equal,
+		 * emit only (key_col = lower_val) for this column, because all rows
+		 * in this partition could only ever contain this value (ie, lower_val)
+		 * in the current partitioning column.  We must consider further
+		 * columns because the above condition does not fully constrain the
+		 * rows of this partition.
+		 */
+		if (lower_val && upper_val)
+		{
+			/* Get the correct btree equality operator for the test */
+			operoid = get_partition_operator(key, i, BTEqualStrategyNumber,
+											 &need_relabel);
+
+			/* Create the test expression */
+			estate = CreateExecutorState();
+			oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+			test_expr = make_opclause(operoid,
+									  BOOLOID,
+									  false,
+									  (Expr *) lower_val,
+									  (Expr *) upper_val,
+									  InvalidOid,
+									  key->partcollation[i]);
+			fix_opfuncids((Node *) test_expr);
+			test_exprstate = ExecInitExpr(test_expr, NULL);
+			test_result = ExecEvalExprSwitchContext(test_exprstate,
+													GetPerTupleExprContext(estate),
+													&isNull, NULL);
+			MemoryContextSwitchTo(oldcxt);
+			FreeExecutorState(estate);
+
+			if (DatumGetBool(test_result))
+			{
+				/* This can never be, but it's better to make sure */
+				if (i == key->partnatts - 1)
+					elog(ERROR, "invalid range bound specification");
+
+				if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+					key_col = (Node *) makeRelabelType((Expr *) key_col,
+													   key->partopcintype[i],
+													   -1,
+													   key->partcollation[i],
+													   COERCE_EXPLICIT_CAST);
+				result = lappend(result,
+									make_opclause(operoid,
+										  BOOLOID,
+										  false,
+										  (Expr *) key_col,
+										  (Expr *) lower_val,
+										  InvalidOid,
+										  key->partcollation[i]));
+
+				/* Go over to consider the next column. */
+				i++;
+				continue;
+			}
+		}
+
+		/*
+		 * We can say here that lower_val <> upper_val.  Emit expressions
+		 * (key_col >= lower_val) and (key_col < upper_val), then stop.
+		 */
+		if (lower_val)
+		{
+			operoid = get_partition_operator(key, i,
+											 BTGreaterEqualStrategyNumber,
+											 &need_relabel);
+
+			if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+				key_col = (Node *) makeRelabelType((Expr *) key_col,
+												   key->partopcintype[i],
+												   -1,
+												   key->partcollation[i],
+												   COERCE_EXPLICIT_CAST);
+			result = lappend(result,
+						make_opclause(operoid,
+									  BOOLOID,
+									  false,
+									  (Expr *) key_col,
+									  (Expr *) lower_val,
+									  InvalidOid,
+									  key->partcollation[i]));
+		}
+
+		if (upper_val)
+		{
+			operoid = get_partition_operator(key, i,
+											 BTLessStrategyNumber,
+											 &need_relabel);
+
+			if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+				key_col = (Node *) makeRelabelType((Expr *) key_col,
+												   key->partopcintype[i],
+												   -1,
+												   key->partcollation[i],
+												   COERCE_EXPLICIT_CAST);
+
+			result = lappend(result,
+						make_opclause(operoid,
+									  BOOLOID,
+									  false,
+									  (Expr *) key_col,
+									  (Expr *) upper_val,
+									  InvalidOid,
+									  key->partcollation[i]));
+		}
+
+		/*
+		 * We can stop at this column, because we would not have checked
+		 * the next column when routing a given row into this partition.
+		 */
+		break;
+	}
+
+	return result;
+}
+
+/*
+ * get_partition_operator
+ *
+ * Return oid of the operator of given strategy for a given partition key
+ * column.
+ */
+static Oid
+get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
+					   bool *need_relabel)
+{
+	Oid		operoid;
+
+	/*
+	 * First check if there exists an operator of the given strategy, with
+	 * this column's type as both its lefttype and righttype, in the
+	 * partitioning operator family specified for the column.
+	 */
+	operoid = get_opfamily_member(key->partopfamily[col],
+								  key->parttypid[col],
+								  key->parttypid[col],
+								  strategy);
+
+	/*
+	 * If one doesn't exist, we must resort to using an operator in the same
+	 * opreator family but with the operator class declared input type.  It is
+	 * OK to do so, because the column's type is known to be binary-coercible
+	 * with the operator class input type (otherwise, the operator class in
+	 * question would not have been accepted as the partitioning operator
+	 * class).  We must however inform the caller to wrap the non-Const
+	 * expression with a RelabelType node to denote the implicit coercion. It
+	 * ensures that the resulting expression structurally matches similarly
+	 * processed expressions within the optimizer.
+	 */
+	if (!OidIsValid(operoid))
+	{
+		operoid = get_opfamily_member(key->partopfamily[col],
+									  key->partopcintype[col],
+									  key->partopcintype[col],
+									  strategy);
+		*need_relabel = true;
+	}
+	else
+		*need_relabel = false;
+
+	if (!OidIsValid(operoid))
+		elog(ERROR, "could not find operator for partitioning");
+
+	return operoid;
+}
+
+/*
+ * generate_partition_qual
+ *
+ * Generate partition predicate from rel's partition bound expression
+ *
+ * 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
+ * working context. To avoid leaking cache memory if this routine fails partway
+ * through, we build in working memory and then copy the completed structure
+ * into cache memory.
+ */
+static List *
+generate_partition_qual(Relation rel, bool recurse)
+{
+	HeapTuple		tuple;
+	MemoryContext	oldcxt;
+	Datum		boundDatum;
+	bool		isnull;
+	Node	   *bound;
+	List	   *my_qual = NIL,
+			   *result = NIL;
+	Relation	parent;
+
+	/* Guard against stack overflow due to overly deep partition tree */
+	check_stack_depth();
+
+	/* Grab at least an AccessShareLock on the parent table */
+	parent = heap_open(get_partition_parent(RelationGetRelid(rel)),
+					   AccessShareLock);
+
+	/* Quick copy */
+	if (rel->rd_partcheck)
+	{
+		if (parent->rd_rel->relispartition && recurse)
+			result = list_concat(generate_partition_qual(parent, true),
+								 copyObject(rel->rd_partcheck));
+		else
+			result = copyObject(rel->rd_partcheck);
+
+		heap_close(parent, AccessShareLock);
+		return result;
+	}
+
+	/* Get pg_class.relpartbound */
+	Assert(rel->rd_rel->relispartition);
+	tuple = SearchSysCache1(RELOID, RelationGetRelid(rel));
+	boundDatum = SysCacheGetAttr(RELOID, tuple,
+								 Anum_pg_class_relpartbound,
+								 &isnull);
+	Assert(!isnull);
+	bound = stringToNode(TextDatumGetCString(boundDatum));
+	ReleaseSysCache(tuple);
+
+	my_qual = get_qual_from_partbound(rel, parent, bound);
+
+	/* If requested, add parent's quals to the list (if any) */
+	if (parent->rd_rel->relispartition && recurse)
+	{
+		List   *parent_check;
+
+		parent_check = generate_partition_qual(parent, true);
+		result = list_concat(parent_check, my_qual);
+	}
+	else
+		result = my_qual;
+
+	/* Save a copy of my_qual in the relcache */
+	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	rel->rd_partcheck = copyObject(my_qual);
+	MemoryContextSwitchTo(oldcxt);
+
+	/* Keep the parent locked until commit */
+	heap_close(parent, NoLock);
+
+	return result;
+}
+
+/* List partition related support functions */
+
+/*
+ * Return whether two list partition bound collections are logically equal
+ */
+static bool
+equal_list_info(PartitionKey key,
+				PartitionListInfo *l1, PartitionListInfo *l2)
+{
+	int		i;
+
+	if (l1->nvalues != l2->nvalues)
+		return false;
+
+	if (l1->has_null != l2->has_null)
+		return false;
+
+	for (i = 0; i < l1->nvalues; i++)
+	{
+		if (partition_list_values_cmp(key, l1->values[i], l2->values[i]))
+			return false;
+
+		if(l1->indexes[i] != l2->indexes[i])
+			return false;
+	}
+
+	if (l1->null_index != l2->null_index)
+		return false;
+
+	return true;
+}
+
+/* Compare two list value datums */
+static int32
+partition_list_values_cmp(PartitionKey key, Datum val1, Datum val2)
+{
+	return DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+										   key->partcollation[0],
+										   val1, val2));
+}
+
+/*
+ * qsort_partition_list_value_cmp
+ *
+ * Compare two list values
+ */
+static int32
+qsort_partition_list_value_cmp(const void *a, const void *b, void *arg)
+{
+	Datum			value1 = (*(const PartitionListValue **) a)->value,
+					value2 = (*(const PartitionListValue **) b)->value;
+	PartitionKey	key = (PartitionKey) arg;
+
+	return partition_list_values_cmp(key, value1, value2);
+}
+
+/* Binary search for list partition values; returns -1 if not found */
+static int
+partition_list_values_bsearch(PartitionKey key, const Datum *values, int n,
+							  const Datum probe)
+{
+	int		lo,
+			hi;
+
+	lo = 0;
+	hi = n - 1;
+	while (lo <= hi)
+	{
+		int		mid = (lo + hi) / 2;
+		int32	res = partition_list_values_cmp(key, probe, values[mid]);
+
+		if (res < 0)
+			hi = mid - 1;
+		else if (res > 0)
+			lo = mid + 1;
+		else
+			return mid;
+	}
+
+	return -1;
+}
+
+/* Range partition related support functions */
+
+/*
+ * Return a PartitionRangeBound given a list of PartitionRangeDatum elements
+ * and a flag telling whether the bound is lower or not.
+ */
+static PartitionRangeBound *
+make_one_range_bound(PartitionKey key, int index, List *datums, bool lower)
+{
+	PartitionRangeBound *bound;
+	ListCell *cell;
+	int		i;
+
+	bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+	bound->index = index;
+	bound->datums = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+	bound->infinite = (bool *) palloc0(key->partnatts * sizeof(bool));
+	bound->lower = lower;
+
+	i = 0;
+	foreach (cell, datums)
+	{
+		PartitionRangeDatum *datum = lfirst(cell);
+
+		bound->infinite[i] = datum->infinite;
+
+		if (!bound->infinite[i])
+		{
+			Const	*val = (Const *) datum->value;
+
+			if (val->constisnull)
+				elog(ERROR, "invalid range bound datum");
+			bound->datums[i] = val->constvalue;
+		}
+
+		i++;
+	}
+
+	return bound;
+}
+
+/*
+ * Return a copy of input PartitionRangeBound
+ */
+static PartitionRangeBound *
+copy_range_bound(PartitionKey key, PartitionRangeBound *src)
+{
+	int		i;
+	int		partnatts = key->partnatts;
+	PartitionRangeBound  *result;
+
+	result = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+	result->index = src->index;
+	result->datums = (Datum *) palloc0(partnatts * sizeof(Datum));
+	result->infinite = (bool *) palloc0(partnatts * sizeof(bool));
+	result->lower = src->lower;
+
+	for (i = 0; i < partnatts; i++)
+	{
+		result->infinite[i] = src->infinite[i];
+		if (!result->infinite[i])
+			result->datums[i] = datumCopy(src->datums[i],
+										  key->parttypbyval[i],
+										  key->parttyplen[i]);
+	}
+
+	return result;
+}
+
+/*
+ * Return whether two range partition bound collections are logically equal
+ */
+static bool
+equal_range_info(PartitionKey key,
+				 PartitionRangeInfo *r1, PartitionRangeInfo *r2)
+{
+	int		i;
+
+	if (r1->nbounds != r2->nbounds)
+		return false;
+
+	for (i = 0; i < r1->nbounds; i++)
+	{
+		if (partition_rbound_cmp(key, r1->bounds[i], r2->bounds[i]) != 0)
+			return false;
+
+		if (r1->indexes[i] != r2->indexes[i])
+			return false;
+	}
+
+	/* There is one more index than bounds */
+	if (r1->indexes[i] != r2->indexes[i])
+		return false;
+
+	return true;
+}
+
+/*
+ * Return for two range bounds whether the 1st one is <=, =, >= the 2nd
+ *
+ * The 3rd argument is void * so that it can be used with
+ * partition_rbound_bsearch()
+ */
+static int32
+partition_rbound_cmp(PartitionKey key, PartitionRangeBound *b1, void *arg)
+{
+	PartitionRangeBound *b2 = (PartitionRangeBound *) arg;
+	Datum  *datums1 = b1->datums,
+		   *datums2 = b2->datums;
+	int32	cmpval;
+	int		i;
+
+	for (i = 0; i < key->partnatts; i++)
+	{
+		/*
+		 * First, handle cases involving infinity, which don't require
+		 * invoking the comparison proc.
+		 */
+		if (b1->infinite[i] && b2->infinite[i])
+		{
+			/*
+			 * Both are infinity, so they are equal unless one is lower and
+			 * the other not.
+			 */
+			if (b1->lower == b2->lower)
+				return 0;
+			else
+				return b1->lower ? -1 : 1;
+		}
+		else if (b1->infinite[i])
+			return b1->lower ? -1 : 1;
+		else if (b2->infinite[i])
+			return b2->lower ? 1 : -1;
+
+		cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+												 key->partcollation[i],
+												 datums1[i], datums2[i]));
+		if (cmpval != 0)
+			break;
+	}
+
+	/*
+	 * If the comparison is anything other than equal, we're done. If they
+	 * compare equal though, we still have to consider whether the boundaries
+	 * are inclusive or exclusive.  Remember lower bounds are inclusive.
+	 */
+	if (cmpval == 0)
+	{
+		/*
+		 * If both are either inclusive or exclusive, they are trivially
+		 * equal
+		 */
+		if (b1->lower == b2->lower)
+			return 0;
+		/* Exclusive one is smaller of the two */
+		else
+			return b1->lower ? 1 : -1;
+	}
+
+	return cmpval;
+}
+
+/*
+ * Return whether two range bounds are equal simply by comparing datums
+ */
+static bool
+partition_rbound_eq(PartitionKey key,
+					PartitionRangeBound *b1, PartitionRangeBound *b2)
+{
+	Datum  *datums1 = b1->datums,
+		   *datums2 = b2->datums;
+	int		i;
+
+	for (i = 0; i < key->partnatts; i++)
+	{
+		int32	cmpval = 0;
+
+		/*
+		 * If either of them has infinite element, we can't equate them.  Even
+		 * if both were infinite, they'd have opposite signs (it's always true
+		 * that b1 and b2 are different types of bounds).
+		 */
+		if (b1->infinite[i] || b2->infinite[i])
+			return false;
+
+		cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+												 key->partcollation[i],
+												 datums1[i], datums2[i]));
+		if (cmpval != 0)
+			return false;
+	}
+
+	return true;
+}
+
+/* Used when sorting range bounds across all range partitions */
+static int32
+qsort_partition_rbound_cmp(const void *a, const void *b, void *arg)
+{
+	PartitionRangeBound *b1 = (*(PartitionRangeBound *const *) a);
+	PartitionRangeBound *b2 = (*(PartitionRangeBound *const *) b);
+	PartitionKey key = (PartitionKey) arg;
+
+	return partition_rbound_cmp(key, b1, b2);
+}
+
+/*
+ * Binary search on an array of range bounds. Returns greatest index of range
+ * bound in array which is less (less or equal) than given range bound. If all
+ * range bounds in array are greater or equal (greater) than given range
+ * bound, return -1.
+ *
+ * cmp is a custom comparison function basically concerned with whether probe
+ * is a partition bound (PartitionRangeBound) or a simple Datum array
+ * containing partition key of a tuple.  The first argument is always a
+ * range bound.
+ *
+ * *is_equal returns whether the concluding comparison returned equal.
+ */
+static int
+partition_rbound_bsearch(PartitionKey key, PartitionRangeBound **bounds, int n,
+						 void *probe, partition_rbound_bsearch_cmp_fn cmp,
+						 bool equal_allowed, bool *is_equal)
+{
+	int		lo, hi, mid;
+
+	lo = -1;
+	hi = n - 1;
+	while (lo < hi)
+	{
+		PartitionRangeBound *cur;
+		int32	cmpval;
+
+		mid = (lo + hi + 1) / 2;
+		cur = bounds[mid];
+
+		cmpval = cmp(key, cur, probe);
+		if (is_equal)
+			*is_equal = (cmpval == 0);
+
+		if (cmpval < 0 || (equal_allowed && cmpval == 0))
+			lo = mid;
+		else
+			hi = mid - 1;
+	}
+
+	return lo;
+}
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 5b4f6af..d6d52d9 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -112,7 +112,7 @@ create_ctas_internal(List *attrList, IntoClause *into)
 	 * Create the relation.  (This will error out if there's an existing view,
 	 * so we don't need more code to complain if "replace" is false.)
 	 */
-	intoRelationAddr = DefineRelation(create, relkind, InvalidOid, NULL);
+	intoRelationAddr = DefineRelation(create, relkind, InvalidOid, NULL, NULL);
 
 	/*
 	 * If necessary, create a TOAST table for the target table.  Note that
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index e08fd5d..d4a1f01 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -234,7 +234,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	stmt->tablespacename = NULL;
 	stmt->if_not_exists = seq->if_not_exists;
 
-	address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL);
+	address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL);
 	seqoid = address.objectId;
 	Assert(seqoid != InvalidOid);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1ddf443..3b72ae3 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -29,6 +29,7 @@
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
@@ -65,6 +66,8 @@
 #include "nodes/parsenodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planner.h"
+#include "optimizer/predtest.h"
+#include "optimizer/prep.h"
 #include "optimizer/var.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
@@ -163,6 +166,7 @@ typedef struct AlteredTableInfo
 	Oid			newTableSpace;	/* new tablespace; 0 means no change */
 	bool		chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
 	char		newrelpersistence;		/* if above is true */
+	List	   *partition_constraint; /* for attach partition validation */
 	/* Objects to rebuild after completing ALTER TYPE operations */
 	List	   *changedConstraintOids;	/* OIDs of constraints to rebuild */
 	List	   *changedConstraintDefs;	/* string definitions of same */
@@ -279,7 +283,8 @@ struct DropRelationCallbackState
 
 static void truncate_check_rel(Relation rel);
 static List *MergeAttributes(List *schema, List *supers, char relpersistence,
-				List **supOids, List **supconstr, int *supOidCount);
+				bool is_partition, List **supOids, List **supconstr,
+				int *supOidCount);
 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);
@@ -346,7 +351,9 @@ static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid);
 static void add_column_collation_dependency(Oid relid, int32 attnum, Oid collid);
 static void ATPrepAddOids(List **wqueue, Relation rel, bool recurse,
 			  AlterTableCmd *cmd, LOCKMODE lockmode);
+static void ATPrepDropNotNull(Relation rel, bool recurse, bool recursing);
 static ObjectAddress ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode);
+static void ATPrepSetNotNull(Relation rel, bool recurse, bool recursing);
 static ObjectAddress ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
 				 const char *colName, LOCKMODE lockmode);
 static ObjectAddress ATExecColumnDefault(Relation rel, const char *colName,
@@ -444,6 +451,11 @@ static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_exp
 static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy);
 static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
 					  List **partexprs, Oid *partopclass, Oid *partcollation);
+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);
 
 
 /* ----------------------------------------------------------------
@@ -466,7 +478,7 @@ static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *pa
  */
 ObjectAddress
 DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
-			   ObjectAddress *typaddress)
+			   ObjectAddress *typaddress, const char *queryString)
 {
 	char		relname[NAMEDATALEN];
 	Oid			namespaceId;
@@ -597,6 +609,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 */
 	schema = MergeAttributes(schema, stmt->inhRelations,
 							 stmt->relation->relpersistence,
+							 stmt->partbound != NULL,
 							 &inheritOids, &old_constraints, &parentOidCount);
 
 	/*
@@ -607,18 +620,33 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	descriptor = BuildDescForRelation(schema);
 
 	/*
-	 * Notice that we allow OIDs here only for plain tables, even though some
-	 * other relkinds can support them.  This is necessary because the
-	 * default_with_oids GUC must apply only to plain tables and not any other
-	 * relkind; doing otherwise would break existing pg_dump files.  We could
-	 * allow explicit "WITH OIDS" while not allowing default_with_oids to
-	 * affect other relkinds, but it would complicate interpretOidsOption().
+	 * Notice that we allow OIDs here only for plain tables and partitioned
+	 * tables, even though some other relkinds can support them.  This is
+	 * necessary because the default_with_oids GUC must apply only to plain
+	 * tables and not any other relkind; doing otherwise would break existing
+	 * pg_dump files.  We could allow explicit "WITH OIDS" while not allowing
+	 * default_with_oids to affect other relkinds, but it would complicate
+	 * interpretOidsOption().
 	 */
 	localHasOids = interpretOidsOption(stmt->options,
 									   (relkind == RELKIND_RELATION ||
 										relkind == RELKIND_PARTITIONED_TABLE));
 	descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
 
+	if (stmt->partbound)
+	{
+		/* If the parent has OIDs, partitions must have them too. */
+		if (parentOidCount > 0 && !localHasOids)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot create table without OIDs as partition of table with OIDs")));
+		/* If the parent doesn't, partitions must not have them. */
+		if (parentOidCount == 0 && localHasOids)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot create table with OIDs as partition of table without OIDs")));
+	}
+
 	/*
 	 * Find columns with default values and prepare for insertion of the
 	 * defaults.  Pre-cooked (that is, inherited) defaults go into a list of
@@ -717,6 +745,51 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 */
 	rel = relation_open(relationId, AccessExclusiveLock);
 
+	/* Process and store partition bound, if any. */
+	if (stmt->partbound)
+	{
+		Node	   *bound;
+		ParseState *pstate;
+		Oid			parentId = linitial_oid(inheritOids);
+		Relation	parentRel;
+
+		/* Already have strong enough lock on the parent */
+		parentRel = heap_open(parentId, NoLock);
+
+		/*
+		 * We are going to try to validate the partition bound specification
+		 * against the partition key of parentRel, so it better have one.
+		 */
+		if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("\"%s\" is not partitioned",
+							RelationGetRelationName(parentRel))));
+
+		/* Tranform the bound values */
+		pstate = make_parsestate(NULL);
+		pstate->p_sourcetext = queryString;
+		bound = transformPartitionBound(pstate, parentRel, stmt->partbound);
+		heap_close(parentRel, NoLock);
+
+		/*
+		 * Check first that the new partition's bound is valid and does not
+		 * overlap with any of existing partitions of the parent - note that
+		 * it does not return on error.
+		 */
+		check_new_partition_bound(relname, parentId, bound);
+
+		/* Update the pg_class entry. */
+		StorePartitionBound(rel, bound);
+
+		/*
+		 * The code that follows may also update the pg_class tuple to update
+		 * relnumchecks, so bump up the command counter to avoid the "already
+		 * updated by self" error.
+		 */
+		CommandCounterIncrement();
+	}
+
 	/*
 	 * Process the partitioning specification (if any) and store the
 	 * partition key information into the catalog.
@@ -1117,6 +1190,10 @@ ExecuteTruncate(TruncateStmt *stmt)
 				relids = lappend_oid(relids, childrelid);
 			}
 		}
+		else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("must truncate child tables too")));
 	}
 
 	/*
@@ -1423,6 +1500,7 @@ storage_name(char c)
  *		of ColumnDef's.) It is destructively changed.
  * 'supers' is a list of names (as RangeVar nodes) of parent relations.
  * 'relpersistence' is a persistence type of the table.
+ * 'is_partition' tells if the table is a partition
  *
  * Output arguments:
  * 'supOids' receives a list of the OIDs of the parent relations.
@@ -1474,7 +1552,8 @@ storage_name(char c)
  */
 static List *
 MergeAttributes(List *schema, List *supers, char relpersistence,
-				List **supOids, List **supconstr, int *supOidCount)
+				bool is_partition, List **supOids, List **supconstr,
+				int *supOidCount)
 {
 	ListCell   *entry;
 	List	   *inhSchema = NIL;
@@ -1484,6 +1563,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	bool		have_bogus_defaults = false;
 	int			child_attno;
 	static Node bogus_marker = {0};		/* marks conflicting defaults */
+	List	   *saved_schema = NIL;
 
 	/*
 	 * Check for and reject tables with too many columns. We perform this
@@ -1503,6 +1583,18 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 						MaxHeapAttributeNumber)));
 
 	/*
+	 * In case of a partition, there are no new column definitions, only
+	 * column options specified using the WITH OPTIONS clauses.  We merge
+	 * those options with actual column definitions after we have finished
+	 * generating them from the parent's schema.
+	 */
+	if (is_partition)
+	{
+		saved_schema = schema;
+		schema = NIL;
+	}
+
+	/*
 	 * Check for duplicate names in the explicit list of attributes.
 	 *
 	 * Although we might consider merging such entries in the same way that we
@@ -1582,18 +1674,35 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		 * on the parent table, which might otherwise be attempting to clear
 		 * the parent's relhassubclass field, if its previous children were
 		 * recently dropped.
+		 *
+		 * If the child table is a partition, then we instead grab an exclusive
+		 * lock on the parent because its partition descriptor will be changed
+		 * by addition of the new partition.
 		 */
-		relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+		if (!is_partition)
+			relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+		else
+			relation = heap_openrv(parent, AccessExclusiveLock);
 
-		/* Cannot inherit from partitioned tables */
-		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		/*
+		 * We do not allow partitioned tables and partitions to participate
+		 * in regular inheritance.
+		 */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+			!is_partition)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot inherit from partitioned table \"%s\"",
 							parent->relname)));
+		if (relation->rd_rel->relispartition && !is_partition)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot inherit from partition \"%s\"",
+							parent->relname)));
 
 		if (relation->rd_rel->relkind != RELKIND_RELATION &&
-			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+			relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("inherited relation \"%s\" is not a table or foreign table",
@@ -1603,7 +1712,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 			relation->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("cannot inherit from temporary relation \"%s\"",
+					 errmsg(!is_partition
+							? "cannot inherit from temporary relation \"%s\""
+							: "cannot create as partition of temporary relation \"%s\"",
 							parent->relname)));
 
 		/* If existing rel is temp, it must belong to this session */
@@ -1611,7 +1722,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 			!relation->rd_islocaltemp)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("cannot inherit from temporary relation of another session")));
+					 errmsg(!is_partition
+							? "cannot inherit from temporary relation of another session"
+							: "cannot create as partition of temporary relation of another session")));
 
 		/*
 		 * We should have an UNDER permission flag for this, but for now,
@@ -1858,7 +1971,8 @@ 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.
+	 * columns into the inherited schema list.  Although, we never have any
+	 * explicitly declared columns if the table is a partition.
 	 */
 	if (inhSchema != NIL)
 	{
@@ -1887,6 +2001,12 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 							newcollid;
 
 				/*
+				 * Partitions have only one parent, so conflict should never
+				 * occur
+				 */
+				Assert(!is_partition);
+
+				/*
 				 * Yes, try to merge the two column definitions. They must
 				 * have the same type, typmod, and collation.
 				 */
@@ -1968,6 +2088,56 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	}
 
 	/*
+	 * Now that we have the column definition list for a partition, we can
+	 * check whether the columns referenced in column option specifications
+	 * actually exist.  Also, we merge the options into the corresponding
+	 * column definitions.
+	 */
+	if (is_partition && list_length(saved_schema) > 0)
+	{
+		schema = list_concat(schema, saved_schema);
+
+		foreach(entry, schema)
+		{
+			ColumnDef  *coldef = lfirst(entry);
+			ListCell   *rest = lnext(entry);
+			ListCell   *prev = entry;
+
+			/*
+			 * Partition column option that does not belong to a column from
+			 * the parent.  This works because the columns from the parent
+			 * come first in the list (see above).
+			 */
+			if (coldef->typeName == NULL)
+				ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_COLUMN),
+					 errmsg("column \"%s\" does not exist",
+							coldef->colname)));
+			while (rest != NULL)
+			{
+				ColumnDef  *restdef = lfirst(rest);
+				ListCell   *next = lnext(rest);		/* need to save it in case
+													 * we delete it */
+
+				if (strcmp(coldef->colname, restdef->colname) == 0)
+				{
+					/*
+					 * 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;
+					list_delete_cell(schema, rest, prev);
+				}
+				prev = rest;
+				rest = next;
+			}
+		}
+	}
+
+	/*
 	 * If we found any conflicting parent default values, check to make sure
 	 * they were overridden by the child.
 	 */
@@ -3129,6 +3299,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);
@@ -3240,12 +3415,14 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 			break;
 		case AT_DropNotNull:	/* ALTER COLUMN DROP NOT NULL */
 			ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+			ATPrepDropNotNull(rel, recurse, recursing);
 			ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
 			/* No command-specific prep needed */
 			pass = AT_PASS_DROP;
 			break;
 		case AT_SetNotNull:		/* ALTER COLUMN SET NOT NULL */
 			ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+			ATPrepSetNotNull(rel, recurse, recursing);
 			ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
 			/* No command-specific prep needed */
 			pass = AT_PASS_ADD_CONSTR;
@@ -3446,6 +3623,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);
@@ -3516,7 +3699,14 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
 	{
 		AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
 
-		if (tab->relkind == RELKIND_RELATION ||
+		/*
+		 * If the table is source table of ATTACH PARTITION command, we did
+		 * not modify anything about it that will change its toasting
+		 * requirement, so no need to check.
+		 */
+		if (((tab->relkind == RELKIND_RELATION ||
+			  tab->relkind == RELKIND_PARTITIONED_TABLE) &&
+			  tab->partition_constraint == NIL) ||
 			tab->relkind == RELKIND_MATVIEW)
 			AlterTableCreateToastTable(tab->relid, (Datum) 0, lockmode);
 	}
@@ -3765,6 +3955,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);
@@ -3950,7 +4146,8 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
 			 * Test the current data within the table against new constraints
 			 * generated by ALTER TABLE commands, but don't rebuild data.
 			 */
-			if (tab->constraints != NIL || tab->new_notnull)
+			if (tab->constraints != NIL || tab->new_notnull ||
+				tab->partition_constraint != NIL)
 				ATRewriteTable(tab, InvalidOid, lockmode);
 
 			/*
@@ -4030,6 +4227,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	CommandId	mycid;
 	BulkInsertState bistate;
 	int			hi_options;
+	List	   *partqualstate = NIL;
 
 	/*
 	 * Open the relation(s).  We have surely already locked the existing
@@ -4094,6 +4292,15 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		}
 	}
 
+	/* Build expression execution states for partition check quals */
+	if (tab->partition_constraint)
+	{
+		needscan = true;
+		partqualstate = (List *)
+						ExecPrepareExpr((Expr *) tab->partition_constraint,
+										estate);
+	}
+
 	foreach(l, tab->newvals)
 	{
 		NewColumnValue *ex = lfirst(l);
@@ -4283,6 +4490,11 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				}
 			}
 
+			if (partqualstate && !ExecQual(partqualstate, econtext, true))
+				ereport(ERROR,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("partition constraint is violated by some row")));
+
 			/* Write the tuple out to the new relation */
 			if (newrel)
 				heap_insert(newrel, tuple, mycid, hi_options, bistate);
@@ -4480,7 +4692,8 @@ ATSimpleRecursion(List **wqueue, Relation rel,
 	 */
 	if (recurse &&
 		(rel->rd_rel->relkind == RELKIND_RELATION ||
-		 rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
+		 rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+		 rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE))
 	{
 		Oid			relid = RelationGetRelid(rel);
 		ListCell   *child;
@@ -4802,6 +5015,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	if (recursing)
 		ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
 
+	if (rel->rd_rel->relispartition && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot add column to a partition")));
+
 	attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
 
 	/*
@@ -5248,6 +5466,20 @@ ATPrepAddOids(List **wqueue, Relation rel, bool recurse, AlterTableCmd *cmd, LOC
  * Return the address of the modified column.  If the column was already
  * nullable, InvalidObjectAddress is returned.
  */
+
+static void
+ATPrepDropNotNull(Relation rel, bool recurse, bool recursing)
+{
+	/*
+	 * If the parent is a partitioned table, like check constraints, NOT NULL
+	 * constraints must be dropped from child tables.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+		!recurse && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("constraint must be dropped from child tables too")));
+}
 static ObjectAddress
 ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 {
@@ -5323,6 +5555,23 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 
 	list_free(indexoidlist);
 
+	/* If rel is partition, shouldn't drop NOT NULL if parent has the same */
+	if (rel->rd_rel->relispartition)
+	{
+		Oid			parentId = get_partition_parent(RelationGetRelid(rel));
+		Relation	parent = heap_open(parentId, AccessShareLock);
+		TupleDesc	tupDesc = RelationGetDescr(parent);
+		AttrNumber	parent_attnum;
+
+		parent_attnum = get_attnum(parentId, colName);
+		if (tupDesc->attrs[parent_attnum - 1]->attnotnull)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("column \"%s\" is marked NOT NULL in parent table",
+							colName)));
+		heap_close(parent, AccessShareLock);
+	}
+
 	/*
 	 * Okay, actually perform the catalog change ... if needed
 	 */
@@ -5355,6 +5604,21 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
  * Return the address of the modified column.  If the column was already NOT
  * NULL, InvalidObjectAddress is returned.
  */
+
+static void
+ATPrepSetNotNull(Relation rel, bool recurse, bool recursing)
+{
+	/*
+	 * If the parent is a partitioned table, like check constraints, NOT NULL
+	 * constraints must be added to the child tables.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+		!recurse && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("constraint must be added to child tables too")));
+}
+
 static ObjectAddress
 ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
 				 const char *colName, LOCKMODE lockmode)
@@ -5914,6 +6178,15 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 		Relation	attr_rel;
 		ListCell   *child;
 
+		/*
+		 * In case of a partitioned table, the column must be dropped from the
+		 * partitions as well.
+		 */
+		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !recurse)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("column must be dropped from child tables too")));
+
 		attr_rel = heap_open(AttributeRelationId, RowExclusiveLock);
 		foreach(child, children)
 		{
@@ -7916,6 +8189,16 @@ ATExecDropConstraint(Relation rel, const char *constrName,
 	}
 
 	/*
+	 * In case of a partitioned table, the constraint must be dropped from
+	 * the partitions too.  There is no such thing as NO INHERIT constraints
+	 * in case of partitioned tables.
+	 */
+	if (!recurse && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("constraint must be dropped from child tables too")));
+
+	/*
 	 * Propagate to children as appropriate.  Unlike most other ALTER
 	 * routines, we have to do this one level of recursion at a time; we can't
 	 * use find_all_inheritors to do it in one pass.
@@ -10217,6 +10500,11 @@ ATPrepAddInherit(Relation child_rel)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot change inheritance of typed table")));
 
+	if (child_rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of a partition")));
+
 	if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -10229,12 +10517,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;
 
@@ -10279,37 +10562,11 @@ 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);
+	/* Likewise for partitions */
+	if (parent_rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot inherit from a partition")));
 
 	/*
 	 * Prevent circularity by seeing if proposed parent inherits from child.
@@ -10344,6 +10601,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);
 
@@ -10358,16 +10678,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;
 }
 
 /*
@@ -10418,7 +10730,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
@@ -10436,12 +10748,17 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 	int			parent_natts;
 	TupleDesc	tupleDesc;
 	HeapTuple	tuple;
+	bool		child_is_partition = false;
 
 	attrrel = heap_open(AttributeRelationId, RowExclusiveLock);
 
 	tupleDesc = RelationGetDescr(parent_rel);
 	parent_natts = tupleDesc->natts;
 
+	/* If parent_rel is a partitioned table, child_rel must be a partition */
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		child_is_partition = true;
+
 	for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++)
 	{
 		Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1];
@@ -10489,6 +10806,18 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 			 * later on, this change will just roll back.)
 			 */
 			childatt->attinhcount++;
+
+			/*
+			 * In case of partitions, we must enforce that value of attislocal
+			 * is same in all partitions. (Note: there are only inherited
+			 * attributes in partitions)
+			 */
+			if (child_is_partition)
+			{
+				Assert(childatt->attinhcount == 1);
+				childatt->attislocal = false;
+			}
+
 			simple_heap_update(attrrel, &tuple->t_self, tuple);
 			CatalogUpdateIndexes(attrrel, tuple);
 			heap_freetuple(tuple);
@@ -10511,7 +10840,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.
@@ -10530,10 +10859,15 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 	SysScanDesc parent_scan;
 	ScanKeyData parent_key;
 	HeapTuple	parent_tuple;
+	bool		child_is_partition = false;
 
 	catalog_relation = heap_open(ConstraintRelationId, RowExclusiveLock);
 	tuple_desc = RelationGetDescr(catalog_relation);
 
+	/* If parent_rel is a partitioned table, child_rel must be a partition */
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		child_is_partition = true;
+
 	/* Outer loop scans through the parent's constraint definitions */
 	ScanKeyInit(&parent_key,
 				Anum_pg_constraint_conrelid,
@@ -10610,6 +10944,18 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 			child_copy = heap_copytuple(child_tuple);
 			child_con = (Form_pg_constraint) GETSTRUCT(child_copy);
 			child_con->coninhcount++;
+
+			/*
+			 * In case of partitions, an inherited constraint must be
+			 * inherited only once since it cannot have multiple parents and
+			 * it is never considered local.
+			 */
+			if (child_is_partition)
+			{
+				Assert(child_con->coninhcount == 1);
+				child_con->conislocal = false;
+			}
+
 			simple_heap_update(catalog_relation, &child_copy->t_self, child_copy);
 			CatalogUpdateIndexes(catalog_relation, child_copy);
 			heap_freetuple(child_copy);
@@ -10634,6 +10980,46 @@ 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;
+
+	if (rel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of a 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.
+	 */
+
+	/* 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.
@@ -10647,13 +11033,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];
@@ -10662,19 +11046,11 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 				constraintTuple;
 	List	   *connames;
 	bool		found = false;
-	ObjectAddress address;
+	bool		child_is_partition = false;
 
-	/*
-	 * 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.
-	 */
+	/* If parent_rel is a partitioned table, child_rel must be a partition */
+	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		child_is_partition = true;
 
 	/*
 	 * Find and destroy the pg_inherits entry linking the two, or error out if
@@ -10684,7 +11060,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);
 
@@ -10705,11 +11081,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 (child_is_partition)
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_TABLE),
+					 errmsg("relation \"%s\" is not a partition of relation \"%s\"",
+							RelationGetRelationName(child_rel),
+							RelationGetRelationName(parent_rel))));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_TABLE),
+					 errmsg("relation \"%s\" is not a parent of relation \"%s\"",
+							RelationGetRelationName(parent_rel),
+							RelationGetRelationName(child_rel))));
+	}
 
 	/*
 	 * Search through child columns looking for ones matching parent rel
@@ -10718,7 +11103,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)))
@@ -10780,7 +11165,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);
 
@@ -10811,7 +11196,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)
@@ -10823,30 +11208,20 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 		}
 	}
 
-	parent_oid = RelationGetRelid(parent_rel);
-
 	systable_endscan(scan);
 	heap_close(catalogRelation, RowExclusiveLock);
 
-	drop_parent_dependency(RelationGetRelid(rel),
+	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;
 }
 
 /*
@@ -12531,3 +12906,447 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
 		attn++;
 	}
 }
+
+/*
+ * 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)
+{
+	PartitionKey	key = RelationGetPartitionKey(rel);
+	Relation	attachRel,
+				catalog;
+	List	   *childrels;
+	TupleConstr	*attachRel_constr;
+	List	   *partConstraint,
+			   *existConstraint;
+	SysScanDesc scan;
+	ScanKeyData skey;
+	HeapTuple	tuple;
+	AttrNumber	attno;
+	int			natts;
+	TupleDesc	tupleDesc;
+	bool		skip_validate = false;
+	ObjectAddress address;
+
+	attachRel = heap_openrv(cmd->name, AccessExclusiveLock);
+
+	/*
+	 * Must be owner of both parent and source table -- parent was checked by
+	 * ATSimplePermissions call in ATPrepCmd
+	 */
+	ATSimplePermissions(attachRel, ATT_TABLE | ATT_FOREIGN_TABLE);
+
+	/* A partition can only have one parent */
+	if (attachRel->rd_rel->relispartition)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is already a partition",
+						RelationGetRelationName(attachRel))));
+
+	if (attachRel->rd_rel->reloftype)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach a typed table as partition")));
+
+	/*
+	 * attachRel should not already be part of inheritance; either as a child
+	 * table...
+	 */
+	catalog = heap_open(InheritsRelationId, AccessShareLock);
+	ScanKeyInit(&skey,
+				Anum_pg_inherits_inhrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(attachRel)));
+	scan = systable_beginscan(catalog, InheritsRelidSeqnoIndexId, true,
+							  NULL, 1, &skey);
+	if (HeapTupleIsValid(systable_getnext(scan)))
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach inheritance child as partition")));
+	systable_endscan(scan);
+
+	/* ...or be a RELKIND_RELATION parent table */
+	ScanKeyInit(&skey,
+				Anum_pg_inherits_inhparent,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(attachRel)));
+	scan = systable_beginscan(catalog, InheritsParentIndexId, true, NULL,
+							  1, &skey);
+	if (HeapTupleIsValid(systable_getnext(scan)) &&
+		attachRel->rd_rel->relkind == RELKIND_RELATION)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach inheritance parent as partition")));
+	systable_endscan(scan);
+	heap_close(catalog, AccessShareLock);
+
+	/*
+	 * Prevent circularity by seeing if rel is a partition of attachRel.
+	 * (In particular, this disallows making a rel a partition of itself.)
+	 */
+	childrels = find_all_inheritors(RelationGetRelid(attachRel),
+									AccessShareLock, NULL);
+	if (list_member_oid(childrels, RelationGetRelid(rel)))
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_TABLE),
+				 errmsg("circular inheritance not allowed"),
+				 errdetail("\"%s\" is already a child of \"%s\".",
+						   RelationGetRelationName(rel),
+						   RelationGetRelationName(attachRel))));
+
+	/* 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 then child 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("New partition should contain only the columns present in parent.")));
+	}
+
+	/* OK to create inheritance.  Rest of the checks performed there */
+	CreateInheritance(attachRel, rel);
+
+	/*
+	 * 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 error.
+	 */
+	check_new_partition_bound(RelationGetRelationName(attachRel),
+							  RelationGetRelid(rel),
+							  cmd->bound);
+
+	/* Update the pg_class entry. */
+	StorePartitionBound(attachRel, cmd->bound);
+
+	/*
+	 * Generate partition constraint from the partition bound specification.
+	 * 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),
+								 RelationGetPartitionQual(rel, true));
+	partConstraint = (List *) eval_const_expressions(NULL,
+													 (Node *) partConstraint);
+	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 are some cases in which we cannot rely on just the result of
+	 * the proof.
+	 */
+	tupleDesc = RelationGetDescr(attachRel);
+	attachRel_constr = tupleDesc->constr;
+	existConstraint = NIL;
+	if (attachRel_constr > 0)
+	{
+		int			num_check = attachRel_constr->num_check;
+		int			i;
+		Bitmapset  *not_null_attrs = NULL;
+
+		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);
+					not_null_attrs = bms_add_member(not_null_attrs, i);
+				}
+			}
+		}
+
+		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))
+			skip_validate = true;
+
+		/*
+		 * We choose to err on the safer side in certain cases, ie, give up on
+		 * skipping the validation scan, if the partition key columns don't
+		 * have the NOT NULL constraint.  There are two such cases:  a) if the
+		 * table is to be a range partition, b) if the table is to be a list
+		 * partition that does not accept nulls.  In such cases, the partition
+		 * predicate (partConstraint) does include an IS NOT NULL expression,
+		 * however, because of the way predicate_implied_by_simple_clause()
+		 * is designed to handle the IS NOT NULL predicates in the absence of
+		 * a IS NOT NULL clause, we cannot rely on there being no NULL values
+		 * in partition key column(s) in the rows of the table based only on
+		 * the above proof.
+		 */
+		switch (key->strategy)
+		{
+			case PARTITION_STRATEGY_RANGE:
+				for (i = 0; i < key->partnatts; i++)
+				{
+					if (!bms_is_member(get_partition_col_attnum(key, i),
+									   not_null_attrs))
+					{
+						skip_validate = false;
+						break;
+					}
+				}
+				break;
+
+			case PARTITION_STRATEGY_LIST:
+			{
+				List   *part_constr;
+				ListCell *lc;
+				bool	partition_accepts_null = true;
+
+				/*
+				 * Partition does not accept nulls if there is a IS NOT NULL
+				 * expression in the partition constraint.
+				 */
+				part_constr = linitial(partConstraint);
+				part_constr = make_ands_implicit((Expr *) part_constr);
+				foreach(lc, part_constr)
+				{
+					Node *expr = lfirst(lc);
+
+					if (IsA(expr, NullTest) &&
+						((NullTest *) expr)->nulltesttype == IS_NOT_NULL)
+					{
+						partition_accepts_null = false;
+						break;
+					}
+				}
+
+				if (!partition_accepts_null &&
+					!bms_is_member(get_partition_col_attnum(key, 0),
+								   not_null_attrs))
+				{
+					skip_validate = false;
+					break;
+				}
+				break;
+			}
+		}
+	}
+
+	if (skip_validate)
+		elog(NOTICE, "skipping scan to validate partition constraint");
+
+	/*
+	 * Set up to have the table to be scanned to validate the partition
+	 * constraint (see partConstraint above).  If it's a partitioned table,
+	 * we instead schdule its leaf partitions to be scanned instead.
+	 */
+	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);
+
+			constr = linitial(partConstraint);
+			tab->partition_constraint = make_ands_implicit((Expr *) constr);
+
+			/* keep our lock until commit */
+			if (part_rel != attachRel)
+				heap_close(part_rel, NoLock);
+		}
+	}
+
+	/*
+	 * Invalidate the relcache so that the new partition is now included
+	 * in rel's partition descriptor.
+	 */
+	CacheInvalidateRelcache(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,
+				classRel;
+	HeapTuple	tuple,
+				newtuple;
+	Datum		new_val[Natts_pg_class];
+	bool		isnull,
+				new_null[Natts_pg_class],
+				new_repl[Natts_pg_class];
+	ObjectAddress address;
+
+	partRel = heap_openrv(name, AccessShareLock);
+
+	/* All inheritance related checks are performed within the function */
+	RemoveInheritance(partRel, rel);
+
+	/* Update pg_class tuple */
+	classRel = heap_open(RelationRelationId, RowExclusiveLock);
+	tuple = SearchSysCacheCopy1(RELOID,
+								ObjectIdGetDatum(RelationGetRelid(partRel)));
+	Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+	(void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+						   &isnull);
+	Assert(!isnull);
+
+	/* Clear relpartbound and reset relispartition */
+	memset(new_val, 0, sizeof(new_val));
+	memset(new_null, false, sizeof(new_null));
+	memset(new_repl, false, sizeof(new_repl));
+	new_val[Anum_pg_class_relpartbound - 1] = (Datum) 0;
+	new_null[Anum_pg_class_relpartbound - 1] = true;
+	new_repl[Anum_pg_class_relpartbound - 1] = true;
+	newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+								 new_val, new_null, new_repl);
+
+	((Form_pg_class) GETSTRUCT(newtuple))->relispartition = false;
+	simple_heap_update(classRel, &newtuple->t_self, newtuple);
+	CatalogUpdateIndexes(classRel, newtuple);
+	heap_freetuple(newtuple);
+	heap_close(classRel, RowExclusiveLock);
+
+	/*
+	 * Invalidate the relcache so that the partition is no longer included
+	 * in our partition descriptor.
+	 */
+	CacheInvalidateRelcache(rel);
+
+	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
+
+	/* keep our lock until commit */
+	heap_close(partRel, NoLock);
+
+	return address;
+}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 056933a..5e3989a 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -2107,7 +2107,8 @@ DefineCompositeType(RangeVar *typevar, List *coldeflist)
 	/*
 	 * Finally create the relation.  This also creates the type.
 	 */
-	DefineRelation(createStmt, RELKIND_COMPOSITE_TYPE, InvalidOid, &address);
+	DefineRelation(createStmt, RELKIND_COMPOSITE_TYPE, InvalidOid, &address,
+				   NULL);
 
 	return address;
 }
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 325a810..c6b0e4f 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -228,7 +228,8 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
 		 * existing view, so we don't need more code to complain if "replace"
 		 * is false).
 		 */
-		address = DefineRelation(createStmt, RELKIND_VIEW, InvalidOid, NULL);
+		address = DefineRelation(createStmt, RELKIND_VIEW, InvalidOid, NULL,
+								 NULL);
 		Assert(address.objectId != InvalidOid);
 		return address;
 	}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 1c978c0..28d0036 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3031,6 +3031,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_NODE_FIELD(tableElts);
 	COPY_NODE_FIELD(inhRelations);
 	COPY_NODE_FIELD(partspec);
+	COPY_NODE_FIELD(partbound);
 	COPY_NODE_FIELD(ofTypename);
 	COPY_NODE_FIELD(constraints);
 	COPY_NODE_FIELD(options);
@@ -4215,6 +4216,43 @@ _copyPartitionElem(const PartitionElem *from)
 	return newnode;
 }
 
+static PartitionBoundSpec *
+_copyPartitionBoundSpec(const PartitionBoundSpec *from)
+{
+	PartitionBoundSpec *newnode = makeNode(PartitionBoundSpec);
+
+	COPY_SCALAR_FIELD(strategy);
+	COPY_NODE_FIELD(listdatums);
+	COPY_NODE_FIELD(lowerdatums);
+	COPY_NODE_FIELD(upperdatums);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static PartitionRangeDatum *
+_copyPartitionRangeDatum(const PartitionRangeDatum *from)
+{
+	PartitionRangeDatum *newnode = makeNode(PartitionRangeDatum);
+
+	COPY_SCALAR_FIELD(infinite);
+	COPY_NODE_FIELD(value);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static PartitionCmd *
+_copyPartitionCmd(const PartitionCmd *from)
+{
+	PartitionCmd *newnode = makeNode(PartitionCmd);
+
+	COPY_NODE_FIELD(name);
+	COPY_NODE_FIELD(bound);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					pg_list.h copy functions
  * ****************************************************************
@@ -5138,6 +5176,15 @@ copyObject(const void *from)
 		case T_PartitionElem:
 			retval = _copyPartitionElem(from);
 			break;
+		case T_PartitionBoundSpec:
+			retval = _copyPartitionBoundSpec(from);
+			break;
+		case T_PartitionRangeDatum:
+			retval = _copyPartitionRangeDatum(from);
+			break;
+		case T_PartitionCmd:
+			retval = _copyPartitionCmd(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 7d0391d..8fc32ca 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1169,6 +1169,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_NODE_FIELD(tableElts);
 	COMPARE_NODE_FIELD(inhRelations);
 	COMPARE_NODE_FIELD(partspec);
+	COMPARE_NODE_FIELD(partbound);
 	COMPARE_NODE_FIELD(ofTypename);
 	COMPARE_NODE_FIELD(constraints);
 	COMPARE_NODE_FIELD(options);
@@ -2668,6 +2669,37 @@ _equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
 	return true;
 }
 
+static bool
+_equalPartitionBoundSpec(const PartitionBoundSpec *a, const PartitionBoundSpec *b)
+{
+	COMPARE_SCALAR_FIELD(strategy);
+	COMPARE_NODE_FIELD(listdatums);
+	COMPARE_NODE_FIELD(lowerdatums);
+	COMPARE_NODE_FIELD(upperdatums);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalPartitionRangeDatum(const PartitionRangeDatum *a, const PartitionRangeDatum *b)
+{
+	COMPARE_SCALAR_FIELD(infinite);
+	COMPARE_NODE_FIELD(value);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
+{
+	COMPARE_NODE_FIELD(name);
+	COMPARE_NODE_FIELD(bound);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3430,6 +3462,15 @@ equal(const void *a, const void *b)
 		case T_PartitionElem:
 			retval = _equalPartitionElem(a, b);
 			break;
+		case T_PartitionBoundSpec:
+			retval = _equalPartitionBoundSpec(a, b);
+			break;
+		case T_PartitionRangeDatum:
+			retval = _equalPartitionRangeDatum(a, b);
+			break;
+		case T_PartitionCmd:
+			retval = _equalPartitionCmd(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 3997441..973fb15 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1552,6 +1552,12 @@ exprLocation(const Node *expr)
 			/* just use nested expr's location */
 			loc = exprLocation((Node *) ((const InferenceElem *) expr)->expr);
 			break;
+		case T_PartitionBoundSpec:
+			loc = ((const PartitionBoundSpec *) expr)->location;
+			break;
+		case T_PartitionRangeDatum:
+			loc = ((const PartitionRangeDatum *) expr)->location;
+			break;
 		default:
 			/* for any other node type it's just unknown... */
 			loc = -1;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 323daf5..0d858f5 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2393,6 +2393,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_NODE_FIELD(tableElts);
 	WRITE_NODE_FIELD(inhRelations);
 	WRITE_NODE_FIELD(partspec);
+	WRITE_NODE_FIELD(partbound);
 	WRITE_NODE_FIELD(ofTypename);
 	WRITE_NODE_FIELD(constraints);
 	WRITE_NODE_FIELD(options);
@@ -3300,6 +3301,26 @@ _outPartitionElem(StringInfo str, const PartitionElem *node)
 	WRITE_LOCATION_FIELD(location);
 }
 
+static void
+_outPartitionBoundSpec(StringInfo str, const PartitionBoundSpec *node)
+{
+	WRITE_NODE_TYPE("PARTITIONBOUND");
+
+	WRITE_CHAR_FIELD(strategy);
+	WRITE_NODE_FIELD(listdatums);
+	WRITE_NODE_FIELD(lowerdatums);
+	WRITE_NODE_FIELD(upperdatums);
+}
+
+static void
+_outPartitionRangeDatum(StringInfo str, const PartitionRangeDatum *node)
+{
+	WRITE_NODE_TYPE("PARTRANGEDATUM");
+
+	WRITE_BOOL_FIELD(infinite);
+	WRITE_NODE_FIELD(value);
+}
+
 /*
  * outNode -
  *	  converts a Node into ascii string and append it to 'str'
@@ -3893,6 +3914,12 @@ outNode(StringInfo str, const void *obj)
 			case T_PartitionElem:
 				_outPartitionElem(str, obj);
 				break;
+			case T_PartitionBoundSpec:
+				_outPartitionBoundSpec(str, obj);
+				break;
+			case T_PartitionRangeDatum:
+				_outPartitionRangeDatum(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 917e6c8..c587d4e 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2266,6 +2266,36 @@ _readExtensibleNode(void)
 }
 
 /*
+ * _readPartitionBoundSpec
+ */
+static PartitionBoundSpec *
+_readPartitionBoundSpec(void)
+{
+	READ_LOCALS(PartitionBoundSpec);
+
+	READ_CHAR_FIELD(strategy);
+	READ_NODE_FIELD(listdatums);
+	READ_NODE_FIELD(lowerdatums);
+	READ_NODE_FIELD(upperdatums);
+
+	READ_DONE();
+}
+
+/*
+ * _readPartitionRangeDatum
+ */
+static PartitionRangeDatum *
+_readPartitionRangeDatum(void)
+{
+	READ_LOCALS(PartitionRangeDatum);
+
+	READ_BOOL_FIELD(infinite);
+	READ_NODE_FIELD(value);
+
+	READ_DONE();
+}
+
+/*
  * parseNodeString
  *
  * Given a character string representing a node tree, parseNodeString creates
@@ -2497,6 +2527,10 @@ parseNodeString(void)
 		return_value = _readAlternativeSubPlan();
 	else if (MATCH("EXTENSIBLENODE", 14))
 		return_value = _readExtensibleNode();
+	else if (MATCH("PARTITIONBOUND", 14))
+		return_value = _readPartitionBoundSpec();
+	else if (MATCH("PARTRANGEDATUM", 14))
+		return_value = _readPartitionRangeDatum();
 	else
 	{
 		elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 2387df9..b458e99 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -231,6 +231,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	VariableSetStmt		*vsetstmt;
 	PartitionElem		*partelem;
 	PartitionSpec		*partspec;
+	PartitionRangeDatum	*partrange_datum;
 }
 
 %type <node>	stmt schema_stmt
@@ -551,6 +552,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>			part_strategy
 %type <partelem>	part_elem
 %type <list>		part_params
+%type <list>		OptPartitionElementList PartitionElementList
+%type <node>		PartitionElement
+%type <node>		ForValues
+%type <node>		partbound_datum
+%type <list>		partbound_datum_list
+%type <partrange_datum>	PartitionRangeDatum
+%type <list>		range_datum_list
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -576,7 +584,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
@@ -592,7 +600,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
@@ -2378,6 +2387,31 @@ alter_table_cmd:
 					n->def = (Node *)$1;
 					$$ = (Node *) n;
 				}
+			/* ALTER TABLE <name> ATTACH PARTITION <table_name> FOR VALUES */
+			| ATTACH PARTITION qualified_name ForValues
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					PartitionCmd *cmd = makeNode(PartitionCmd);
+
+					n->subtype = AT_AttachPartition;
+					cmd->name = $3;
+					cmd->bound = (Node *) $4;
+					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;
+				}
 		;
 
 alter_column_default:
@@ -2473,6 +2507,73 @@ reloption_elem:
 				}
 		;
 
+ForValues:
+			/* a LIST partition */
+			FOR VALUES IN_P '(' partbound_datum_list ')'
+				{
+					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+					n->strategy = PARTITION_STRATEGY_LIST;
+					n->listdatums = $5;
+					n->location = @3;
+
+					$$ = (Node *) n;
+				}
+
+			/* a RANGE partition */
+			| FOR VALUES FROM '(' range_datum_list ')' TO '(' range_datum_list ')'
+				{
+					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+					n->strategy = PARTITION_STRATEGY_RANGE;
+					n->lowerdatums = $5;
+					n->upperdatums = $9;
+					n->location = @3;
+
+					$$ = (Node *) n;
+				}
+		;
+
+partbound_datum:
+			Sconst			{ $$ = makeStringConst($1, @1); }
+			| NumericOnly	{ $$ = makeAConst($1, @1); }
+			| NULL_P		{ $$ = makeNullAConst(@1); }
+		;
+
+partbound_datum_list:
+			partbound_datum						{ $$ = list_make1($1); }
+			| partbound_datum_list ',' partbound_datum
+												{ $$ = lappend($1, $3); }
+		;
+
+range_datum_list:
+			PartitionRangeDatum					{ $$ = list_make1($1); }
+			| range_datum_list ',' PartitionRangeDatum
+												{ $$ = lappend($1, $3); }
+		;
+
+PartitionRangeDatum:
+			UNBOUNDED
+				{
+					PartitionRangeDatum *n = makeNode(PartitionRangeDatum);
+
+					n->infinite = true;
+					n->value = NULL;
+					n->location = @1;
+
+					$$ = n;
+				}
+			| partbound_datum
+				{
+					PartitionRangeDatum *n = makeNode(PartitionRangeDatum);
+
+					n->infinite = false;
+					n->value = $1;
+					n->location = @1;
+
+					$$ = n;
+				}
+		;
 
 /*****************************************************************************
  *
@@ -2890,6 +2991,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 OptPartitionSpec 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->partspec = $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 OptPartitionSpec
+			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->partspec = $13;
+					n->ofTypename = NULL;
+					n->constraints = NIL;
+					n->options = $14;
+					n->oncommit = $15;
+					n->tablespacename = $16;
+					n->if_not_exists = true;
+					$$ = (Node *)n;
+				}
 		;
 
 /*
@@ -2935,6 +3074,11 @@ OptTypedTableElementList:
 			| /*EMPTY*/							{ $$ = NIL; }
 		;
 
+OptPartitionElementList:
+			'(' PartitionElementList ')'		{ $$ = $2; }
+			| /*EMPTY*/							{ $$ = NIL; }
+		;
+
 TableElementList:
 			TableElement
 				{
@@ -2957,6 +3101,17 @@ TypedTableElementList:
 				}
 		;
 
+PartitionElementList:
+			PartitionElement
+				{
+					$$ = list_make1($1);
+				}
+			| PartitionElementList ',' PartitionElement
+				{
+					$$ = lappend($1, $3);
+				}
+		;
+
 TableElement:
 			columnDef							{ $$ = $1; }
 			| TableLikeClause					{ $$ = $1; }
@@ -2968,6 +3123,11 @@ TypedTableElement:
 			| TableConstraint					{ $$ = $1; }
 		;
 
+PartitionElement:
+			columnOptions						{ $$ = $1; }
+			| TableConstraint					{ $$ = $1; }
+		;
+
 columnDef:	ColId Typename create_generic_options ColQualList
 				{
 					ColumnDef *n = makeNode(ColumnDef);
@@ -4555,6 +4715,48 @@ CreateForeignTableStmt:
 					n->options = $14;
 					$$ = (Node *) n;
 				}
+		| CREATE FOREIGN TABLE qualified_name
+			PARTITION OF qualified_name OptPartitionElementList ForValues
+			SERVER name create_generic_options
+				{
+					CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
+					$4->relpersistence = RELPERSISTENCE_PERMANENT;
+					n->base.relation = $4;
+					n->base.inhRelations = list_make1($7);
+					n->base.tableElts = $8;
+					n->base.partbound = (Node *) $9;
+					n->base.ofTypename = NULL;
+					n->base.constraints = NIL;
+					n->base.options = NIL;
+					n->base.oncommit = ONCOMMIT_NOOP;
+					n->base.tablespacename = NULL;
+					n->base.if_not_exists = false;
+					/* FDW-specific data */
+					n->servername = $11;
+					n->options = $12;
+					$$ = (Node *) n;
+				}
+		| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
+			PARTITION OF qualified_name OptPartitionElementList ForValues
+			SERVER name create_generic_options
+				{
+					CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
+					$7->relpersistence = RELPERSISTENCE_PERMANENT;
+					n->base.relation = $7;
+					n->base.inhRelations = list_make1($10);
+					n->base.tableElts = $11;
+					n->base.partbound = (Node *) $12;
+					n->base.ofTypename = NULL;
+					n->base.constraints = NIL;
+					n->base.options = NIL;
+					n->base.oncommit = ONCOMMIT_NOOP;
+					n->base.tablespacename = NULL;
+					n->base.if_not_exists = true;
+					/* FDW-specific data */
+					n->servername = $14;
+					n->options = $15;
+					$$ = (Node *) n;
+				}
 		;
 
 /*****************************************************************************
@@ -13804,6 +14006,7 @@ unreserved_keyword:
 			| ASSERTION
 			| ASSIGNMENT
 			| AT
+			| ATTACH
 			| ATTRIBUTE
 			| BACKWARD
 			| BEFORE
@@ -13850,6 +14053,7 @@ unreserved_keyword:
 			| DELIMITER
 			| DELIMITERS
 			| DEPENDS
+			| DETACH
 			| DICTIONARY
 			| DISABLE_P
 			| DISCARD
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 666cc1f..4175ef5 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -47,8 +47,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"
@@ -62,6 +64,7 @@
 #include "utils/guc.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/ruleutils.h"
 #include "utils/syscache.h"
 #include "utils/typcache.h"
 
@@ -88,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 */
@@ -130,6 +134,7 @@ 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 transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd);
 
 
 /*
@@ -253,7 +258,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	{
 		int		partnatts = list_length(stmt->partspec->partParams);
 
-		if (stmt->inhRelations)
+		if (stmt->inhRelations && !stmt->partbound)
 			ereport(ERROR,
 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 				 errmsg("cannot create partitioned table as inheritance child")));
@@ -2580,6 +2585,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
 	cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
+	cxt.partbound = NULL;
 
 	/*
 	 * The only subtypes that currently require parse transformation handling
@@ -2662,6 +2668,19 @@ 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;
+
 			default:
 				newcmds = lappend(newcmds, cmd);
 				break;
@@ -3026,3 +3045,242 @@ setSchemaName(char *context_schema, char **stmt_schema_name)
 						"different from the one being created (%s)",
 						*stmt_schema_name, context_schema)));
 }
+
+/*
+ * transformAttachPartition
+ *		Analyze ATTACH PARTITION ... FOR VALUES ...
+ */
+static void
+transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd)
+{
+	Relation	parentRel = cxt->rel;
+
+	/*
+	 * We are going to try to validate the partition bound specification
+	 * against the partition key of rel, so it better have one.
+	 */
+	if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("\"%s\" is not partitioned",
+						RelationGetRelationName(parentRel))));
+
+	/* tranform the values */
+	Assert(RelationGetPartitionKey(parentRel) != NULL);
+	cxt->partbound = transformPartitionBound(cxt->pstate, parentRel,
+											 cmd->bound);
+}
+
+/*
+ * transformPartitionBound
+ *
+ * Transform partition bound specification
+ */
+Node *
+transformPartitionBound(ParseState *pstate, Relation parent, Node *bound)
+{
+	PartitionBoundSpec *spec = (PartitionBoundSpec *) bound,
+					   *result_spec;
+	PartitionKey	key = RelationGetPartitionKey(parent);
+	char			strategy = get_partition_strategy(key);
+	int				partnatts = get_partition_natts(key);
+	List		   *partexprs = get_partition_exprs(key);
+
+	result_spec = copyObject(spec);
+	switch (strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+		{
+			ListCell   *cell;
+			char	   *colname;
+
+			/* Get the only column's name in case we need to output an error */
+			if (key->partattrs[0] != 0)
+				colname = get_relid_attribute_name(RelationGetRelid(parent),
+												   key->partattrs[0]);
+			else
+				colname = deparse_expression((Node *) linitial(partexprs),
+							deparse_context_for(RelationGetRelationName(parent),
+											 RelationGetRelid(parent)),
+											 false, false);
+
+			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(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)
+				{
+					Const	*value2 = (Const *) lfirst(cell2);
+
+					if (equal(value, value2))
+					{
+						duplicate = true;
+						break;
+					}
+				}
+				if (duplicate)
+					continue;
+
+				result_spec->listdatums = lappend(result_spec->listdatums,
+												  value);
+			}
+
+			break;
+		}
+
+		case PARTITION_STRATEGY_RANGE:
+		{
+			ListCell *cell1,
+					 *cell2;
+			int		i,
+					j;
+			char   *colname;
+
+			if (spec->strategy != PARTITION_STRATEGY_RANGE)
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("invalid bound specification for a range partition"),
+					 parser_errposition(pstate, exprLocation(bound))));
+
+			Assert(spec->lowerdatums != NIL && spec->upperdatums != NIL);
+
+			if (list_length(spec->lowerdatums) != partnatts)
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("FROM must specify exactly one value per partitioning column")));
+			if (list_length(spec->upperdatums) != partnatts)
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("TO must specify exactly one value per partitioning column")));
+
+			i = j = 0;
+			result_spec->lowerdatums = result_spec->upperdatums = NIL;
+			forboth (cell1, spec->lowerdatums, cell2, spec->upperdatums)
+			{
+				PartitionRangeDatum *ldatum,
+									*rdatum;
+				Node	   *value;
+				A_Const	   *lcon = NULL,
+						   *rcon = NULL;
+
+				ldatum = (PartitionRangeDatum *) lfirst(cell1);
+				rdatum = (PartitionRangeDatum *) lfirst(cell2);
+				/* Get the column's name in case we need to output an error */
+				if (key->partattrs[i] != 0)
+					colname = get_relid_attribute_name(RelationGetRelid(parent),
+														   key->partattrs[i]);
+				else
+				{
+					colname = deparse_expression((Node *) list_nth(partexprs, j),
+								deparse_context_for(RelationGetRelationName(parent),
+												 RelationGetRelid(parent)),
+												 false, false);
+					++j;
+				}
+
+				if (!ldatum->infinite)
+					lcon = (A_Const *) ldatum->value;
+				if (!rdatum->infinite)
+					rcon = (A_Const *) rdatum->value;
+
+				if (lcon)
+				{
+					value = (Node *) make_const(pstate, &lcon->val, lcon->location);
+					if (((Const *) value)->constisnull)
+						ereport(ERROR,
+								(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+								 errmsg("cannot specify NULL in range bound")));
+					value = coerce_to_target_type(pstate,
+												  value, exprType(value),
+												  get_partition_col_typid(key, i),
+												  get_partition_col_typmod(key, 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 column \"%s\"",
+									format_type_be(get_partition_col_typid(key, i)),
+									colname),
+							 parser_errposition(pstate, exprLocation((Node *) ldatum))));
+
+					/* Simplify the expression */
+					value = (Node *) expression_planner((Expr *) value);
+					ldatum->value = value;
+				}
+
+				if (rcon)
+				{
+					value = (Node *) make_const(pstate, &rcon->val, rcon->location);
+					if (((Const *) value)->constisnull)
+						ereport(ERROR,
+								(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+								 errmsg("cannot specify NULL in range bound")));
+					value = coerce_to_target_type(pstate,
+												  value, exprType(value),
+												  get_partition_col_typid(key, i),
+												  get_partition_col_typmod(key, 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 column \"%s\"",
+									format_type_be(get_partition_col_typid(key, i)),
+									colname),
+								 parser_errposition(pstate, exprLocation((Node *) rdatum))));
+
+					/* Simplify the expression */
+					value = (Node *) expression_planner((Expr *) value);
+					rdatum->value = value;
+				}
+
+				result_spec->lowerdatums = lappend(result_spec->lowerdatums,
+												   copyObject(ldatum));
+				result_spec->upperdatums = lappend(result_spec->upperdatums,
+												   copyObject(rdatum));
+
+				++i;
+			}
+
+			break;
+		}
+	}
+
+	return (Node *) result_spec;
+}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index f50ce40..fd4eff4 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -987,7 +987,8 @@ ProcessUtilitySlow(ParseState *pstate,
 							/* Create the table itself */
 							address = DefineRelation((CreateStmt *) stmt,
 													 RELKIND_RELATION,
-													 InvalidOid, NULL);
+													 InvalidOid, NULL,
+													 queryString);
 							EventTriggerCollectSimpleCommand(address,
 															 secondaryObject,
 															 stmt);
@@ -1020,7 +1021,8 @@ ProcessUtilitySlow(ParseState *pstate,
 							/* Create the table itself */
 							address = DefineRelation((CreateStmt *) stmt,
 													 RELKIND_FOREIGN_TABLE,
-													 InvalidOid, NULL);
+													 InvalidOid, NULL,
+													 queryString);
 							CreateForeignTable((CreateForeignTableStmt *) stmt,
 											   address.objectId);
 							EventTriggerCollectSimpleCommand(address,
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index a2d16ea..eb16f70 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -41,6 +41,7 @@
 #include "catalog/index.h"
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_amproc.h"
 #include "catalog/pg_attrdef.h"
@@ -282,6 +283,8 @@ static OpClassCacheEnt *LookupOpclassInfo(Oid operatorClassOid,
 				  StrategyNumber numSupport);
 static void RelationCacheInitFileRemoveInDir(const char *tblspcpath);
 static void unlink_initfile(const char *initfilename);
+static bool equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+					PartitionDesc pdesc2);
 
 
 /*
@@ -1161,6 +1164,58 @@ equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2)
 }
 
 /*
+ * equalPartitionDescs
+ *		Compare two partition descriptors for logical equality
+ */
+static bool
+equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+					PartitionDesc pdesc2)
+{
+	int		i;
+
+	if (pdesc1 != NULL)
+	{
+		if (pdesc2 == NULL)
+			return false;
+		if (pdesc1->nparts != pdesc2->nparts)
+			return false;
+
+		Assert(key != NULL || pdesc1->nparts == 0);
+
+		/*
+		 * Same oids? If the partitioning structure did not change, that is,
+		 * no partitions were added or removed to the relation, the oids array
+		 * should still match element-by-element.
+		 */
+		for (i = 0; i < pdesc1->nparts; i++)
+		{
+			if (pdesc1->oids[i] != pdesc2->oids[i])
+				return false;
+		}
+
+		/*
+		 * Now compare partition bound collections.  The logic to iterate over
+		 * the collections is local to partition.c.
+		 */
+		if (pdesc1->boundinfo != NULL)
+		{
+			if (pdesc2->boundinfo == NULL)
+				return false;
+
+			if (!partition_bounds_equal(key,
+										pdesc1->boundinfo, pdesc2->boundinfo))
+				return false;
+		}
+		else if (pdesc2->boundinfo != NULL)
+			return false;
+	}
+	else if (pdesc2 != NULL)
+		return false;
+
+	return true;
+}
+
+/*
  *		RelationBuildDesc
  *
  *		Build a relation descriptor.  The caller must hold at least
@@ -1288,13 +1343,18 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	relation->rd_fkeylist = NIL;
 	relation->rd_fkeyvalid = false;
 
-	/* if it's a partitioned table, initialize key info */
+	/* if a partitioned table, initialize key and partition descriptor info */
 	if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
 		RelationBuildPartitionKey(relation);
+		RelationBuildPartitionDesc(relation);
+	}
 	else
 	{
 		relation->rd_partkeycxt = NULL;
 		relation->rd_partkey = NULL;
+		relation->rd_partdesc = NULL;
+		relation->rd_pdcxt = NULL;
 	}
 
 	/*
@@ -2291,6 +2351,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		MemoryContextDelete(relation->rd_rsdesc->rscxt);
 	if (relation->rd_partkeycxt)
 		MemoryContextDelete(relation->rd_partkeycxt);
+	if (relation->rd_pdcxt)
+		MemoryContextDelete(relation->rd_pdcxt);
+	if (relation->rd_partcheck)
+		pfree(relation->rd_partcheck);
 	if (relation->rd_fdwroutine)
 		pfree(relation->rd_fdwroutine);
 	pfree(relation);
@@ -2439,11 +2503,12 @@ RelationClearRelation(Relation relation, bool rebuild)
 		 *
 		 * When rebuilding an open relcache entry, we must preserve ref count,
 		 * rd_createSubid/rd_newRelfilenodeSubid, and rd_toastoid state.  Also
-		 * attempt to preserve the pg_class entry (rd_rel), tupledesc, and
-		 * rewrite-rule substructures in place, because various places assume
-		 * that these structures won't move while they are working with an
-		 * open relcache entry.  (Note: the refcount mechanism for tupledescs
-		 * might someday allow us to remove this hack for the tupledesc.)
+		 * attempt to preserve the pg_class entry (rd_rel), tupledesc,
+		 * rewrite-rule, and partition descriptor substructures in place,
+		 * because various places assume that these structures won't move while
+		 * they are working with an open relcache entry.  (Note: the refcount
+		 * mechanism for tupledescs might someday allow us to remove this hack
+		 * for the tupledesc.)
 		 *
 		 * Note that this process does not touch CurrentResourceOwner; which
 		 * is good because whatever ref counts the entry may have do not
@@ -2454,6 +2519,7 @@ RelationClearRelation(Relation relation, bool rebuild)
 		bool		keep_tupdesc;
 		bool		keep_rules;
 		bool		keep_policies;
+		bool		keep_partdesc;
 
 		/* Build temporary entry, but don't link it into hashtable */
 		newrel = RelationBuildDesc(save_relid, false);
@@ -2484,6 +2550,9 @@ RelationClearRelation(Relation relation, bool rebuild)
 		keep_tupdesc = equalTupleDescs(relation->rd_att, newrel->rd_att);
 		keep_rules = equalRuleLocks(relation->rd_rules, newrel->rd_rules);
 		keep_policies = equalRSDesc(relation->rd_rsdesc, newrel->rd_rsdesc);
+		keep_partdesc = equalPartitionDescs(relation->rd_partkey,
+											relation->rd_partdesc,
+											newrel->rd_partdesc);
 
 		/*
 		 * Perform swapping of the relcache entry contents.  Within this
@@ -2539,6 +2608,13 @@ RelationClearRelation(Relation relation, bool rebuild)
 		/* pgstat_info must be preserved */
 		SWAPFIELD(struct PgStat_TableStatus *, pgstat_info);
 
+		/* preserve old partdesc if no logical change */
+		if (keep_partdesc)
+		{
+			SWAPFIELD(PartitionDesc, rd_partdesc);
+			SWAPFIELD(MemoryContext, rd_pdcxt);
+		}
+
 #undef SWAPFIELD
 
 		/* And now we can throw away the temporary entry */
@@ -3773,6 +3849,9 @@ RelationCacheInitializePhase3(void)
 			RelationBuildPartitionKey(relation);
 			Assert(relation->rd_partkey != NULL);
 
+			RelationBuildPartitionDesc(relation);
+			Assert(relation->rd_partdesc != NULL);
+
 			restart = true;
 		}
 
@@ -5301,6 +5380,8 @@ load_relcache_init_file(bool shared)
 		rel->rd_rsdesc = NULL;
 		rel->rd_partkeycxt = NULL;
 		rel->rd_partkey = NULL;
+		rel->rd_partdesc = NULL;
+		rel->rd_partcheck = NIL;
 		rel->rd_indexprs = NIL;
 		rel->rd_indpred = NIL;
 		rel->rd_exclops = NULL;
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 11b16a9..77dc198 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -143,5 +143,6 @@ extern void StorePartitionKey(Relation rel,
 					Oid *partopclass,
 					Oid *partcollation);
 extern void RemovePartitionKeyByRelId(Oid relid);
+extern void StorePartitionBound(Relation rel, Node *bound);
 
 #endif   /* HEAP_H */
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
new file mode 100644
index 0000000..062de88
--- /dev/null
+++ b/src/include/catalog/partition.h
@@ -0,0 +1,48 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.h
+ *		Header file for structures and utility functions related to
+ *		partitioning
+ *
+ * Copyright (c) 2007-2016, PostgreSQL Global Development Group
+ *
+ * src/include/catalog/partition.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARTITION_H
+#define PARTITION_H
+
+#include "fmgr.h"
+#include "parser/parse_node.h"
+#include "utils/rel.h"
+
+/*
+ * BoundCollection encapsulates a set of partition bounds.  It is usually
+ * associated with partitioned tables as part of its partition descriptor.
+ *
+ * The internal structure is opaque outside partition.c.
+ */
+typedef struct BoundCollectionData *BoundCollection;
+
+/*
+ * Information about partitions of a partitioned table.
+ */
+typedef struct PartitionDescData
+{
+	int					nparts;		/* Number of partitions */
+	Oid				   *oids;		/* OIDs of partitions */
+	BoundCollection		boundinfo;	/* collection of partition bounds */
+} PartitionDescData;
+
+typedef struct PartitionDescData *PartitionDesc;
+
+extern void RelationBuildPartitionDesc(Relation relation);
+extern bool partition_bounds_equal(PartitionKey key,
+					   BoundCollection p1, BoundCollection p2);
+
+extern void check_new_partition_bound(char *relname, Oid parentId, Node *bound);
+extern Oid get_partition_parent(Oid relid);
+extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
+extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+#endif   /* PARTITION_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index ba0f745..62a9377 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -70,6 +70,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 										 * not */
 	bool		relispopulated; /* matview currently holds query results */
 	char		relreplident;	/* see REPLICA_IDENTITY_xxx constants  */
+	bool		relispartition;	/* is relation a partition? */
 	TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
 	TransactionId relminmxid;	/* all multixacts in this rel are >= this.
 								 * this is really a MultiXactId */
@@ -78,6 +79,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 	/* NOTE: These fields are not present in a relcache entry's rd_rel field. */
 	aclitem		relacl[1];		/* access permissions */
 	text		reloptions[1];	/* access-method-specific options */
+	pg_node_tree relpartbound;	/* partition bound node tree */
 #endif
 } FormData_pg_class;
 
@@ -97,7 +99,7 @@ typedef FormData_pg_class *Form_pg_class;
  * ----------------
  */
 
-#define Natts_pg_class						31
+#define Natts_pg_class						33
 #define Anum_pg_class_relname				1
 #define Anum_pg_class_relnamespace			2
 #define Anum_pg_class_reltype				3
@@ -125,10 +127,12 @@ typedef FormData_pg_class *Form_pg_class;
 #define Anum_pg_class_relforcerowsecurity	25
 #define Anum_pg_class_relispopulated		26
 #define Anum_pg_class_relreplident			27
-#define Anum_pg_class_relfrozenxid			28
-#define Anum_pg_class_relminmxid			29
-#define Anum_pg_class_relacl				30
-#define Anum_pg_class_reloptions			31
+#define Anum_pg_class_relispartition		28
+#define Anum_pg_class_relfrozenxid			29
+#define Anum_pg_class_relminmxid			30
+#define Anum_pg_class_relacl				31
+#define Anum_pg_class_reloptions			32
+#define Anum_pg_class_relpartbound			33
 
 /* ----------------
  *		initial contents of pg_class
@@ -143,13 +147,13 @@ typedef FormData_pg_class *Form_pg_class;
  * Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId;
  * similarly, "1" in relminmxid stands for FirstMultiXactId
  */
-DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
-DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 31 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 33 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
 DESCR("");
 
 
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 7a770f4..fa48f2e 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -23,7 +23,7 @@
 
 
 extern ObjectAddress DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
-			   ObjectAddress *typaddress);
+			   ObjectAddress *typaddress, const char *queryString);
 
 extern void RemoveRelations(DropStmt *drop);
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index b27412c..c514d3f 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -406,6 +406,7 @@ typedef enum NodeTag
 	T_AlterPolicyStmt,
 	T_CreateTransformStmt,
 	T_CreateAmStmt,
+	T_PartitionCmd,
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -456,6 +457,8 @@ typedef enum NodeTag
 	T_TriggerTransition,
 	T_PartitionElem,
 	T_PartitionSpec,
+	T_PartitionBoundSpec,
+	T_PartitionRangeDatum,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d30c82b..427eff2 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -728,6 +728,51 @@ typedef struct PartitionSpec
 #define PARTITION_STRATEGY_LIST		'l'
 #define PARTITION_STRATEGY_RANGE	'r'
 
+/*
+ * PartitionBoundSpec - a partition bound specification
+ */
+typedef struct PartitionBoundSpec
+{
+	NodeTag		type;
+
+	char		strategy;
+
+	/* List partition values */
+	List	   *listdatums;
+
+	/*
+	 * Range partition lower and upper bounds; each member of the lists
+	 * is a PartitionRangeDatum (see below).
+	 */
+	List	   *lowerdatums;
+	List	   *upperdatums;
+
+	int			location;
+} PartitionBoundSpec;
+
+/*
+ * PartitionRangeDatum
+ */
+typedef struct PartitionRangeDatum
+{
+	NodeTag		type;
+
+	bool		infinite;
+	Node	   *value;
+
+	int			location;
+} PartitionRangeDatum;
+
+/*
+ * PartitionCmd -  ALTER TABLE partition commands
+ */
+typedef struct PartitionCmd
+{
+	NodeTag		type;
+	RangeVar   *name;
+	Node	   *bound;
+} PartitionCmd;
+
 /****************************************************************************
  *	Nodes for a Query tree
  ****************************************************************************/
@@ -1577,7 +1622,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
@@ -1803,7 +1850,8 @@ typedef struct CreateStmt
 	List	   *tableElts;		/* column definitions (list of ColumnDef) */
 	List	   *inhRelations;	/* relations to inherit from (list of
 								 * inhRelation) */
-	PartitionSpec *partspec;		/* PARTITION BY clause */
+	Node	   *partbound;		/* FOR VALUES clause */
+	PartitionSpec *partspec;	/* PARTITION BY clause */
 	TypeName   *ofTypename;		/* OF typename */
 	List	   *constraints;	/* constraints (list of Constraint nodes) */
 	List	   *options;		/* options from WITH clause */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 77d873b..581ff6e 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)
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index be3b6f7..783bb00 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -25,5 +25,7 @@ extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 				  List **actions, Node **whereClause);
 extern List *transformCreateSchemaStmt(CreateSchemaStmt *stmt);
+extern Node *transformPartitionBound(ParseState *pstate, Relation parent,
+						Node *bound);
 
 #endif   /* PARSE_UTILCMD_H */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 60d8de3..cd7ea1d 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -125,6 +125,9 @@ typedef struct RelationData
 
 	MemoryContext		 rd_partkeycxt;	/* private memory cxt for the below */
 	struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+	MemoryContext 	rd_pdcxt;		/* private context for partdesc */
+	struct PartitionDescData *rd_partdesc;	/* partitions, or NULL */
+	List		   *rd_partcheck;	/* partition CHECK quals */
 
 	/* data managed by RelationGetIndexList: */
 	List	   *rd_indexlist;	/* list of OIDs of indexes on relation */
@@ -602,6 +605,24 @@ get_partition_col_attnum(PartitionKey key, int col)
 	return key->partattrs[col];
 }
 
+static inline Oid
+get_partition_col_typid(PartitionKey key, int col)
+{
+	return key->parttypid[col];
+}
+
+static inline int32
+get_partition_col_typmod(PartitionKey key, int col)
+{
+	return key->parttypmod[col];
+}
+
+/*
+ * RelationGetPartitionDesc
+ *		Returns partition descriptor for a relation.
+ */
+#define RelationGetPartitionDesc(relation) ((relation)->rd_partdesc)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index fb492ad..0672018 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3017,3 +3017,301 @@ ERROR:  cannot inherit from partitioned table "partitioned"
 ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
 ERROR:  cannot add NO INHERIT constraint to partitioned table "partitioned"
 DROP TABLE partitioned, foo;
+--
+-- ATTACH PARTITION
+--
+-- check that target table is partitioned
+CREATE TABLE unparted (
+	a int
+);
+CREATE TABLE fail_part (like unparted);
+ALTER TABLE unparted ATTACH PARTITION fail_part FOR VALUES IN ('a');
+ERROR:  "unparted" is not partitioned
+DROP TABLE unparted, fail_part;
+-- check that partition bound is compatible
+CREATE TABLE list_parted (
+	a int NOT NULL,
+	b char(2) COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part (LIKE list_parted);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES FROM (1) TO (10);
+ERROR:  invalid bound specification for a list partition
+LINE 1: ...list_parted ATTACH PARTITION fail_part FOR VALUES FROM (1) T...
+                                                             ^
+DROP TABLE fail_part;
+-- check that the table being attached exists
+ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
+ERROR:  relation "nonexistant" does not exist
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+ERROR:  must be owner of relation not_owned_by_me
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+-- check that the table being attached is not part of regular inheritance
+CREATE TABLE parent (LIKE list_parted);
+CREATE TABLE child () INHERITS (parent);
+ALTER TABLE list_parted ATTACH PARTITION child FOR VALUES IN (1);
+ERROR:  cannot attach inheritance child as partition
+ALTER TABLE list_parted ATTACH PARTITION parent FOR VALUES IN (1);
+ERROR:  cannot attach inheritance parent as partition
+DROP TABLE parent CASCADE;
+NOTICE:  drop cascades to table child
+-- check that the table being attached is not a typed table
+CREATE TYPE mytype AS (a int);
+CREATE TABLE fail_part OF mytype;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  cannot attach a typed table as partition
+DROP TYPE mytype CASCADE;
+NOTICE:  drop cascades to table fail_part
+-- check existence (or non-existence) of oid column
+ALTER TABLE list_parted SET WITH OIDS;
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  cannot attach table "fail_part" without OIDs as partition of table "list_parted" with OIDs
+ALTER TABLE list_parted SET WITHOUT OIDS;
+ALTER TABLE fail_part SET WITH OIDS;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  cannot attach table "fail_part" with OIDs as partition of table "list_parted" without OIDs
+DROP TABLE fail_part;
+-- check that the table being attached has only columns present in the parent
+CREATE TABLE fail_part (like list_parted, c int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  table "fail_part" contains column "c" not found in parent "list_parted"
+DETAIL:  New partition should contain only the columns present in parent.
+DROP TABLE fail_part;
+-- check that the table being attached has every column of the parent
+CREATE TABLE fail_part (a int NOT NULL);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  child table is missing column "b"
+DROP TABLE fail_part;
+-- check that columns match in type, collation and NOT NULL status
+CREATE TABLE fail_part (
+	b char(3),
+	a int NOT NULL
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  child table "fail_part" has different type for column "b"
+ALTER TABLE fail_part ALTER b TYPE char (2) COLLATE "en_CA";
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  child table "fail_part" has different collation for column "b"
+DROP TABLE fail_part;
+-- check that the table being attached has all constraints of the parent
+CREATE TABLE fail_part (
+	b char(2) COLLATE "en_US",
+	a int NOT NULL
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  child table is missing constraint "check_a"
+-- check that the constraint matches in definition with parent's constraint
+ALTER TABLE fail_part ADD CONSTRAINT check_a CHECK (a >= 0);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  child table "fail_part" has different definition for check constraint "check_a"
+DROP TABLE fail_part;
+-- check the attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+	a int NOT NULL,
+	b char(2) COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+-- attislocal and conislocal are always false for merged attributes and constraints respectively.
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+ attislocal | attinhcount 
+------------+-------------
+ f          |           1
+ f          |           1
+(2 rows)
+
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+ conislocal | coninhcount 
+------------+-------------
+ f          |           1
+(1 row)
+
+-- 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);
+ERROR:  partition "fail_part" would overlap partition "part_1"
+-- check validation when attaching list partitions
+CREATE TABLE list_parted2 (
+	a int,
+	b char
+) PARTITION BY LIST (a);
+-- check that violating rows are correctly reported
+CREATE TABLE part_2 (LIKE list_parted2);
+INSERT INTO part_2 VALUES (3, 'a');
+ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+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);
+-- adding constraints that describe the desired partition constraint
+-- (or more restrictive) will help skip the validation scan
+CREATE TABLE part_3_4 (
+	LIKE list_parted2,
+	CONSTRAINT check_a CHECK (a IN (3))
+);
+-- however, if a list partition does not accept nulls, there should be
+-- an explicit NOT NULL constraint on the partition key column for the
+-- validation scan to be skipped;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
+-- adding a NOT NULL constraint will cause the scan to be skipped
+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);
+NOTICE:  skipping scan to validate partition constraint
+-- check validation when attaching range partitions
+CREATE TABLE range_parted (
+	a int,
+	b int
+) PARTITION BY RANGE (a, b);
+-- check that violating rows are correctly reported
+CREATE TABLE part1 (
+	a int CHECK (a = 1),
+	b int CHECK (b >= 1 AND b <= 10)
+);
+INSERT INTO part1 VALUES (1, 10);
+-- Remember the TO bound is exclusive
+ALTER TABLE range_parted ATTACH PARTITION part1 FOR VALUES FROM (1, 1) TO (1, 10);
+ERROR:  partition constraint is violated by some row
+-- should be ok after deleting the bad row
+DELETE FROM part1;
+ALTER TABLE range_parted ATTACH PARTITION part1 FOR VALUES FROM (1, 1) TO (1, 10);
+-- adding constraints that describe the desired partition constraint
+-- (or more restrictive) will help skip the validation scan
+CREATE TABLE part2 (
+	a int CHECK (a = 1),
+	b int CHECK (b >= 10 AND b < 18)
+);
+-- however, range partition key cannot contain NULLs, so there should be
+-- explicit NOT NULL constraints on the key columns for the validation scan
+-- to be skipped
+ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
+-- set a and b to NOT NULL and the validation scan will be skipped
+ALTER TABLE range_parted DETACH PARTITION part2;
+ALTER TABLE part2 ALTER a SET NOT NULL, ALTER b SET NOT NULL;
+ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
+NOTICE:  skipping scan to validate partition constraint
+-- check that leaf partitions are scanned when attaching a partitioned
+-- table
+CREATE TABLE part_5 (
+	LIKE list_parted2
+) PARTITION BY LIST (b);
+-- check that violating rows are correctly reported
+CREATE TABLE part_5_a PARTITION OF part_5 FOR VALUES IN ('a');
+INSERT INTO part_5_a (a, b) VALUES (6, 'a');
+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);
+NOTICE:  skipping scan to validate partition constraint
+-- 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
+-- check that circular inheritance is not allowed
+ALTER TABLE part_5 ATTACH PARTITION list_parted2 FOR VALUES IN ('b');
+ERROR:  circular inheritance not allowed
+DETAIL:  "part_5" is already a child of "list_parted2".
+ALTER TABLE list_parted2 ATTACH PARTITION list_parted2 FOR VALUES IN (0);
+ERROR:  circular inheritance not allowed
+DETAIL:  "list_parted2" is already a child of "list_parted2".
+--
+-- DETACH PARTITION
+--
+-- check that the partition being detached exists at all
+ALTER TABLE list_parted2 DETACH PARTITION part_4;
+ERROR:  relation "part_4" does not exist
+-- check that the partition being detached is actually a partition of the parent
+CREATE TABLE not_a_part (a int);
+ALTER TABLE list_parted2 DETACH PARTITION not_a_part;
+ERROR:  relation "not_a_part" is not a partition of relation "list_parted2"
+ALTER TABLE list_parted2 DETACH PARTITION part_1;
+ERROR:  relation "part_1" is not a partition of relation "list_parted2"
+-- check that, after being detached, attinhcount/coninhcount is dropped to 0 and
+-- attislocal/conislocal is set to true
+ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
+SELECT attinhcount, attislocal FROM pg_attribute WHERE attrelid = 'part_3_4'::regclass AND attnum > 0;
+ attinhcount | attislocal 
+-------------+------------
+           0 | t
+           0 | t
+(2 rows)
+
+SELECT coninhcount, conislocal FROM pg_constraint WHERE conrelid = 'part_3_4'::regclass AND conname = 'check_a';
+ coninhcount | conislocal 
+-------------+------------
+           0 | t
+(1 row)
+
+DROP TABLE part_3_4;
+-- Check ALTER TABLE commands for partitioned tables and partitions
+-- cannot add/drop column to/from *only* the parent
+ALTER TABLE ONLY list_parted2 ADD COLUMN c int;
+ERROR:  column must be added to child tables too
+ALTER TABLE ONLY list_parted2 DROP COLUMN b;
+ERROR:  column must be dropped from child tables too
+-- cannot add a column to partition or drop an inherited one
+ALTER TABLE part_2 ADD COLUMN c text;
+ERROR:  cannot add column to a partition
+ALTER TABLE part_2 DROP COLUMN b;
+ERROR:  cannot drop inherited column "b"
+-- Nor rename, alter type
+ALTER TABLE part_2 RENAME COLUMN b to c;
+ERROR:  cannot rename inherited column "b"
+ALTER TABLE part_2 ALTER COLUMN b TYPE text;
+ERROR:  cannot alter inherited column "b"
+-- cannot add NOT NULL or check constraints to *only* the parent (ie, non-inherited)
+ALTER TABLE ONLY list_parted2 ALTER b SET NOT NULL;
+ERROR:  constraint must be added to child tables too
+ALTER TABLE ONLY list_parted2 add constraint check_b check (b <> 'zz');
+ERROR:  constraint must be added to child tables too
+ALTER TABLE list_parted2 add constraint check_b check (b <> 'zz') NO INHERIT;
+ERROR:  cannot add NO INHERIT constraint to partitioned table "list_parted2"
+-- cannot drop inherited NOT NULL or check constraints from partition
+ALTER TABLE list_parted2 ALTER b SET NOT NULL, ADD CONSTRAINT check_a2 CHECK (a > 0);
+ALTER TABLE part_2 ALTER b DROP NOT NULL;
+ERROR:  column "b" is marked NOT NULL in parent table
+ALTER TABLE part_2 DROP CONSTRAINT check_a2;
+ERROR:  cannot drop inherited constraint "check_a2" of relation "part_2"
+-- cannot drop NOT NULL or check constraints from *only* the parent
+ALTER TABLE ONLY list_parted2 ALTER a DROP NOT NULL;
+ERROR:  constraint must be dropped from child tables too
+ALTER TABLE ONLY list_parted2 DROP CONSTRAINT check_a2;
+ERROR:  constraint must be dropped from child tables too
+-- check that a partition cannot participate in regular inheritance
+CREATE TABLE inh_test () INHERITS (part_2);
+ERROR:  cannot inherit from partition "part_2"
+CREATE TABLE inh_test (LIKE part_2);
+ALTER TABLE inh_test INHERIT part_2;
+ERROR:  cannot inherit from a partition
+ALTER TABLE part_2 INHERIT inh_test;
+ERROR:  cannot change inheritance of a partition
+-- cannot drop or alter type of partition key columns of lower level
+-- partitioned tables; for example, part_5, which is list_parted2's
+-- partition, is partitioned on b;
+ALTER TABLE list_parted2 DROP COLUMN b;
+ERROR:  cannot drop column named in partition key
+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 CASCADE;
+NOTICE:  drop cascades to 6 other objects
+DETAIL:  drop cascades to table part1
+drop cascades to table part2
+drop cascades to table part_2
+drop cascades to table part_5
+drop cascades to table part_5_a
+drop cascades to table part_1
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 0f15c98..01124e1 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -429,3 +429,190 @@ Partition key: RANGE (a oid_ops, plusone(b), c, d COLLATE "en_US")
 Partition key: LIST ((a + 1))
 
 DROP TABLE partitioned, partitioned2;
+--
+-- Partitions
+--
+-- check partition bound syntax
+CREATE TABLE list_parted (
+	a int
+) PARTITION BY LIST (a);
+-- syntax allows only string literal, numeric literal and null to be
+-- specified for a partition bound value
+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 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');
+                                                              ^
+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);
+                                                                ^
+-- 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 ")"
+LINE 1: ...E 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);
+ERROR:  invalid bound specification for a list partition
+LINE 1: ...BLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) T...
+                                                             ^
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE bools (
+	a bool
+) PARTITION BY LIST (a);
+CREATE TABLE bools_true PARTITION OF bools FOR VALUES IN (1);
+ERROR:  specified value cannot be cast to type "boolean" of column "a"
+LINE 1: ...REATE TABLE bools_true PARTITION OF bools FOR VALUES IN (1);
+                                                                    ^
+DROP TABLE bools;
+CREATE TABLE range_parted (
+	a date
+) PARTITION BY RANGE (a);
+-- trying to specify list for range partitioned table
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES IN ('a');
+ERROR:  invalid bound specification for a range partition
+LINE 1: ...BLE fail_part PARTITION OF range_parted FOR VALUES IN ('a');
+                                                              ^
+-- each of start and end bounds must have same number of values as the
+-- length of the partition key
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a', 1) TO ('z');
+ERROR:  FROM must specify exactly one value per partitioning column
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a') TO ('z', 1);
+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
+-- check if compatible with the specified parent
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE unparted (
+	a int
+);
+CREATE TABLE fail_part PARTITION OF unparted FOR VALUES IN ('a');
+ERROR:  "unparted" is not partitioned
+DROP TABLE unparted;
+-- cannot create a permanent rel as partition of a temp rel
+CREATE TEMP TABLE temp_parted (
+	a int
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part PARTITION OF temp_parted FOR VALUES IN ('a');
+ERROR:  cannot create as partition of temporary relation "temp_parted"
+DROP TABLE temp_parted;
+-- cannot create a table with oids as partition of table without oids
+CREATE TABLE no_oids_parted (
+	a int
+) PARTITION BY RANGE (a) WITHOUT OIDS;
+CREATE TABLE fail_part PARTITION OF no_oids_parted FOR VALUES FROM (1) TO (10 )WITH OIDS;
+ERROR:  cannot create table with OIDs as partition of table without OIDs
+DROP TABLE no_oids_parted;
+-- likewise, the reverse if also true
+CREATE TABLE oids_parted (
+	a int
+) PARTITION BY RANGE (a) WITH OIDS;
+CREATE TABLE fail_part PARTITION OF oids_parted FOR VALUES FROM (1) TO (10 ) WITHOUT OIDS;
+ERROR:  cannot create table without OIDs as partition of table with OIDs
+DROP TABLE oids_parted;
+-- check for partition bound overlap and other invalid specifications
+CREATE TABLE list_parted2 (
+	a varchar
+) 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 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"
+CREATE TABLE range_parted2 (
+	a int
+) PARTITION BY RANGE (a);
+-- trying to create range partition with empty range
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (0);
+ERROR:  cannot create range partition with empty range
+-- note that the range '[1, 1)' has no elements
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (1);
+ERROR:  cannot create range partition with empty range
+CREATE TABLE part0 PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (1);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (2);
+ERROR:  partition "fail_part" would overlap partition "part0"
+CREATE TABLE part1 PARTITION OF range_parted2 FOR VALUES FROM (1) TO (10);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (9) TO (unbounded);
+ERROR:  partition "fail_part" would overlap partition "part1"
+-- now check for multi-column range partition key
+CREATE TABLE range_parted3 (
+	a int,
+	b int
+) PARTITION BY RANGE (a, (b+1));
+CREATE TABLE part00 PARTITION OF range_parted3 FOR VALUES FROM (0, unbounded) TO (0, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (0, unbounded) TO (0, 1);
+ERROR:  partition "fail_part" would overlap partition "part00"
+CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO (1, 1);
+CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10);
+CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
+ERROR:  partition "fail_part" would overlap partition "part12"
+-- 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, unbounded) TO (1, unbounded);
+ERROR:  partition "fail_part" would overlap partition "part12"
+-- check schema propagation from parent
+CREATE TABLE parted (
+	a text,
+	b int NOT NULL DEFAULT 0,
+	CONSTRAINT check_a CHECK (length(a) > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a');
+-- only inherited attributes (never local ones)
+SELECT attname, attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_a'::regclass and attnum > 0;
+ attname | attislocal | attinhcount 
+---------+------------+-------------
+ a       | f          |           1
+ b       | f          |           1
+(2 rows)
+
+-- able to specify column default, column constraint, and table constraint
+CREATE TABLE part_b PARTITION OF parted (
+	b WITH OPTIONS NOT NULL DEFAULT 1 CHECK (b >= 0),
+	CONSTRAINT check_a CHECK (length(a) > 0)
+) FOR VALUES IN ('b');
+NOTICE:  merging constraint "check_a" with inherited definition
+-- conislocal should be false for any merged constraints
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_a';
+ conislocal | coninhcount 
+------------+-------------
+ f          |           1
+(1 row)
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_part_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+ERROR:  column "c" named in partition key does not exist
+CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+-- create a level-2 partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
+-- partitions cannot be dropped directly
+DROP TABLE part_a;
+-- need to specify CASCADE to drop partitions along with the parent
+DROP TABLE parted;
+ERROR:  cannot drop table parted because other objects depend on it
+DETAIL:  table part_b depends on table parted
+table part_c depends on table parted
+table part_c_1_10 depends on table part_c
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
+NOTICE:  drop cascades to 14 other objects
+DETAIL:  drop cascades to table part00
+drop cascades to table part10
+drop cascades to table part11
+drop cascades to table part12
+drop cascades to table part0
+drop cascades to table part1
+drop cascades to table part_null_z
+drop cascades to table part_ab
+drop cascades to table part_1
+drop cascades to table part_2
+drop cascades to table part_null
+drop cascades to table part_b
+drop cascades to table part_c
+drop cascades to table part_c_1_10
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index d929b4d..21f319a 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1904,3 +1904,269 @@ ALTER TABLE foo INHERIT partitioned;
 ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
 
 DROP TABLE partitioned, foo;
+
+--
+-- ATTACH PARTITION
+--
+
+-- check that target table is partitioned
+CREATE TABLE unparted (
+	a int
+);
+CREATE TABLE fail_part (like unparted);
+ALTER TABLE unparted ATTACH PARTITION fail_part FOR VALUES IN ('a');
+DROP TABLE unparted, fail_part;
+
+-- check that partition bound is compatible
+CREATE TABLE list_parted (
+	a int NOT NULL,
+	b char(2) COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part (LIKE list_parted);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES FROM (1) TO (10);
+DROP TABLE fail_part;
+
+-- check that the table being attached exists
+ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
+
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+	a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+
+-- check that the table being attached is not part of regular inheritance
+CREATE TABLE parent (LIKE list_parted);
+CREATE TABLE child () INHERITS (parent);
+ALTER TABLE list_parted ATTACH PARTITION child FOR VALUES IN (1);
+ALTER TABLE list_parted ATTACH PARTITION parent FOR VALUES IN (1);
+DROP TABLE parent CASCADE;
+
+-- check that the table being attached is not a typed table
+CREATE TYPE mytype AS (a int);
+CREATE TABLE fail_part OF mytype;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TYPE mytype CASCADE;
+
+-- check existence (or non-existence) of oid column
+ALTER TABLE list_parted SET WITH OIDS;
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+ALTER TABLE list_parted SET WITHOUT OIDS;
+ALTER TABLE fail_part SET WITH OIDS;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check that the table being attached has only columns present in the parent
+CREATE TABLE fail_part (like list_parted, c int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check that the table being attached has every column of the parent
+CREATE TABLE fail_part (a int NOT NULL);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check that columns match in type, collation and NOT NULL status
+CREATE TABLE fail_part (
+	b char(3),
+	a int NOT NULL
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ALTER TABLE fail_part ALTER b TYPE char (2) COLLATE "en_CA";
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check that the table being attached has all constraints of the parent
+CREATE TABLE fail_part (
+	b char(2) COLLATE "en_US",
+	a int NOT NULL
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+-- check that the constraint matches in definition with parent's constraint
+ALTER TABLE fail_part ADD CONSTRAINT check_a CHECK (a >= 0);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+	a int NOT NULL,
+	b char(2) COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0)
+);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+-- attislocal and conislocal are always false for merged attributes and constraints respectively.
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+
+-- 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 validation when attaching list partitions
+CREATE TABLE list_parted2 (
+	a int,
+	b char
+) PARTITION BY LIST (a);
+
+-- check that violating rows are correctly reported
+CREATE TABLE part_2 (LIKE list_parted2);
+INSERT INTO part_2 VALUES (3, 'a');
+ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+
+-- should be ok after deleting the bad row
+DELETE FROM part_2;
+ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+
+-- adding constraints that describe the desired partition constraint
+-- (or more restrictive) will help skip the validation scan
+CREATE TABLE part_3_4 (
+	LIKE list_parted2,
+	CONSTRAINT check_a CHECK (a IN (3))
+);
+
+-- however, if a list partition does not accept nulls, there should be
+-- an explicit NOT NULL constraint on the partition key column for the
+-- validation scan to be skipped;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
+
+-- adding a NOT NULL constraint will cause the scan to be skipped
+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 validation when attaching range partitions
+CREATE TABLE range_parted (
+	a int,
+	b int
+) PARTITION BY RANGE (a, b);
+
+-- check that violating rows are correctly reported
+CREATE TABLE part1 (
+	a int CHECK (a = 1),
+	b int CHECK (b >= 1 AND b <= 10)
+);
+INSERT INTO part1 VALUES (1, 10);
+-- Remember the TO bound is exclusive
+ALTER TABLE range_parted ATTACH PARTITION part1 FOR VALUES FROM (1, 1) TO (1, 10);
+
+-- should be ok after deleting the bad row
+DELETE FROM part1;
+ALTER TABLE range_parted ATTACH PARTITION part1 FOR VALUES FROM (1, 1) TO (1, 10);
+
+-- adding constraints that describe the desired partition constraint
+-- (or more restrictive) will help skip the validation scan
+CREATE TABLE part2 (
+	a int CHECK (a = 1),
+	b int CHECK (b >= 10 AND b < 18)
+);
+
+-- however, range partition key cannot contain NULLs, so there should be
+-- explicit NOT NULL constraints on the key columns for the validation scan
+-- to be skipped
+ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
+
+-- set a and b to NOT NULL and the validation scan will be skipped
+ALTER TABLE range_parted DETACH PARTITION part2;
+ALTER TABLE part2 ALTER a SET NOT NULL, ALTER b SET NOT NULL;
+ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
+
+
+-- check that leaf partitions are scanned when attaching a partitioned
+-- table
+CREATE TABLE part_5 (
+	LIKE list_parted2
+) PARTITION BY LIST (b);
+
+-- check that violating rows are correctly reported
+CREATE TABLE part_5_a PARTITION OF part_5 FOR VALUES IN ('a');
+INSERT INTO part_5_a (a, b) VALUES (6, 'a');
+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);
+
+
+-- check that the table being attached is not already a partition
+ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+
+-- check that circular inheritance is not allowed
+ALTER TABLE part_5 ATTACH PARTITION list_parted2 FOR VALUES IN ('b');
+ALTER TABLE list_parted2 ATTACH PARTITION list_parted2 FOR VALUES IN (0);
+
+--
+-- DETACH PARTITION
+--
+
+-- check that the partition being detached exists at all
+ALTER TABLE list_parted2 DETACH PARTITION part_4;
+
+-- check that the partition being detached is actually a partition of the parent
+CREATE TABLE not_a_part (a int);
+ALTER TABLE list_parted2 DETACH PARTITION not_a_part;
+ALTER TABLE list_parted2 DETACH PARTITION part_1;
+
+-- check that, after being detached, attinhcount/coninhcount is dropped to 0 and
+-- attislocal/conislocal is set to true
+ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
+SELECT attinhcount, attislocal FROM pg_attribute WHERE attrelid = 'part_3_4'::regclass AND attnum > 0;
+SELECT coninhcount, conislocal FROM pg_constraint WHERE conrelid = 'part_3_4'::regclass AND conname = 'check_a';
+DROP TABLE part_3_4;
+
+-- Check ALTER TABLE commands for partitioned tables and partitions
+
+-- cannot add/drop column to/from *only* the parent
+ALTER TABLE ONLY list_parted2 ADD COLUMN c int;
+ALTER TABLE ONLY list_parted2 DROP COLUMN b;
+
+-- cannot add a column to partition or drop an inherited one
+ALTER TABLE part_2 ADD COLUMN c text;
+ALTER TABLE part_2 DROP COLUMN b;
+
+-- Nor rename, alter type
+ALTER TABLE part_2 RENAME COLUMN b to c;
+ALTER TABLE part_2 ALTER COLUMN b TYPE text;
+
+-- cannot add NOT NULL or check constraints to *only* the parent (ie, non-inherited)
+ALTER TABLE ONLY list_parted2 ALTER b SET NOT NULL;
+ALTER TABLE ONLY list_parted2 add constraint check_b check (b <> 'zz');
+ALTER TABLE list_parted2 add constraint check_b check (b <> 'zz') NO INHERIT;
+
+-- cannot drop inherited NOT NULL or check constraints from partition
+ALTER TABLE list_parted2 ALTER b SET NOT NULL, ADD CONSTRAINT check_a2 CHECK (a > 0);
+ALTER TABLE part_2 ALTER b DROP NOT NULL;
+ALTER TABLE part_2 DROP CONSTRAINT check_a2;
+
+-- cannot drop NOT NULL or check constraints from *only* the parent
+ALTER TABLE ONLY list_parted2 ALTER a DROP NOT NULL;
+ALTER TABLE ONLY list_parted2 DROP CONSTRAINT check_a2;
+
+-- check that a partition cannot participate in regular inheritance
+CREATE TABLE inh_test () INHERITS (part_2);
+CREATE TABLE inh_test (LIKE part_2);
+ALTER TABLE inh_test INHERIT part_2;
+ALTER TABLE part_2 INHERIT inh_test;
+
+-- cannot drop or alter type of partition key columns of lower level
+-- partitioned tables; for example, part_5, which is list_parted2's
+-- partition, is partitioned on b;
+ALTER TABLE list_parted2 DROP COLUMN b;
+ALTER TABLE list_parted2 ALTER COLUMN b TYPE text;
+
+-- cleanup
+DROP TABLE list_parted, list_parted2, range_parted CASCADE;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index f100498..683b852 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -416,3 +416,156 @@ CREATE TABLE fail () INHERITS (partitioned2);
 \d partitioned2
 
 DROP TABLE partitioned, partitioned2;
+
+--
+-- Partitions
+--
+
+-- check partition bound syntax
+
+CREATE TABLE list_parted (
+	a int
+) PARTITION BY LIST (a);
+-- syntax allows only string literal, numeric literal and null to be
+-- specified for a partition bound value
+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 fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+
+-- 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);
+
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE bools (
+	a bool
+) PARTITION BY LIST (a);
+CREATE TABLE bools_true PARTITION OF bools FOR VALUES IN (1);
+DROP TABLE bools;
+
+CREATE TABLE range_parted (
+	a date
+) PARTITION BY RANGE (a);
+
+-- trying to specify list for range partitioned table
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES IN ('a');
+-- each of start and end bounds must have same number of values as the
+-- length of the partition key
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a', 1) TO ('z');
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a') TO ('z', 1);
+
+-- cannot specify null values in range bounds
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (unbounded);
+
+-- check if compatible with the specified parent
+
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE unparted (
+	a int
+);
+CREATE TABLE fail_part PARTITION OF unparted FOR VALUES IN ('a');
+DROP TABLE unparted;
+
+-- cannot create a permanent rel as partition of a temp rel
+CREATE TEMP TABLE temp_parted (
+	a int
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part PARTITION OF temp_parted FOR VALUES IN ('a');
+DROP TABLE temp_parted;
+
+-- cannot create a table with oids as partition of table without oids
+CREATE TABLE no_oids_parted (
+	a int
+) PARTITION BY RANGE (a) WITHOUT OIDS;
+CREATE TABLE fail_part PARTITION OF no_oids_parted FOR VALUES FROM (1) TO (10 )WITH OIDS;
+DROP TABLE no_oids_parted;
+
+-- likewise, the reverse if also true
+CREATE TABLE oids_parted (
+	a int
+) PARTITION BY RANGE (a) WITH OIDS;
+CREATE TABLE fail_part PARTITION OF oids_parted FOR VALUES FROM (1) TO (10 ) WITHOUT OIDS;
+DROP TABLE oids_parted;
+
+-- check for partition bound overlap and other invalid specifications
+
+CREATE TABLE list_parted2 (
+	a varchar
+) 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 fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+
+CREATE TABLE range_parted2 (
+	a int
+) PARTITION BY RANGE (a);
+
+-- trying to create range partition with empty range
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (0);
+-- note that the range '[1, 1)' has no elements
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (1);
+
+CREATE TABLE part0 PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (1);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (2);
+CREATE TABLE part1 PARTITION OF range_parted2 FOR VALUES FROM (1) TO (10);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (9) TO (unbounded);
+
+-- now check for multi-column range partition key
+CREATE TABLE range_parted3 (
+	a int,
+	b int
+) PARTITION BY RANGE (a, (b+1));
+
+CREATE TABLE part00 PARTITION OF range_parted3 FOR VALUES FROM (0, unbounded) TO (0, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (0, unbounded) TO (0, 1);
+
+CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO (1, 1);
+CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10);
+CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
+
+-- 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, unbounded) TO (1, unbounded);
+
+-- check schema propagation from parent
+
+CREATE TABLE parted (
+	a text,
+	b int NOT NULL DEFAULT 0,
+	CONSTRAINT check_a CHECK (length(a) > 0)
+) PARTITION BY LIST (a);
+
+CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a');
+
+-- only inherited attributes (never local ones)
+SELECT attname, attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_a'::regclass and attnum > 0;
+
+-- able to specify column default, column constraint, and table constraint
+CREATE TABLE part_b PARTITION OF parted (
+	b WITH OPTIONS NOT NULL DEFAULT 1 CHECK (b >= 0),
+	CONSTRAINT check_a CHECK (length(a) > 0)
+) FOR VALUES IN ('b');
+-- conislocal should be false for any merged constraints
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_a';
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_part_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+
+-- create a level-2 partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
+
+-- partitions cannot be dropped directly
+DROP TABLE part_a;
+
+-- need to specify CASCADE to drop partitions along with the parent
+DROP TABLE parted;
+
+DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
-- 
1.7.1

