From 867b570df34a0e8f9a3d9aef5954b9111c88dd48 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.

Parent-child relationships of a partitioned table and its partitions are
managed with regular inheritance (pg_inherits catalog) so not much here
about that.

DDL includes both a way to create new partition and "attach" an existing table
as a partition.  Attempt to drop a partition using DROP TABLE causes an error.
Instead a partition needs first to be "detached".  On the other hand, dropping
the parent drops all the partitions if CASCADE specified.

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                 |   56 ++
 doc/src/sgml/ref/alter_table.sgml          |  122 +++-
 doc/src/sgml/ref/create_foreign_table.sgml |   28 +
 doc/src/sgml/ref/create_table.sgml         |  110 ++-
 src/backend/catalog/Makefile               |    4 +-
 src/backend/catalog/heap.c                 |   13 +-
 src/backend/catalog/partition.c            | 1484 ++++++++++++++++++++++++++++
 src/backend/catalog/pg_partition.c         |  106 ++
 src/backend/commands/foreigncmds.c         |   17 +
 src/backend/commands/lockcmds.c            |    4 +-
 src/backend/commands/sequence.c            |    1 +
 src/backend/commands/tablecmds.c           |  703 +++++++++++---
 src/backend/nodes/copyfuncs.c              |   48 +
 src/backend/nodes/equalfuncs.c             |   42 +
 src/backend/nodes/outfuncs.c               |   27 +
 src/backend/nodes/readfuncs.c              |   33 +
 src/backend/parser/gram.y                  |  209 ++++-
 src/backend/parser/parse_agg.c             |    6 +
 src/backend/parser/parse_expr.c            |   23 +
 src/backend/parser/parse_utilcmd.c         |  334 +++++++-
 src/backend/utils/cache/relcache.c         |   73 ++-
 src/backend/utils/cache/syscache.c         |   12 +
 src/include/catalog/indexing.h             |    3 +
 src/include/catalog/partition.h            |   24 +
 src/include/catalog/pg_partition.h         |   54 +
 src/include/catalog/pg_partition_fn.h      |   22 +
 src/include/nodes/nodes.h                  |    3 +
 src/include/nodes/parsenodes.h             |   43 +-
 src/include/parser/kwlist.h                |    3 +
 src/include/parser/parse_node.h            |    3 +-
 src/include/utils/rel.h                    |    9 +
 src/include/utils/syscache.h               |    1 +
 src/test/regress/expected/alter_table.out  |  185 ++++-
 src/test/regress/expected/create_table.out |  199 ++++
 src/test/regress/expected/sanity_check.out |    1 +
 src/test/regress/sql/alter_table.sql       |  153 +++-
 src/test/regress/sql/create_table.sql      |  135 +++
 37 files changed, 4140 insertions(+), 153 deletions(-)
 create mode 100644 src/backend/catalog/pg_partition.c
 create mode 100644 src/include/catalog/pg_partition.h
 create mode 100644 src/include/catalog/pg_partition_fn.h

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index b51323a..d5b3b5d 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -226,6 +226,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-partition"><structname>pg_partition</structname></link></entry>
+      <entry>information about partitions</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-partitioned"><structname>pg_partitioned</structname></link></entry>
       <entry>information about partitioned tables, including the partition key</entry>
      </row>
@@ -4673,6 +4678,57 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-partition">
+  <title><structname>pg_partition</structname></title>
+
+  <indexterm zone="catalog-pg-partition">
+   <primary>pg_partition</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_partition</structname> stores partition bounds
+   of tables that are partitions.
+  </para>
+
+  <table>
+   <title><structname>pg_partition</> Columns</title>
+
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Type</entry>
+      <entry>References</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+    <tbody>
+
+     <row>
+      <entry><structfield>partrelid</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+      <entry>The OID of the <structname>pg_class</> entry for this partition</entry>
+     </row>
+
+     <row>
+      <entry><structfield>partbound</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>
+       Expression tree (in <function>nodeToString()</function> representation)
+       for partition boundary value.  For list partitions, it is simply a list
+       of values.  For range partitions, it consists of lower and upper bound
+       values along with their inclusivity.
+      </entry>
+     </row>
+
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
  <sect1 id="catalog-pg-partitioned">
   <title><structname>pg_partitioned</structname></title>
 
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 5ca211e..54ef48b 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -77,12 +77,15 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     NOT OF
     OWNER TO { <replaceable class="PARAMETER">new_owner</replaceable> | CURRENT_USER | SESSION_USER }
     REPLICA IDENTITY { DEFAULT | USING INDEX <replaceable class="PARAMETER">index_name</replaceable> | FULL | NOTHING }
+    ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> <replaceable class="PARAMETER">partition_bound_spec</replaceable> [ VALIDATE | NO VALIDATE ]
+    DETACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable>
 
 <phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
 
     [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
     { UNIQUE | PRIMARY KEY } USING INDEX <replaceable class="PARAMETER">index_name</replaceable>
     [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
+
 </synopsis>
  </refsynopsisdiv>
 
@@ -166,6 +169,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 not null in the parent table.
+      not null.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -704,6 +713,57 @@ 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 non-partitioned table as partition of the
+      target table.  Partition bound specification must correspond with the
+      partition method and the key of the target table.  The table being
+      attached must have all the columns of the target table and no more.
+      Also, it must have all the matching constraints as the target table,
+      that is, with a matching name and a matching expression.  That inclues
+      <literal>NOT NULL</> and <literal>CHECK</> constraints.
+      <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and
+      <literal>FOREIGN KEY</literal> constraints are not considered, but that
+      might change in the future.
+     </para>
+
+     <para>
+      If <literal>NO VALIDATE</literal> clause is specified, full scan to
+      check existing rows in the table being attached to see if they fall
+      within the specified bounds is skipped.  This allows to quickly load a
+      large amount of data into the partitioned table typically called roll-in.
+      The default behavior is to scan the table to perform the check, as if
+      the <literal> VALIDATE</literal> clause were specified.
+     </para>
+
+     <para>
+      Note that once the table becomes a partition, it will be dropped or
+      truncated when the parent table is dropped or truncated.  Dropping
+      it directly using <literal>DROP TABLE</literal> will fail; it must
+      first be detached from the parent.  However, truncating a partition
+      directly works.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>DETACH PARTITION</literal></term>
+    <listitem>
+     <para>
+      This form detaches specified partition of the target table.  The
+      detached partition continues to exist as a standalone table with no ties
+      remaining with the partitioned table.
+     </para>
+     <para>
+      Note that unlike the <literal>ATTACH PARTITION</> command, a partition
+      being detached can be itself partitioned.  In that case, it continues
+      to exist as such.
+     </para>
+    </listitem>
+   </varlistentry>
+
   </variablelist>
   </para>
 
@@ -722,7 +782,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
    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.
+   parent table as well.  That applies to both adding as a inheritance child
+   and attaching a new partition to a partitioned table.
    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
@@ -758,7 +819,10 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
         that table is altered. If <literal>ONLY</> is not specified, the table
         and all its descendant tables (if any) are altered.  Optionally,
         <literal>*</> can be specified after the table name to explicitly
-        indicate that descendant tables are included.
+        indicate that descendant tables are included.  Note that whether
+        <literal>ONLY</> or <literal>*</> is specified has no effect in case
+        of a partitioned table; descendant tables (in this case, partitions)
+        are always included.
        </para>
       </listitem>
      </varlistentry>
@@ -936,6 +1000,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 to or 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>
 
@@ -976,6 +1058,12 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
    </para>
 
    <para>
+    Similarly, when attaching a new partition the source table is scanned to
+    verify that existing rows fall within the specified bounds, unless
+    <literal>NO VALIDATE</> option is spcified.
+   </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.
@@ -1044,11 +1132,18 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
    </para>
 
    <para>
+    With partitioned tables, the <literal>ONLY</> specification is simply
+    ignored and changes such as adding, dropping, changing the type of, or
+    renaming a column or a constraint are always applied to the partitions.
+   </para>
+
+   <para>
     The <literal>TRIGGER</>, <literal>CLUSTER</>, <literal>OWNER</>,
     and <literal>TABLESPACE</> actions never recurse to descendant tables;
     that is, they always act as though <literal>ONLY</> were specified.
     Adding a constraint recurses only for <literal>CHECK</> constraints
-    that are not marked <literal>NO INHERIT</>.
+    that are not marked <literal>NO INHERIT</>.  If the table is partitioned,
+    the <literal>NO INHERIT</> specification is ignored and recursion occurs.
    </para>
 
    <para>
@@ -1227,6 +1322,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 START ('2016-07-01') END ('2016-08-01');
+</programlisting></para>
+
+  <para>
+   Attach a partition to list partitioned table and ask to skip checking existing rows:
+<programlisting>
+ALTER TABLE cities
+    ATTACH PARTITION cities_west FOR VALUES IN ('los angeles', 'san fransisco') NO VALIDATE;
+</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..efd0c0d 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,14 @@ 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.  However, unlike regular tables, one cannot specify
+   <literal>PARTITION BY</literal> clause which means foreign tables can
+   only be created as leaf partitions.
+  </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 +331,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</>, that is partition
+   of the range partitioned table <structname>measurement</>:
+
+<programlisting>
+CREATE FOREIGN TABLE measurement_y2016m07
+    PARTITION OF measurement FOR VALUES START ('2016-07-01') END ('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 51e6936..c960705 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -44,6 +44,17 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
 [ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
 
+CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable>
+    PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable> [ (
+  { <replaceable class="PARAMETER">column_name</replaceable> WITH OPTIONS [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
+    | <replaceable>table_constraint</replaceable> }
+    [, ... ]
+) ] <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+[ PARTITION BY {RANGE | LIST} ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] )
+[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
+[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
+[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
+
 <phrase>where <replaceable class="PARAMETER">column_constraint</replaceable> is:</phrase>
 
 [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
@@ -72,6 +83,10 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 
 { INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES | STORAGE | COMMENTS | ALL }
 
+<phrase>and <replaceable class="PARAMETER">partition_bound_spec</replaceable> is:</phrase>
+
+FOR VALUES { <replaceable class="PARAMETER">list_spec</replaceable> | <replaceable class="PARAMETER">range_spec</replaceable> }
+
 <phrase><replaceable class="PARAMETER">index_parameters</replaceable> in <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and <literal>EXCLUDE</literal> constraints are:</phrase>
 
 [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) ]
@@ -80,8 +95,20 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 <phrase><replaceable class="PARAMETER">exclude_element</replaceable> in an <literal>EXCLUDE</literal> constraint is:</phrase>
 
 { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ]
-</synopsis>
 
+<phrase><replaceable class="PARAMETER">list_spec</replaceable> in <literal>FOR VALUES</literal> is:</phrase>
+
+IN ( <replaceable class="PARAMETER">expression</replaceable> [, ...] )
+
+<phrase><replaceable class="PARAMETER">range_spec</replaceable> in <literal>FOR VALUES</literal> is:</phrase>
+
+START <replaceable class="PARAMETER">lower-bound</replaceable> [ INCLUSIVE | EXCLUSIVE ] END <replaceable class="PARAMETER">upper-bound</replaceable> [ INCLUSIVE | EXCLUSIVE ]
+
+<phrase>where <replaceable class="PARAMETER">lower-bound</replaceable> and <replaceable class="PARAMETER">upper-bound</replaceable> are:</phrase>
+
+{ ( <replaceable class="PARAMETER">expression</replaceable> [, ...] ) | UNBOUNDED }
+
+</synopsis>
  </refsynopsisdiv>
 
  <refsect1 id="SQL-CREATETABLE-description">
@@ -232,6 +259,49 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
    </varlistentry>
 
    <varlistentry>
+    <term><literal>PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+    <listitem>
+     <para>
+      Creates the table as <firstterm>partition</firstterm> of the specified
+      parent table (name optionally schema-qualified).
+     </para>
+
+     <para>
+      A partition bound specification must be present and must correspond with
+      partition method and key of the parent table.  It is checked using the
+      specification that the new partition does not overlap with any existing
+      partitions of the parent.
+     </para>
+
+     <para>
+      A partition cannot have columns other than those inherited from the
+      parent.  That includes the <structfield>oid</> column, which can be
+      specified using the <literal>WITH (OIDS)</literal> clause.  On the other
+      hand, if parent has the <structfield>oid</> column, the partition
+      inherits the same, overriding the <literal>WITH (OIDS=FALSE)</literal>
+      clause, if any.  Defaults and constraints can optionally be specified
+      for each of the inherited columns, which override those in the parent.
+      One can also specify table constraints, in addition to those inherited
+      from the parent.  Note that all subsequent schema modifications to the
+      parent propagate to partition.
+     </para>
+
+     <para>
+      Any data row subsequently inserted into the parent table is mapped to
+      and stored in the partition, provided partition key of the row falls
+      within the partition bounds.
+     </para>
+
+     <para>
+      A partition is dropped or truncated when the parent table is dropped or
+      truncated.  Dropping it directly using <literal>DROP TABLE</literal>
+      will fail; it must first be <firstterm>detached</> from the parent.
+      However, truncating a partition directly works.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><replaceable class="PARAMETER">column_name</replaceable></term>
     <listitem>
      <para>
@@ -340,6 +410,11 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
      </para>
 
      <para>
+      When specifying for a table being created as partition, one needs to
+      use column names from the parent table as part of the key.
+     </para>
+
+     <para>
       Currently, there are following limitations on definition of partitioned
       tables: one cannot specify any UNIQUE, PRIMARY KEY, EXCLUDE and/or
       FOREIGN KEY constraints.
@@ -1422,7 +1497,38 @@ CREATE TABLE measurement (
 CREATE TABLE cities (
     name         text not null,
     population   int,
-) PARTITION BY LIST (name);
+) PARTITION BY LIST (lower(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 START ('2016-07-01') END ('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 fransisco');
+</programlisting></para>
+
+  <para>
+   Create partition of a list partitioned table that itself is further
+   partitioned and then create its partition:
+<programlisting>
+CREATE TABLE cities_west
+    PARTITION OF cities (
+    CONSTRAINT city_id_nonzero CHECK (city_id != 0)
+) FOR VALUES IN ('los angeles', 'san fransisco') PARTITION BY RANGE (population);
+
+CREATE TABLE cities_west_10000_to_100000
+    PARTITION OF cities_west FOR VALUES START (10000) END (100000);
 </programlisting></para>
  </refsect1>
 
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 777459d..2e6f754 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -15,7 +15,7 @@ OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.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 \
-       pg_type.o storage.o toasting.o pg_partitioned.o
+       pg_type.o storage.o toasting.o pg_partitioned.o pg_partition.o
 
 BKIFILES = postgres.bki postgres.description postgres.shdescription
 
@@ -41,7 +41,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
 	pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
 	pg_foreign_table.h pg_policy.h pg_replication_origin.h \
 	pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
-	pg_collation.h pg_range.h pg_transform.h pg_partitioned.h\
+	pg_collation.h pg_range.h pg_transform.h pg_partitioned.h pg_partition.h\
 	toasting.h indexing.h \
     )
 
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index f5dcc56..566d633 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"
@@ -48,6 +49,7 @@
 #include "catalog/pg_foreign_table.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/pg_partition_fn.h"
 #include "catalog/pg_partitioned_fn.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_tablespace.h"
@@ -1810,6 +1812,9 @@ heap_drop_with_catalog(Oid relid)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
 		RemovePartitionKeyByRelId(relid);
 
+	if (relid_is_partition(relid))
+		RemovePartitionEntryByRelId(relid);
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -2043,8 +2048,8 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
 	else
 		attNos = NULL;
 
-	/* Remove NO INHERIT flag if rel is a partitioned table */
-	if (relid_is_partitioned(relid))
+	/* Discard NO INHERIT, if relation is a partitioned table or a partition */
+	if (relid_is_partitioned(relid) || relid_is_partition(relid))
 		is_no_inherit = false;
 
 	/*
@@ -2472,7 +2477,9 @@ MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
 				con->conislocal = true;
 			else
 				con->coninhcount++;
-			if (is_no_inherit)
+
+			/* Discard the NO INHERIT flag if the relation is a partition */
+			if (is_no_inherit && !relid_is_partition(RelationGetRelid(rel)))
 			{
 				Assert(is_local);
 				con->connoinherit = true;
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index f00caf2..109d391 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -18,19 +18,28 @@
 #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_partition.h"
+#include "catalog/pg_partition_fn.h"
 #include "catalog/pg_partitioned.h"
+#include "catalog/pg_partitioned_fn.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 "storage/lmgr.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
@@ -39,6 +48,7 @@
 #include "utils/fmgroids.h"
 #include "utils/inval.h"
 #include "utils/lsyscache.h"
+#include "utils/rel.h"
 #include "utils/syscache.h"
 
 /* Type and collation information for partition key columns */
@@ -67,12 +77,111 @@ typedef struct PartitionKeyData
 	KeyTypeCollInfo *tcinfo;	/* type and collation info (all columns) */
 } PartitionKeyData;
 
+/* Internal representation of a list partition bound */
+typedef struct PartitionListInfo
+{
+	int		nvalues;	/* number of values in the following array */
+	Datum  *values;		/* values contained in the list */
+	bool   *nulls;
+} PartitionListInfo;
+
+/* Internal representation of a range partition bound */
+typedef struct RangeBound
+{
+	Datum	   *val;			/* composite bound value, if any */
+	bool		infinite;		/* bound is +/- infinity */
+	bool		inclusive;		/* bound is inclusive (vs exclusive) */
+	bool		lower;			/* this is the lower (vs upper) bound */
+} RangeBound;
+
+typedef struct PartitionRangeInfo
+{
+	RangeBound	*lower;
+	RangeBound	*upper;
+} PartitionRangeInfo;
+
+/*
+ * Information about a single partition
+ */
+typedef struct PartitionInfoData
+{
+	Oid						oid;		/* partition OID */
+	PartitionListInfo	   *list;		/* list partition info */
+	PartitionRangeInfo	   *range;		/* range partition info */
+} PartitionInfoData;
+
 /* Support RelationBuildPartitionKey() */
 static PartitionKey copy_partition_key(PartitionKey fromkey);
 static KeyTypeCollInfo *copy_key_type_coll_info(int nkeycols,
 								KeyTypeCollInfo *tcinfo);
 static void free_key_type_coll_info(KeyTypeCollInfo *tcinfo);
 
+/* Support RelationBuildPartitionDesc() */
+static List *get_partitions(Oid relid, int lockmode);
+static PartitionInfo *get_partition_info(Relation rel, int *nparts);
+static void free_partition_info(PartitionInfo *p, int num);
+static int32 partition_cmp(const void *a, const void *b, void *arg);
+
+/* Support check_new_partition_bound() */
+static bool list_overlaps_existing_partition(PartitionKey key,
+							PartitionListSpec *list_spec,
+							PartitionDesc pdesc,
+							Oid *with);
+static bool partition_range_empty(PartitionKey key,
+							PartitionRangeSpec *range_spec);
+static bool range_overlaps_existing_partition(PartitionKey key,
+							PartitionRangeSpec *range_spec,
+							PartitionDesc pdesc,
+							Oid *with);
+
+/* Support get_check_qual_from_partbound */
+typedef struct translate_var_attno_mutator_context
+{
+	AttrNumber	old_attno;
+	AttrNumber	new_attno;
+} translate_var_attno_mutator_context;
+
+static Node *translate_var_attno(Node *expr, AttrNumber attno,
+							AttrNumber new_attno);
+static Node *translate_var_attno_mutator(Node *node,
+							translate_var_attno_mutator_context *cxt);
+static List *get_check_qual_for_list(PartitionKey key, PartitionListSpec *list);
+static List *get_check_qual_for_range(PartitionKey key, PartitionRangeSpec *range);
+static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
+					   bool *need_relabel);
+
+/* Support RelationGetPartitionCheckQual() */
+static List *generate_partition_check_qual(Relation rel);
+
+/* List partition related support functions */
+static PartitionListInfo *make_list_from_spec(PartitionKey key,
+							PartitionListSpec *list_spec);
+static PartitionListInfo *copy_list_info(PartitionListInfo *src,
+							PartitionKey key);
+static bool equal_list_info(PartitionKey key, PartitionListInfo *l1,
+				PartitionListInfo *l2);
+static bool partition_list_values_equal(PartitionKey key,
+						   Datum val1, Datum val2);
+
+/* Range partition related support functions */
+static PartitionRangeInfo *make_range_from_spec(PartitionKey key,
+							PartitionRangeSpec *range_spec);
+static RangeBound *make_range_bound(PartitionKey key, List *val, bool inclusive,
+							bool lower);
+static PartitionRangeInfo *copy_range_info(PartitionRangeInfo *src,
+							PartitionKey key);
+static RangeBound *copy_range_bound(RangeBound *src, PartitionKey key);
+static bool equal_range_info(PartitionKey key, PartitionRangeInfo *r1,
+				 PartitionRangeInfo *r2);
+static int32 partition_range_cmp(PartitionKey key, PartitionRangeInfo *r1,
+									  PartitionRangeInfo *r2);
+static int32 partition_range_bound_cmp(PartitionKey key, RangeBound *b1,
+							RangeBound *b2);
+static int32 partition_range_tuple_cmp(PartitionKey key,
+						   Datum *val1, Datum *val2);
+static bool partition_range_overlaps(PartitionKey key,
+							PartitionRangeInfo *r1, PartitionRangeInfo *r2);
+
 /*
  * Partition key related functions
  */
@@ -390,3 +499,1378 @@ free_key_type_coll_info(KeyTypeCollInfo *tcinfo)
 	pfree(tcinfo->typcoll);
 	pfree(tcinfo);
 }
+
+/*
+ * Partition bound and partition descriptor related functions
+ */
+
+/*
+ * RelationBuildPartitionDesc
+ *		Form relation's partition descriptor from information in pg_partition
+ *		and store it in the relcache
+ *
+ * Not flushed from the cache by RelationClearRelation() unless changed because
+ * of addition or removal of partitions.
+ */
+void
+RelationBuildPartitionDesc(Relation relation)
+{
+	int				i,
+					nparts;
+	PartitionKey	key = RelationGetPartitionKey(relation);
+	PartitionDesc	result;
+	PartitionInfo  *parts;
+	MemoryContext	oldcxt;
+
+	/*
+	 * The following happens when we have created our pg_class entry but not
+	 * the pg_partitioned entry yet.
+	 */
+	if (key == NULL)
+		return;
+
+	/* Read partition bound information from the catalog */
+	parts = get_partition_info(relation, &nparts);
+
+	relation->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
+										RelationGetRelationName(relation),
+										ALLOCSET_DEFAULT_MINSIZE,
+										ALLOCSET_DEFAULT_INITSIZE,
+										ALLOCSET_DEFAULT_MAXSIZE);
+
+	oldcxt = MemoryContextSwitchTo(relation->rd_pdcxt);
+	result = (PartitionDescData *) palloc0(sizeof(PartitionDescData));
+
+	result->nparts = nparts;
+	if (parts)
+	{
+		result->parts = (PartitionInfoData **)
+								palloc0(nparts * sizeof(PartitionInfoData *));
+		/*
+		 * Sort range partitions in ascending order of their ranges before
+		 * continuing.
+		 */
+		qsort_arg(parts, nparts, sizeof(PartitionInfo), partition_cmp, key);
+
+		for (i = 0; i < nparts; i++)
+		{
+			PartitionInfoData *part;
+			part = (PartitionInfoData *) palloc0(sizeof(PartitionInfoData));
+			part->oid = parts[i]->oid;
+			switch (key->strategy)
+			{
+				case PARTITION_STRAT_LIST:
+					part->list = copy_list_info(parts[i]->list, key);
+					break;
+				case PARTITION_STRAT_RANGE:
+					part->range = copy_range_info(parts[i]->range, key);
+					break;
+			}
+
+			result->parts[i] = part;
+		}
+
+		/* Clean up */
+		free_partition_info(parts, nparts);
+	}
+
+	MemoryContextSwitchTo(oldcxt);
+	relation->rd_partdesc = result;
+}
+
+/*
+ * Are two partitions p1 and p2 equal?
+ */
+bool
+partition_equal(PartitionKey key, PartitionInfo p1, PartitionInfo p2)
+{
+
+	if (p1->oid != p2->oid)
+		return false;
+
+	switch (key->strategy)
+	{
+		case PARTITION_STRAT_LIST:
+			if (!equal_list_info(key, p1->list, p2->list))
+				return false;
+			break;
+		case PARTITION_STRAT_RANGE:
+			if (!equal_range_info(key, p1->range, p2->range))
+				return false;
+			break;
+	}
+
+	return true;
+}
+
+/*
+ * check_new_partition_bound
+ *
+ * Call partition method specific routines to check if the new partition's
+ * bound overlaps with any of partitions of parent.  Some partition types may
+ * have still other validations to perform, for example, a range partition
+ * partition with an empty range is not allowed.
+ */
+void
+check_new_partition_bound(Oid parentId, Node *bound)
+{
+	Relation		parent = heap_open(parentId, AccessShareLock);
+	PartitionKey	key = RelationGetPartitionKey(parent);
+	PartitionDesc	pdesc = RelationGetPartitionDesc(parent);
+	ParseState	   *pstate = make_parsestate(NULL);
+	Oid				with;
+
+	switch (key->strategy)
+	{
+		case PARTITION_STRAT_LIST:
+		{
+			PartitionListSpec *list;
+
+			Assert(IsA(bound, PartitionListSpec));
+			list = (PartitionListSpec *) bound;
+			if (list_overlaps_existing_partition(key, list, pdesc, &with))
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("new partition's list of values overlaps with"
+							" partition \"%s\" of \"%s\"",
+							get_rel_name(with),	get_rel_name(parentId)),
+					 errhint("Please specify a list of values that does not"
+							 " overlap with any existing partition's list."),
+					 parser_errposition(pstate, list->location)));
+			break;
+		}
+
+		case PARTITION_STRAT_RANGE:
+		{
+			PartitionRangeSpec *range;
+
+			Assert(IsA(bound, PartitionRangeSpec));
+			range = (PartitionRangeSpec *) bound;
+			if (partition_range_empty(key, range))
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("new partition's range contains no element"),
+					 errhint("Please specify a non-empty range."),
+					 parser_errposition(pstate, range->location)));
+
+			if (range_overlaps_existing_partition(key, range, pdesc, &with))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						 errmsg("new partition's range overlaps with partition"
+								" \"%s\" of \"%s\"",
+								get_rel_name(with),	get_rel_name(parentId)),
+						 errhint("Please specify a range that does not overlap"
+								 " with any existing partition's range."),
+						 parser_errposition(pstate, range->location)));
+			break;
+		}
+	}
+
+	heap_close(parent, AccessShareLock);
+}
+
+/*
+ * get_partition_parent
+ *
+ * Returns inheritance parent of relid by scanning pg_inherits
+ */
+Oid
+get_partition_parent(Oid relid)
+{
+	Form_pg_inherits form;
+	Relation	catalogRelation;
+	SysScanDesc scan;
+	ScanKeyData key[2];
+	HeapTuple	tuple;
+
+	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);
+
+	systable_endscan(scan);
+	heap_close(catalogRelation, AccessShareLock);
+
+	return form->inhparent;
+}
+
+/*
+ * get_check_qual_from_partbound
+ *		Given a parser node for partition bound, return the list of executable
+ *		expressions as partition predicate
+ */
+List *
+get_check_qual_from_partbound(Relation rel, Relation parent, Node *bound)
+{
+	PartitionKey key = RelationGetPartitionKey(parent);
+	List   *my_check;
+	int		i;
+	ListCell *partexprs_item;
+
+	/*
+	 * First convert from transformed parser node representation to the
+	 * internal representation
+	 */
+	if (IsA(bound, PartitionListSpec))
+	{
+		PartitionListSpec *list_spec = (PartitionListSpec *) bound;
+
+		Assert(key->strategy == PARTITION_STRAT_LIST);
+		my_check = get_check_qual_for_list(key, list_spec);
+	}
+	else if (IsA(bound, PartitionRangeSpec))
+	{
+		PartitionRangeSpec *range_spec = (PartitionRangeSpec *) bound;
+
+		Assert(key->strategy == PARTITION_STRAT_RANGE);
+		my_check = get_check_qual_for_range(key, range_spec);
+	}
+
+	/*
+	 * Translate vars in the generated expression with the correct attnos.
+	 * Note that the vars in my_expr 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 for partitions
+	 * that are "attached".
+	 */
+	partexprs_item = list_head(key->partexprs);
+	for (i = 0; i < key->partnatts; i++)
+	{
+		AttrNumber	attno = key->partattrs[i],
+					new_attno;
+		char	   *attname;
+
+		if (attno != 0)
+		{
+			/* Simple column reference */
+			attname = get_attname(RelationGetRelid(parent), attno);
+			new_attno = get_attnum(RelationGetRelid(rel), attname);
+
+			if (new_attno != attno)
+				my_check = (List *) translate_var_attno((Node *) my_check,
+													   attno,
+													   new_attno);
+		}
+		else
+		{
+			/* Arbitrary expression */
+			Node *expr = (Node *) lfirst(partexprs_item);
+			Bitmapset  *expr_attrs = NULL;
+			int			index;
+
+			/* Find all attributes referenced and translate each reference */
+			pull_varattnos(expr, 1, &expr_attrs);
+			partexprs_item = lnext(partexprs_item);
+
+			index = -1;
+			while ((index = bms_next_member(expr_attrs, index)) > 0)
+			{
+				AttrNumber attno = index + FirstLowInvalidHeapAttributeNumber;
+
+				attname = get_attname(RelationGetRelid(parent), attno);
+				new_attno = get_attnum(RelationGetRelid(rel), attname);
+
+				if (new_attno != attno)
+					my_check = (List *) translate_var_attno((Node *) my_check,
+														   attno,
+														   new_attno);
+			}
+		}
+	}
+
+	return my_check;
+}
+
+/*
+ * RelationGetPartitionCheckQual
+ *		Returns a list of OpExpr's (or a ScalarArrayOpExpr's) as partition
+ *		predicate
+ */
+List *
+RelationGetPartitionCheckQual(Relation rel)
+{
+	/* Quick exit */
+	if (!relid_is_partition(RelationGetRelid(rel)))
+		return NULL;
+
+	/* Quick copy */
+	if (rel->rd_partcheck)
+		return copyObject(rel->rd_partcheck);
+
+	/* Nope, so generate. */
+	return generate_partition_check_qual(rel);
+}
+
+/* Module-local functions */
+
+/*
+ * Wrapper around find_inheritance_children for use by partitioning
+ * related code
+ */
+static List *
+get_partitions(Oid relid, int lockmode)
+{
+	return find_inheritance_children(relid, lockmode);
+}
+
+/*
+ * get_partition_info
+ *		Return information of partitions of rel
+ *
+ * Returns number of partitions in *nparts.
+ */
+static PartitionInfo *
+get_partition_info(Relation rel, int *nparts)
+{
+	int				i,
+					n;
+	Relation		partrel;
+	List		   *partoids;
+	ListCell	   *cell;
+	PartitionInfo  *parts;
+	PartitionKey	key = RelationGetPartitionKey(rel);
+
+	/* Read info from pg_inherits */
+	partoids = get_partitions(RelationGetRelid(rel), NoLock);
+
+	partrel = heap_open(PartitionRelationId, AccessShareLock);
+
+	n = list_length(partoids);
+	parts = (PartitionInfoData **) palloc0(n * sizeof(PartitionInfoData *));
+
+	i = 0;
+	foreach(cell, partoids)
+	{
+		Oid 		partrelid = lfirst_oid(cell);
+		HeapTuple	tuple;
+		Datum		datum;
+		bool		isnull;
+		Node	   *bound;
+
+		tuple = SearchSysCache1(PARTRELID, partrelid);
+
+		/*
+		 * Happens when we have created the pg_inherits entry but not the
+		 * pg_partition entry yet.
+		 */
+		if (!HeapTupleIsValid(tuple))
+			continue;
+
+		parts[i] = (PartitionInfoData *) palloc0(sizeof(PartitionInfoData));
+		parts[i]->oid = partrelid;
+
+		datum = fastgetattr(tuple,
+							Anum_pg_partition_partbound,
+							partrel->rd_att,
+							&isnull);
+
+		bound = stringToNode(TextDatumGetCString(datum));
+
+		switch (key->strategy)
+		{
+			case PARTITION_STRAT_LIST:
+			{
+				PartitionListSpec  *list_spec;
+
+				Assert(IsA(bound, PartitionListSpec));
+				list_spec = (PartitionListSpec *) bound;
+				parts[i]->list = make_list_from_spec(key, list_spec);
+			}
+			break;
+
+			case PARTITION_STRAT_RANGE:
+			{
+				PartitionRangeSpec *range_spec;
+
+				Assert(IsA(bound, PartitionRangeSpec));
+				range_spec = (PartitionRangeSpec *) bound;
+				parts[i]->range = make_range_from_spec(key, range_spec);
+			}
+			break;
+		}
+
+		ReleaseSysCache(tuple);
+		i++;
+	}
+
+	heap_close(partrel, AccessShareLock);
+
+	*nparts = i;
+	if (*nparts == 0 && parts)
+	{
+		pfree(parts);
+		parts = NULL;
+	}
+
+	return parts;
+}
+
+/*
+ * Helper routine to deep-free a PartitionInfo array
+ */
+static void
+free_partition_info(PartitionInfoData **p, int num)
+{
+	int	i;
+
+	for (i = 0; i < num; i++)
+	{
+		if (p[i]->list)
+		{
+			pfree(p[i]->list->values);
+			pfree(p[i]->list);
+		}
+
+		if (p[i]->range)
+		{
+			if (p[i]->range->lower->val)
+				pfree(p[i]->range->lower->val);
+			pfree(p[i]->range->lower);
+
+			if (p[i]->range->upper->val)
+				pfree(p[i]->range->upper->val);
+			pfree(p[i]->range->upper);
+		}
+
+		pfree(p[i]);
+	}
+
+	pfree(p);
+}
+
+/*
+ * partition_cmp
+ *		Compare two (range) partitions
+ *
+ * Used as a callback to qsort_arg for sorting partitions.
+ */
+static int32
+partition_cmp(const void *a, const void *b, void *arg)
+{
+	PartitionKey key = (PartitionKey) arg;
+
+	if (key->strategy == PARTITION_STRAT_LIST)
+		return 0;
+
+	return partition_range_cmp((PartitionKey) arg,
+							   (*(const PartitionInfoData **) a)->range,
+							   (*(const PartitionInfoData **) b)->range);
+}
+
+/* Local support functions for check_new_partition_bound() */
+
+/*
+ * list_overlaps_existing_partition
+ *
+ * Does a new list partition's list of values overlap that of any of existing
+ * partitions?
+ */
+static bool
+list_overlaps_existing_partition(PartitionKey key,
+								 PartitionListSpec *list_spec,
+								 PartitionDesc pdesc,
+								 Oid *with)
+{
+	PartitionListInfo *new_list;
+	int			i;
+
+	if (pdesc->nparts == 0)
+		return false;
+
+	new_list = make_list_from_spec(key, list_spec);
+
+	for (i = 0; i < pdesc->nparts; i++)
+	{
+		int		j;
+
+		/*
+		 * No value in an existing partition's list of values should occur
+		 * in the list of values of the new partition.
+		 */
+		for (j = 0; j < pdesc->parts[i]->list->nvalues; j++)
+		{
+			Datum	existing_val = pdesc->parts[i]->list->values[j];
+			bool	existing_val_null = pdesc->parts[i]->list->nulls[j];
+			int		k;
+
+			for (k = 0; k < new_list->nvalues; k++)
+			{
+				Datum	new_val = new_list->values[k];
+				bool	new_val_null = new_list->nulls[k];
+
+				if ((existing_val_null && new_val_null) ||
+					(!existing_val_null && !new_val_null &&
+					 partition_list_values_equal(key,
+												 existing_val,
+												 new_val)))
+				{
+					*with = pdesc->parts[i]->oid;
+					return true;
+				}
+			}
+		}
+	}
+
+	return false;
+}
+
+
+/*
+ * Is a new partition's range empty?
+ */
+static bool
+partition_range_empty(PartitionKey key, PartitionRangeSpec *range_spec)
+{
+	PartitionRangeInfo *range;
+	RangeBound *lower,
+			   *upper;
+
+	range = make_range_from_spec(key, range_spec);
+	lower = range->lower;
+	upper = range->upper;
+
+	/*
+	 * Range is not empty if one (and only one) of the bounds is infinity.
+	 * Both cannot be infinite because of how the syntax is specified.
+	 */
+	Assert(!lower->infinite || !upper->infinite);
+	if (lower->infinite || upper->infinite)
+		return false;
+
+	/*
+	 * If upper < lower, then it's outright empty.  Also if lower = upper
+	 * and either is exclusive.
+	 */
+	if (partition_range_tuple_cmp(key, upper->val, lower->val) < 0 ||
+		(partition_range_tuple_cmp(key, lower->val, upper->val) == 0 &&
+		 (!lower->inclusive || !upper->inclusive)))
+		return true;
+
+	return false;
+}
+
+/*
+ * range_overlaps_existing_partition
+ *
+ * Does the new range partition's range overlap that of any of existing
+ * partitions?
+ */
+static bool
+range_overlaps_existing_partition(PartitionKey key,
+								  PartitionRangeSpec *range_spec,
+								  PartitionDesc pdesc,
+								  Oid *with)
+{
+	int		i;
+	PartitionRangeInfo *range;
+
+	if (pdesc->nparts == 0)
+		return false;
+
+	/* Create internal representation of range from range_spec */
+	range = make_range_from_spec(key, range_spec);
+
+	/* Check with existing partitions for overlap */
+	for (i = 0; i < pdesc->nparts; i++)
+	{
+		if (partition_range_overlaps(key, range, pdesc->parts[i]->range))
+		{
+			*with = pdesc->parts[i]->oid;
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/* Check two range partitions for overlap */
+static bool
+partition_range_overlaps(PartitionKey key,
+						 PartitionRangeInfo *r1, PartitionRangeInfo *r2)
+{
+	if (partition_range_bound_cmp(key, r1->lower, r2->lower) >= 0 &&
+		partition_range_bound_cmp(key, r1->lower, r2->upper) <= 0)
+		return true;
+
+	if (partition_range_bound_cmp(key, r2->lower, r1->lower) >= 0 &&
+		partition_range_bound_cmp(key, r2->lower, r1->upper) <= 0)
+		return true;
+
+	return false;
+}
+
+/* Local support functions for get_check_qual_from_partbound */
+
+/*
+ * translate_var_attno
+ *		Changes Vars with a given attno in the provided expression tree to
+ *		Vars with new_attno
+ */
+static Node *
+translate_var_attno(Node *expr, AttrNumber attno, AttrNumber new_attno)
+{
+	translate_var_attno_mutator_context cxt;
+
+	cxt.old_attno = attno;
+	cxt.new_attno = new_attno;
+
+	return expression_tree_mutator(expr, translate_var_attno_mutator, &cxt);
+}
+
+/*
+ * translate_var_attno_mutator
+ */
+static Node *
+translate_var_attno_mutator(Node *node,
+							 translate_var_attno_mutator_context *cxt)
+{
+	if (node == NULL)
+		return NULL;
+
+	if (IsA(node, Var) && ((Var *) node)->varattno == cxt->old_attno)
+	{
+		Var		*newvar = copyObject(node);
+
+		newvar->varattno = cxt->new_attno;
+
+		return (Node *) newvar;
+	}
+
+	return expression_tree_mutator(node, translate_var_attno_mutator,
+								  (void *) cxt);
+}
+
+/*
+ * get_check_qual_for_list
+ *		Get a ScalarArrayOpExpr to use as a list partition's predicate, given
+ *		the partition key (for left operand) and list of values obtained from
+ *		PartitionListSpec of the partition.
+ */
+static List *
+get_check_qual_for_list(PartitionKey key, PartitionListSpec *list_spec)
+{
+	ArrayExpr		   *values_arr = makeNode(ArrayExpr);
+	ScalarArrayOpExpr  *arrayop_expr;
+	Node   *key_col;
+	Oid		operoid;
+	bool	need_relabel;
+
+	/* Left operand is either a simple Var or arbitrary expression */
+	if (key->partattrs[0] != 0)
+		key_col = (Node *) makeVar(1, key->partattrs[0],
+									key->tcinfo->typid[0],
+									key->tcinfo->typmod[0],
+									key->tcinfo->typcoll[0],
+									0);
+	else
+		key_col = (Node *) copyObject(linitial(key->partexprs));
+
+	/* Right operand is an ArrayExpr */
+	values_arr->array_typeid = get_array_type(key->tcinfo->typid[0]);
+	values_arr->array_collid = key->tcinfo->typcoll[0];
+	values_arr->element_typeid = key->tcinfo->typid[0];
+	values_arr->elements = list_spec->values;
+	values_arr->multidims = false;
+	values_arr->location = -1;
+
+	/* Get the correct btree equality operator */
+	operoid = get_partition_operator(key, 0, BTEqualStrategyNumber,
+									 &need_relabel);
+	if (need_relabel)
+		key_col = (Node *) makeRelabelType((Expr *) key_col,
+										   key->partopcintype[0], -1,
+							   get_typcollation(key->partopcintype[0]),
+										   COERCE_EXPLICIT_CAST);
+
+	/* Build leftop = ANY (rightop) */
+	arrayop_expr = makeNode(ScalarArrayOpExpr);
+	arrayop_expr->opno = operoid;
+	arrayop_expr->opfuncid = get_opcode(operoid);
+	arrayop_expr->useOr = true;
+	arrayop_expr->inputcollid = key->tcinfo->typcoll[0];
+	arrayop_expr->args = list_make2(key_col, values_arr);
+	arrayop_expr->location = -1;
+
+	return list_make1(arrayop_expr);
+}
+
+/*
+ * get_check_qual_for_range
+ *		Get a list of OpExpr's to use as a range partition's predicate,
+ *		given the partition key (for left operands), lower and upper bounds
+ *		in PartitionRangeSpec (for right operands)
+ *
+ * Note: For each column, a 'col IS NOT NULL' constraint is emitted since
+ * we do not allow null values in range partition key.
+ */
+static List *
+get_check_qual_for_range(PartitionKey key, PartitionRangeSpec *spec)
+{
+	List	   *result = NIL;
+	ListCell   *cell1,
+			   *cell2,
+			   *partexprs_item;
+	int			i;
+	Oid			operoid;
+	uint16		strategy;
+	bool		need_relabel;
+
+	/*
+	 * Handle the case where the partition is bounded on only one side.
+	 *
+	 * In this case, consider only the first column of the key since
+	 * comparison with only the first column would have determined whether
+	 * whether a row went into such partition.  In other words, it always
+	 * follows that -INF < someval and someval < +INF.
+	 */
+	if (spec->lower == NIL || spec->upper == NIL)
+	{
+		List   *values;
+		Const  *key_val;
+		Node   *key_col;
+		bool	islower,
+				inclusive;
+		NullTest *keynulltest;
+
+		if (spec->lower != NIL)
+		{
+			values = spec->lower;
+			islower = true;
+			inclusive = spec->lowerinc;
+		}
+		else
+		{
+			values = spec->upper;
+			islower = false;
+			inclusive = spec->upperinc;
+		}
+
+		/* Left operand */
+		if (key->partattrs[0] != 0)
+			key_col = (Node *) makeVar(1, key->partattrs[0],
+									  key->tcinfo->typid[0],
+									  key->tcinfo->typmod[0],
+									  key->tcinfo->typcoll[0],
+									  0);
+		else
+			key_col = (Node *) copyObject(linitial(key->partexprs));
+
+		/* Right operand */
+		key_val = linitial(values);
+
+		if (islower)
+			strategy = inclusive ? BTGreaterEqualStrategyNumber : BTGreaterStrategyNumber;
+		else
+			strategy = inclusive ? BTLessEqualStrategyNumber : BTLessStrategyNumber;
+
+		/* Get the correct btree operator for given strategy */
+		operoid = get_partition_operator(key, 0, strategy, &need_relabel);
+
+		if (need_relabel)
+			key_col = (Node *) makeRelabelType((Expr *) key_col,
+											   key->partopcintype[0], -1,
+											   get_typcollation(key->partopcintype[0]),
+											   COERCE_EXPLICIT_CAST);
+
+		/* Gin up a col IS NOT NULL test */
+		keynulltest = makeNode(NullTest);
+		keynulltest->arg = (Expr *) key_col;
+		keynulltest->nulltesttype = IS_NOT_NULL;
+		keynulltest->argisrow = false;
+		keynulltest->location = -1;
+
+		/* Build leftop op rightop and return the list containing it */
+		return list_make2(keynulltest,
+						  make_opclause(operoid, BOOLOID,
+										false,
+										(Expr *) key_col,
+										(Expr *) key_val,
+										InvalidOid,
+										key->tcinfo->typcoll[0]));
+	}
+
+	/*
+	 * We must consider both the lower and upper bounds.  Iterate over
+	 * columns of the key.
+	 */
+	i = 0;
+	partexprs_item = list_head(key->partexprs);
+	forboth (cell1, spec->lower, cell2, spec->upper)
+	{
+		Node   *key_col;
+		Const  *lower_val = lfirst(cell1);
+		Const  *upper_val = lfirst(cell2);
+		EState		   *estate;
+		MemoryContext	oldcxt;
+		Expr		   *test_expr;
+		ExprState	   *test_exprstate;
+		Datum			test_result;
+		bool 			isNull;
+		bool			need_relabel = false;
+		NullTest *keynulltest;
+
+		/* Left operand */
+		if (key->partattrs[i] != 0)
+		{
+			key_col = (Node *) makeVar(1, key->partattrs[i],
+									  key->tcinfo->typid[i],
+									  key->tcinfo->typmod[i],
+									  key->tcinfo->typcoll[i],
+									  0);
+		}
+		else
+		{
+			key_col = (Node *) copyObject(lfirst(partexprs_item));
+			partexprs_item = lnext(partexprs_item);
+		}
+
+		/* Gin up a col IS NOT NULL test */
+		keynulltest = makeNode(NullTest);
+		keynulltest->arg = (Expr *) key_col;
+		keynulltest->nulltesttype = IS_NOT_NULL;
+		keynulltest->argisrow = false;
+		keynulltest->location = -1;
+		result = lappend(result, keynulltest);
+
+		/*
+		 * Is lower_val = upper_val?
+		 */
+
+		/* Get the correct btree equality operator for the test */
+		operoid = get_partition_operator(key, i, BTEqualStrategyNumber,
+										 &need_relabel);
+
+		estate = CreateExecutorState();
+		oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+		test_expr = make_opclause(operoid,
+								  BOOLOID,
+								  false,
+								  (Expr *) lower_val,
+								  (Expr *) upper_val,
+								  InvalidOid,
+								  key->tcinfo->typcoll[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))
+		{
+			/*
+			 * Yes, build leftop eq lower_val (we use lower_val arbitrarily)
+			 */
+			if (need_relabel)
+				key_col = (Node *) makeRelabelType((Expr *) key_col,
+												   key->partopcintype[i], -1,
+									 get_typcollation(key->partopcintype[i]),
+														COERCE_EXPLICIT_CAST);
+			result = lappend(result,
+								make_opclause(operoid,
+									  BOOLOID,
+									  false,
+									  (Expr *) key_col,
+									  (Expr *) lower_val,
+									  InvalidOid,
+									  key->tcinfo->typcoll[i]));
+
+			/* Go to the next column. */
+		}
+		else
+		{
+			/*
+			 * Build leftop >= lower_val and leftop <= upper_val, if leftop
+			 * is not the last column.
+			 *
+			 * For the last column, determine operator inclusivity to use from
+			 * the partition bound spec (lowerinc or upperinc).
+			 */
+
+			/* Build leftop ge/gt lower_val */
+			strategy = i < key->partnatts - 1 || spec->lowerinc
+									? BTGreaterEqualStrategyNumber
+									: BTGreaterStrategyNumber;
+			operoid = get_partition_operator(key, i, strategy, &need_relabel);
+
+			if (need_relabel)
+				key_col = (Node *) makeRelabelType((Expr *) key_col,
+												   key->partopcintype[i], -1,
+									 get_typcollation(key->partopcintype[i]),
+														COERCE_EXPLICIT_CAST);
+
+			result = lappend(result,
+						make_opclause(operoid,
+									  BOOLOID,
+									  false,
+									  (Expr *) key_col,
+									  (Expr *) lower_val,
+									  InvalidOid,
+									  key->tcinfo->typcoll[i]));
+
+			/* Build leftop le/lt upper_val */
+			strategy = i < key->partnatts - 1 || spec->upperinc
+									? BTLessEqualStrategyNumber
+									: BTLessStrategyNumber;
+			operoid = get_partition_operator(key, i, strategy, &need_relabel);
+
+			if (need_relabel)
+				key_col = (Node *) makeRelabelType((Expr *) key_col,
+												   key->partopcintype[i], -1,
+									 get_typcollation(key->partopcintype[i]),
+														COERCE_EXPLICIT_CAST);
+
+			result = lappend(result,
+						make_opclause(operoid,
+									  BOOLOID,
+									  false,
+									  (Expr *) key_col,
+									  (Expr *) upper_val,
+									  InvalidOid,
+									  key->tcinfo->typcoll[i]));
+
+			/* No need to constrain further columns. */
+			break;
+		}
+
+		i++;
+	}
+
+	return result;
+}
+
+/*
+ * get_partition_operator
+ *		Return oid of the operator of given strategy for a given partition
+ *		key column.
+ *
+ * Use either the column type as the operator datatype or opclass's declared
+ * input type.
+ */
+static Oid
+get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
+					   bool *need_relabel)
+{
+	Oid		operoid;
+
+	if (need_relabel)
+		*need_relabel = false;
+	operoid = get_opfamily_member(key->partopfamily[col],
+								  key->tcinfo->typid[col],
+								  key->tcinfo->typid[col],
+								  strategy);
+
+	if (!OidIsValid(operoid))
+	{
+		operoid = get_opfamily_member(key->partopfamily[col],
+									  key->partopcintype[col],
+									  key->partopcintype[col],
+									  strategy);
+		*need_relabel = true;
+	}
+
+	return operoid;
+}
+
+/*
+ * generate_partition_check_qual
+ *		Generate partition predicate from rel's pg_partition entry
+ *
+ * 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_check_qual(Relation rel)
+{
+	HeapTuple		tuple;
+	MemoryContext	oldcxt;
+	Datum		boundDatum;
+	bool		isnull;
+	Node	   *bound;
+	List	   *my_check = NIL,
+			   *result = NIL;
+	Oid			relid = RelationGetRelid(rel);
+	Relation	parent = heap_open(get_partition_parent(relid), NoLock);
+
+	/* Quick copy */
+	if (rel->rd_partcheck)
+	{
+		heap_close(parent, NoLock);
+		return copyObject(rel->rd_partcheck);
+	}
+
+	/* Actually generate from pg_partition.partbound */
+	tuple = SearchSysCache1(PARTRELID, relid);
+	Assert(HeapTupleIsValid(tuple));
+	boundDatum = SysCacheGetAttr(PARTRELID, tuple,
+								 Anum_pg_partition_partbound,
+								 &isnull);
+	Assert(!isnull);
+	bound = stringToNode(TextDatumGetCString(boundDatum));
+
+	/*
+	 * Generate executable expressions from ListPartitionSpec and
+	 * RangePartitionSpec nodes.
+	 */
+	my_check = get_check_qual_from_partbound(rel, parent, bound);
+
+	/*
+	 * If parent is also a partition, add its expressions to the list.
+	 */
+	if (relid_is_partition(RelationGetRelid(parent)))
+	{
+		List   *parent_check;
+
+		parent_check = generate_partition_check_qual(parent);
+		result = list_concat(parent_check, my_check);
+	}
+	else
+		result = my_check;
+
+	/* Save a copy in the relcache */
+	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	rel->rd_partcheck = copyObject(result);
+	MemoryContextSwitchTo(oldcxt);
+
+	ReleaseSysCache(tuple);
+	heap_close(parent, NoLock);
+
+	return result;
+}
+
+/* List partition related support functions */
+
+/*
+ * Make a PartitionListInfo from list of values using the information in key
+ */
+static PartitionListInfo *
+make_list_from_spec(PartitionKey key, PartitionListSpec *list_spec)
+{
+	PartitionListInfo *list;
+	ListCell   *cell;
+	int			i;
+
+	list = (PartitionListInfo *) palloc0(sizeof(PartitionListInfo));
+	list->nvalues = list_length(list_spec->values);
+	list->values = (Datum *) palloc0(list->nvalues * sizeof(Datum));
+	list->nulls = (bool *) palloc0(list->nvalues * sizeof(bool));
+
+	i = 0;
+	foreach (cell, list_spec->values)
+	{
+		Const	*val = lfirst(cell);
+
+		if (val->constisnull)
+			list->nulls[i] = true;
+		else
+			list->values[i] = datumCopy(val->constvalue,
+										key->tcinfo->typbyval[0],
+										key->tcinfo->typlen[0]);
+		i++;
+	}
+
+	return list;
+}
+
+/*
+ * Helper routine to copy a list partition bound struct
+ */
+static PartitionListInfo *
+copy_list_info(PartitionListInfo *src, PartitionKey key)
+{
+	int				i;
+	PartitionListInfo  *result;
+
+	result = (PartitionListInfo *) palloc0(sizeof(PartitionListInfo));
+	result->nvalues = src->nvalues;
+	result->values = (Datum *) palloc0(src->nvalues * sizeof(Datum));
+	result->nulls = (bool *) palloc0(src->nvalues * sizeof(bool));
+
+	for (i = 0; i < src->nvalues; i++)
+	{
+		if (!src->nulls[i])
+			result->values[i] = datumCopy(src->values[i],
+									  key->tcinfo->typbyval[0],
+									  key->tcinfo->typlen[0]);
+		else
+			result->nulls[i] = true;
+	}
+	return result;
+}
+
+/* Are two list partition bounds equal (contain the same set of values)? */
+static bool
+equal_list_info(PartitionKey key,
+				PartitionListInfo *l1, PartitionListInfo *l2)
+{
+	int		i,
+			j;
+
+	/* Check that every value (including null) in l1 also exists in l2 */
+	for (i = 0; i < l1->nvalues; i++)
+	{
+		Datum	l1_val = l1->values[i];
+		bool	l1_val_null = l1->nulls[i];
+		bool	found = false;
+
+		for (j = 0; j < l2->nvalues; j++)
+		{
+			Datum	l2_val = l2->values[j];
+			bool	l2_val_null = l2->nulls[j];
+
+			if ((l1_val_null && l2_val_null) ||
+				(!l1_val_null && !l2_val_null &&
+				 partition_list_values_equal(key, l1_val, l2_val)))
+			{
+				found = true;
+				break;
+			}
+		}
+		if (!found)
+			return false;
+	}
+
+	return true;
+}
+
+/* Are two values val1 and val2 equal per the list partition key */
+static bool
+partition_list_values_equal(PartitionKey key, Datum val1, Datum val2)
+{
+	int		cmpval;
+
+	cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+									  key->tcinfo->typcoll[0],
+									  val1, val2));
+	return cmpval == 0;
+}
+
+/* Range partition related support functions */
+
+/*
+ * Make a PartitionRangeInfo from given PartitionRangeSpec
+ */
+static PartitionRangeInfo *
+make_range_from_spec(PartitionKey key, PartitionRangeSpec *range_spec)
+{
+	PartitionRangeInfo *range;
+
+	range = (PartitionRangeInfo *) palloc0(sizeof(PartitionRangeInfo));
+	range->lower = make_range_bound(key,
+									range_spec->lower,
+									range_spec->lowerinc,
+									true);
+	range->upper = make_range_bound(key,
+									range_spec->upper,
+									range_spec->upperinc,
+									false);
+
+	return range;
+}
+
+static RangeBound *
+make_range_bound(PartitionKey key, List *val, bool inclusive, bool lower)
+{
+	RangeBound *bound;
+	ListCell *cell;
+
+	bound = (RangeBound *) palloc0(sizeof(RangeBound));
+	bound->infinite = (val == NIL);
+	bound->inclusive = inclusive;
+	bound->lower = lower;
+
+	if (val)
+	{
+		int		i;
+
+		bound->val = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+
+		i = 0;
+		foreach (cell, val)
+		{
+			Const *val = lfirst(cell);
+
+			if (val->constisnull)
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("cannot specify NULL in range bound")));
+			else
+				bound->val[i] = datumCopy(val->constvalue,
+										  key->tcinfo->typbyval[i],
+										  key->tcinfo->typlen[i]);
+			i++;
+		}
+	}
+
+	return bound;
+}
+
+/*
+ * Helper routines to copy a range partition info
+ */
+
+static PartitionRangeInfo *
+copy_range_info(PartitionRangeInfo *src, PartitionKey key)
+{
+	PartitionRangeInfo *result;
+
+	result = (PartitionRangeInfo *) palloc0(sizeof(PartitionRangeInfo));
+	result->lower = copy_range_bound(src->lower, key);
+	result->upper = copy_range_bound(src->upper, key);
+
+	return result;
+}
+
+static RangeBound *
+copy_range_bound(RangeBound *src, PartitionKey key)
+{
+	int		i;
+	int		partnatts = get_partition_key_natts(key);
+	RangeBound  *result;
+
+	result = (RangeBound *) palloc0(sizeof(RangeBound));
+	result->infinite = src->infinite;
+	result->inclusive = src->inclusive;
+	result->lower = src->lower;
+
+	if (src->val)
+	{
+		result->val = (Datum *) palloc0(partnatts * sizeof(Datum));
+		for (i = 0; i < partnatts; i++)
+			result->val[i] = datumCopy(src->val[i],
+									   key->tcinfo->typbyval[i],
+									   key->tcinfo->typlen[i]);
+	}
+
+	return result;
+}
+
+/*
+ * Are two range partition ranges equal?
+ *
+ * It's clear in this case that both are either lower bounds or upper bounds.
+ */
+static bool
+equal_range_info(PartitionKey key,
+				 PartitionRangeInfo *r1, PartitionRangeInfo *r2)
+{
+	return 	partition_range_bound_cmp(key, r1->lower, r2->lower) == 0 &&
+			partition_range_bound_cmp(key, r1->upper, r2->upper) == 0;
+}
+
+/*
+ * Compare two range partitions' ranges
+ */
+static int32
+partition_range_cmp(PartitionKey key, PartitionRangeInfo *r1,
+									  PartitionRangeInfo *r2)
+{
+	int			cmp;
+
+	cmp = partition_range_bound_cmp(key, r1->lower, r2->lower);
+	if (cmp == 0)
+		cmp = partition_range_bound_cmp(key, r1->upper, r2->upper);
+
+	return cmp;
+}
+
+/* Returns for two range partition bounds whether, b1 <=, =, >= b2 */
+static int32
+partition_range_bound_cmp(PartitionKey key, RangeBound *b1, RangeBound *b2)
+{
+	int32		result;
+
+	/*
+	 * First, handle cases involving infinity, which don't require invoking
+	 * the comparison proc.
+	 */
+	if (b1->infinite && b2->infinite)
+	{
+		/*
+		 * 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)
+		return b1->lower ? -1 : 1;
+	else if (b2->infinite)
+		return b2->lower ? 1 : -1;
+
+	/*
+	 * Both boundaries are finite, so compare the held values.
+	 */
+	result = partition_range_tuple_cmp(key, b1->val, b2->val);
+
+	/*
+	 * 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.
+	 */
+	if (result == 0)
+	{
+		if (!b1->inclusive && !b2->inclusive)
+		{
+			/* both are exclusive */
+			if (b1->lower == b2->lower)
+				return 0;
+			else
+				return b1->lower ? 1 : -1;
+		}
+		else if (!b1->inclusive)
+			return b1->lower ? 1 : -1;
+		else if (!b2->inclusive)
+			return b2->lower ? -1 : 1;
+		else
+		{
+			/*
+			 * Both are inclusive and the values held are equal, so they are
+			 * equal regardless of whether they are upper or lower boundaries,
+			 * or a mix.
+			 */
+			return 0;
+		}
+	}
+
+	return result;
+}
+
+/* Compare two composite keys */
+static int32
+partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2)
+{
+	int32	result;
+	int		i;
+
+	for (i = 0; i < key->partnatts; i++)
+	{
+		result = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+											 key->tcinfo->typcoll[i],
+											 val1[i], val2[i]));
+		if (result != 0)
+			break;
+	}
+
+	return result;
+}
diff --git a/src/backend/catalog/pg_partition.c b/src/backend/catalog/pg_partition.c
new file mode 100644
index 0000000..1f946dc
--- /dev/null
+++ b/src/backend/catalog/pg_partition.c
@@ -0,0 +1,106 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partition.c
+ *	  routines to support manipulation of the pg_partition relation
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/pg_partition.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "catalog/indexing.h"
+#include "catalog/pg_partition.h"
+#include "catalog/pg_partition_fn.h"
+#include "parser/parse_type.h"
+#include "storage/lmgr.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/syscache.h"
+#include "utils/tqual.h"
+
+/*
+ * StorePartitionBound
+ *
+ * Store partition boundary info in pg_partition after checking that it does
+ * not overlap with any existing partition.  Error out if it does.
+ */
+void
+StorePartitionBound(Oid relid, Oid parentId, Node *bound)
+{
+	Relation		pg_partition;
+	Relation		parent;
+	HeapTuple		tuple;
+	char		   *boundString;
+	Datum			values[Natts_pg_partition];
+	bool			nulls[Natts_pg_partition];
+
+	parent = heap_open(parentId, AccessShareLock);
+
+	pg_partition = heap_open(PartitionRelationId, RowExclusiveLock);
+
+	boundString = nodeToString(bound);
+
+	/* Build the tuple - bound can never be null */
+	MemSet(nulls, false, sizeof(nulls));
+
+	values[Anum_pg_partition_partrelid - 1] = ObjectIdGetDatum(relid);
+	values[Anum_pg_partition_partbound - 1] = CStringGetTextDatum(boundString);
+
+	tuple = heap_form_tuple(RelationGetDescr(pg_partition), values, nulls);
+
+	simple_heap_insert(pg_partition, tuple);
+
+	/* Update the indexes on pg_partition */
+	CatalogUpdateIndexes(pg_partition, tuple);
+
+	/* Invalidate the parent's relcache */
+	CacheInvalidateRelcache(parent);
+
+	heap_close(parent, AccessShareLock);
+	heap_close(pg_partition, RowExclusiveLock);
+}
+
+/*
+ *  RemovePartitionByRelId
+ *		Remove a pg_partition entry for a partition.
+ */
+void
+RemovePartitionEntryByRelId(Oid relid)
+{
+	Relation		rel;
+	HeapTuple		tuple;
+
+	rel = heap_open(PartitionRelationId, RowExclusiveLock);
+	tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for partition entry of relation %u",
+					 relid);
+
+	simple_heap_delete(rel, &tuple->t_self);
+
+	/* Update the indexes on pg_partitioned_rel */
+	CatalogUpdateIndexes(rel, tuple);
+
+	ReleaseSysCache(tuple);
+	heap_close(rel, RowExclusiveLock);
+}
+
+/*
+ * does relid have pg_partition entry?
+ */
+bool
+relid_is_partition(Oid relid)
+{
+	return SearchSysCacheExists1(PARTRELID, ObjectIdGetDatum(relid));
+}
diff --git a/src/backend/commands/foreigncmds.c b/src/backend/commands/foreigncmds.c
index eb531af..bfe4e0b 100644
--- a/src/backend/commands/foreigncmds.c
+++ b/src/backend/commands/foreigncmds.c
@@ -1464,6 +1464,23 @@ CreateForeignTable(CreateForeignTableStmt *stmt, Oid relid)
 	fdw = GetForeignDataWrapper(server->fdwid);
 
 	/*
+	 * If this foreign table is a partition, check that the FDW supports
+	 * insert.
+	 */
+	if (stmt->base.partbound != NULL)
+	{
+		FdwRoutine *fdw_routine;
+
+		fdw_routine = GetFdwRoutine(fdw->fdwhandler);
+		if (fdw_routine->ExecForeignInsert == NULL)
+			ereport(ERROR,
+					(errcode(ERRCODE_FDW_NO_SCHEMAS),
+					 errmsg("cannot create foreign table as partition"),
+					 errdetail("foreign-data wrapper \"%s\" does not support insert",
+							fdw->fdwname)));
+	}
+
+	/*
 	 * Insert tuple into pg_foreign_table.
 	 */
 	memset(values, 0, sizeof(values));
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 62671a8..eb6bfbb 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -17,6 +17,7 @@
 #include "access/heapam.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_inherits_fn.h"
+#include "catalog/pg_partitioned_fn.h"
 #include "commands/lockcmds.h"
 #include "miscadmin.h"
 #include "parser/parse_clause.h"
@@ -63,7 +64,8 @@ LockTableCommand(LockStmt *lockstmt)
 										  RangeVarCallbackForLockTable,
 										  (void *) &lockstmt->mode);
 
-		if (recurse)
+		/* Force inheritance recursion, if partitioned table. */
+		if (recurse || relid_is_partitioned(reloid))
 			LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
 	}
 }
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 5dcd4cf..21d9f96 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -159,6 +159,7 @@ DefineSequence(CreateSeqStmt *seq)
 		coldef->is_local = true;
 		coldef->is_not_null = true;
 		coldef->is_from_type = false;
+		coldef->is_for_partition = false;
 		coldef->storage = 0;
 		coldef->raw_default = NULL;
 		coldef->cooked_default = NULL;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index e070a1a..361b5ea 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -40,6 +40,7 @@
 #include "catalog/pg_inherits_fn.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_partition_fn.h"
 #include "catalog/pg_partitioned_fn.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
@@ -122,6 +123,19 @@ static List *on_commits = NIL;
 
 
 /*
+ * For AT ATTACH PARTITION <table> FOR VALUES <partition_bound_spec>,
+ *
+ * Struct describing partition check expression corresponding to
+ * <partition_bound_spec> to validate for rows in <table> during
+ * Phase 3 scan.
+ */
+typedef struct PartitionCheck
+{
+	Expr	*expr;
+	List	*state;
+} PartitionCheck;
+
+/*
  * State information for ALTER TABLE
  *
  * The pending-work queue for an ALTER TABLE is a List of AlteredTableInfo
@@ -164,6 +178,8 @@ 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 */
+	PartitionCheck *partitionCheck; /* partition check expression 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 */
@@ -280,7 +296,8 @@ typedef struct
 
 static void truncate_check_rel(Relation rel);
 static List *MergeAttributes(List *schema, List *supers, char relpersistence,
-				List **supOids, List **supconstr, int *supOidCount);
+				List **supOids, List **supconstr, int *supOidCount,
+				bool is_partition);
 static bool MergeCheckConstraint(List *constraints, char *name, Node *expr);
 static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel);
 static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel);
@@ -448,6 +465,11 @@ static void ComputePartitionAttrs(Oid relid, List *partParams,
 								AttrNumber *partattrs,
 								List **partexprs,
 								Oid *partoplass);
+static void CreateInheritance(Relation child_rel, Relation parent_rel);
+static void RemoveInheritance(Relation child_rel, Relation parent_rel);
+static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
+						PartitionCmd *cmd);
+static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
 
 
 /* ----------------------------------------------------------------
@@ -590,10 +612,16 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Look up inheritance ancestors and generate relation schema, including
 	 * inherited attributes.
+	 *
+	 * The last parameter implicitly specifies that the table is being created
+	 * as partition and schema consists of columns definitions corresponding
+	 * to non-dropped columns of the parent constructed such that each
+	 * attribute of the table is created as inherited and non-local.
 	 */
 	schema = MergeAttributes(schema, stmt->inhRelations,
 							 stmt->relation->relpersistence,
-							 &inheritOids, &old_constraints, &parentOidCount);
+							 &inheritOids, &old_constraints, &parentOidCount,
+							 stmt->partbound != NULL);
 
 	/*
 	 * Create a tuple descriptor from the relation schema.  Note that this
@@ -713,6 +741,20 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 */
 	rel = relation_open(relationId, AccessExclusiveLock);
 
+	/* Process and store partition bound. */
+	if (stmt->partbound)
+	{
+		Oid		parentId = linitial_oid(inheritOids);
+
+		/*
+		 * 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(parentId, stmt->partbound);
+		StorePartitionBound(relationId, parentId, stmt->partbound);
+	}
+
 	/* Process and store partition key, if any */
 	if (stmt->partby)
 	{
@@ -728,14 +770,16 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		partnatts = list_length(stmt->partby->partParams);
 		StorePartitionKey(rel, stmt->partby->strategy, partnatts,
 						  partattrs, partexprs, partopclass);
+	}
 
-		/*
-		 * Bump the command counter to make the newly-created pg_partitioned
-		 * tuple visible so that the constraint code invoked below can know
-		 * that the relation is a partitioned table.
-		 */
+	/*
+	 * Bump the command counter to make the newly-created pg_partitioned
+	 * and/or pg_partition tuple visible so that the constraint code invoked
+	 * below can know that the relation is a partitioned table and/or a
+	 * partition.
+	 */
+	if (stmt->partbound || stmt->partby)
 		CommandCounterIncrement();
-	}
 
 	/*
 	 * Now add any newly specified column default values and CHECK constraints
@@ -990,6 +1034,13 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 	if (!OidIsValid(relOid))
 		return;
 
+	if (relid_is_partition(relOid))
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is a partition of \"%s\"", rel->relname,
+						get_rel_name(get_partition_parent(relOid))),
+				 errhint("Use ALTER TABLE DETACH PARTITION to be able to drop it.")));
+
 	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
 	if (!HeapTupleIsValid(tuple))
 		return;					/* concurrently dropped, so nothing to do */
@@ -1081,7 +1132,8 @@ ExecuteTruncate(TruncateStmt *stmt)
 		rels = lappend(rels, rel);
 		relids = lappend_oid(relids, myrelid);
 
-		if (recurse)
+		/* Force inheritance recursion, if partitioned table. */
+		if (recurse || relid_is_partitioned(myrelid))
 		{
 			ListCell   *child;
 			List	   *children;
@@ -1459,7 +1511,8 @@ storage_name(char c)
  */
 static List *
 MergeAttributes(List *schema, List *supers, char relpersistence,
-				List **supOids, List **supconstr, int *supOidCount)
+				List **supOids, List **supconstr, int *supOidCount,
+				bool is_partition)
 {
 	ListCell   *entry;
 	List	   *inhSchema = NIL;
@@ -1504,8 +1557,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 
 			/*
 			 * Typed table column option that does not belong to a column from
-			 * the type.  This works because the columns from the type come
-			 * first in the list.
+			 * the type or the partition parent.  This works because the columns
+			 * from the type or the partition parent come first in the list.
 			 */
 			ereport(ERROR,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
@@ -1532,6 +1585,18 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 					coldef->is_from_type = false;
 					list_delete_cell(schema, rest, prev);
 				}
+				else if (coldef->is_for_partition)
+				{
+					/*
+					 * merge the column options into the column from the parent
+					 */
+					coldef->is_not_null = restdef->is_not_null;
+					coldef->raw_default = restdef->raw_default;
+					coldef->cooked_default = restdef->cooked_default;
+					coldef->constraints = restdef->constraints;
+					coldef->is_for_partition = false;	/* job is done */
+					list_delete_cell(schema, rest, prev);
+				}
 				else
 					ereport(ERROR,
 							(errcode(ERRCODE_DUPLICATE_COLUMN),
@@ -1570,13 +1635,23 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		 */
 		relation = heap_openrv(parent, ShareUpdateExclusiveLock);
 
-		/* Cannot inherit from partitioned tables */
-		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+		/*
+		 * Cannot inherit from a partitioned table unless being created as
+		 * partition.
+		 */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL && !is_partition)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot inherit from partitioned table \"%s\"",
 							parent->relname)));
 
+		/* The same if the parent is a partition */
+		if (relid_is_partition(RelationGetRelid(relation)) && !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_PARTITIONED_REL)
@@ -1717,6 +1792,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 				def->is_local = false;
 				def->is_not_null = attribute->attnotnull;
 				def->is_from_type = false;
+				def->is_for_partition = false;
 				def->storage = attribute->attstorage;
 				def->raw_default = NULL;
 				def->cooked_default = NULL;
@@ -1845,6 +1921,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	 * If we had no inherited attributes, the result schema is just the
 	 * explicitly declared columns.  Otherwise, we need to merge the declared
 	 * columns into the inherited schema list.
+	 *
+	 * Note: In case of a partition, there are only inherited attributes that
+	 * we copied from the parent in transformPartitionOf().
 	 */
 	if (inhSchema != NIL)
 	{
@@ -1874,16 +1953,20 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 
 				/*
 				 * Yes, try to merge the two column definitions. They must
-				 * have the same type, typmod, and collation.
+				 * have the same type, typmod, and collation.  Don't output
+				 * the notices, if partition.
 				 */
-				if (exist_attno == schema_attno)
-					ereport(NOTICE,
-					(errmsg("merging column \"%s\" with inherited definition",
-							attributeName)));
-				else
-					ereport(NOTICE,
-							(errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
-							 errdetail("User-specified column moved to the position of the inherited column.")));
+				if (!is_partition)
+				{
+					if (exist_attno == schema_attno)
+						ereport(NOTICE,
+						(errmsg("merging column \"%s\" with inherited definition",
+								attributeName)));
+					else
+						ereport(NOTICE,
+								(errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
+								 errdetail("User-specified column moved to the position of the inherited column.")));
+				}
 				def = (ColumnDef *) list_nth(inhSchema, exist_attno - 1);
 				typenameTypeIdAndMod(NULL, def->typeName, &defTypeId, &deftypmod);
 				typenameTypeIdAndMod(NULL, newdef->typeName, &newTypeId, &newtypmod);
@@ -1920,8 +2003,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 									   storage_name(def->storage),
 									   storage_name(newdef->storage))));
 
-				/* Mark the column as locally defined */
-				def->is_local = true;
+				/* Mark the column as locally defined (unless partition) */
+				if (!is_partition)
+					def->is_local = true;
 				/* Merge of NOT NULL constraints = OR 'em together */
 				def->is_not_null |= newdef->is_not_null;
 				/* If new def has a default, override previous default */
@@ -2211,6 +2295,11 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot rename column of typed table")));
 
+	if (relid_is_partition(myrelid) && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot rename column of a partition")));
+
 	/*
 	 * Renaming the columns of sequences or toast tables doesn't actually
 	 * break anything from the system's point of view, since internal
@@ -2437,11 +2526,13 @@ renameatt(RenameStmt *stmt)
 		return InvalidObjectAddress;
 	}
 
+	/* Force inheritance recursion, if partitioned table. */
 	attnum =
 		renameatt_internal(relid,
 						   stmt->subname,		/* old att name */
 						   stmt->newname,		/* new att name */
-						   interpretInhOption(stmt->relation->inhOpt),	/* recursive? */
+						   relid_is_partitioned(relid) ||	/* recursive? */
+						   interpretInhOption(stmt->relation->inhOpt),
 						   false,		/* recursing? */
 						   0,	/* expected inhcount */
 						   stmt->behavior);
@@ -2589,11 +2680,13 @@ RenameConstraint(RenameStmt *stmt)
 		}
 	}
 
+	/* Force inheritance recursion, if partitioned table. */
 	return
 		rename_constraint_internal(relid, typid,
 								   stmt->subname,
 								   stmt->newname,
-		 stmt->relation ? interpretInhOption(stmt->relation->inhOpt) : false,	/* recursive? */
+		 stmt->relation ? relid_is_partitioned(relid) ||
+						  interpretInhOption(stmt->relation->inhOpt) : false, /* recursive? */
 								   false,		/* recursing? */
 								   0 /* expected inhcount */ );
 
@@ -2835,8 +2928,11 @@ AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt)
 
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
+	/* Force inheritance recursion, if partitioned table */
 	ATController(stmt,
-				 rel, stmt->cmds, interpretInhOption(stmt->relation->inhOpt),
+				 rel, stmt->cmds,
+				 relid_is_partitioned(RelationGetRelid(rel)) ||
+					interpretInhOption(stmt->relation->inhOpt),
 				 lockmode);
 }
 
@@ -3115,6 +3211,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);
@@ -3432,6 +3533,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);
@@ -3751,6 +3858,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);
@@ -3936,7 +4049,7 @@ 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->partitionCheck)
 				ATRewriteTable(tab, InvalidOid, lockmode);
 
 			/*
@@ -4080,6 +4193,21 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		}
 	}
 
+	/*
+	 * Build expression execution states for partition check expression
+	 * as well.
+	 */
+	if (tab->partitionCheck)
+	{
+		List *expr;
+
+		needscan = true;
+		Assert(tab->partitionCheck->expr);
+		expr = list_make1(tab->partitionCheck->expr);
+		tab->partitionCheck->state = (List *)
+									ExecPrepareExpr((Expr *) expr, estate);
+	}
+
 	foreach(l, tab->newvals)
 	{
 		NewColumnValue *ex = lfirst(l);
@@ -4269,6 +4397,12 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				}
 			}
 
+			if (tab->partitionCheck &&
+				!ExecQual(tab->partitionCheck->state, econtext, true))
+				ereport(ERROR,
+						(errcode(ERRCODE_CHECK_VIOLATION),
+						 errmsg("table contains a row violating the partition bound specification")));
+
 			/* Write the tuple out to the new relation */
 			if (newrel)
 				heap_insert(newrel, tuple, mycid, hi_options, bistate);
@@ -4466,7 +4600,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_REL))
 	{
 		Oid			relid = RelationGetRelid(rel);
 		ListCell   *child;
@@ -4788,6 +4923,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	if (recursing)
 		ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
 
+	if (relid_is_partition(RelationGetRelid(rel)) && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot add column to a partition")));
+
 	attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
 
 	/*
@@ -5243,6 +5383,7 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 	List	   *indexoidlist;
 	ListCell   *indexoidscan;
 	ObjectAddress address;
+	Oid			relid;
 
 	/*
 	 * lookup the attribute
@@ -5310,6 +5451,28 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 	list_free(indexoidlist);
 
 	/*
+	 * If rel is partition, throw error if the attribute is not null in
+	 * parent
+	 */
+	relid = RelationGetRelid(rel);
+	if (relid_is_partition(relid))
+	{
+		Oid			parentId = get_partition_parent(relid);
+		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 not null in parent",
+							colName),
+					 errhint("Please drop not null in the parent instead")));
+		heap_close(parent, AccessShareLock);
+	}
+
+	/*
 	 * Okay, actually perform the catalog change ... if needed
 	 */
 	if (((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
@@ -5843,6 +6006,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 	if (recursing)
 		ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
 
+	if (relid_is_partition(RelationGetRelid(rel)) && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot drop column from a partition")));
+
 	/*
 	 * get the number of the attribute
 	 */
@@ -5888,11 +6056,13 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 		if (!is_expr)
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
-					 errmsg("cannot drop column named in partition key")));
+					 errmsg("cannot drop column named in%s partition key",
+							recursing ? " downstream" : "")));
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
-					 errmsg("cannot drop column referenced in partition key expression")));
+					 errmsg("cannot drop column referenced in%s partition key expression",
+							recursing ? " downstream" : "")));
 	}
 
 	ReleaseSysCache(tuple);
@@ -6326,8 +6496,9 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 
 	/*
 	 * If adding a NO INHERIT constraint, no need to find our children.
+	 * Remember that we discard is_no_inherit for partitioned tables.
 	 */
-	if (constr->is_no_inherit)
+	if (constr->is_no_inherit && !relid_is_partitioned(RelationGetRelid(rel)))
 		return address;
 
 	/*
@@ -7888,7 +8059,9 @@ ATExecDropConstraint(Relation rel, const char *constrName,
 	/*
 	 * 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.
+	 * use find_all_inheritors to do it in one pass.  Note that if the parent
+	 * is a partitioned table, we propagate to children (partitions) despite
+	 * is_no_inherit_constraint.
 	 */
 	if (!is_no_inherit_constraint)
 		children = find_inheritance_children(RelationGetRelid(rel), lockmode);
@@ -8020,6 +8193,11 @@ ATPrepAlterColumnType(List **wqueue,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot alter column type of typed table")));
 
+	if (relid_is_partition(RelationGetRelid(rel)) && !recursing)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot alter column type of a partition")));
+
 	/* lookup the attribute so we can check inheritance status */
 	tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
 	if (!HeapTupleIsValid(tuple))
@@ -8050,11 +8228,13 @@ ATPrepAlterColumnType(List **wqueue,
 		if (!is_expr)
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
-					 errmsg("cannot alter column named in partition key")));
+					 errmsg("cannot alter column named in%s partition key",
+							recursing ? " downstream" : "")));
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
-					 errmsg("cannot alter column referenced in partition key expression")));
+					 errmsg("cannot alter column referenced in%s partition key expression",
+							recursing ? " downstream" : "")));
 	}
 
 	/* Look up the target type */
@@ -10187,6 +10367,11 @@ ATPrepAddInherit(Relation child_rel)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot change inheritance of typed table")));
 
+	if (relid_is_partition(RelationGetRelid(child_rel)))
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot change inheritance of a partition")));
+
 	if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -10199,12 +10384,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;
 
@@ -10248,37 +10428,10 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				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);
+	if (relid_is_partition(RelationGetRelid(parent_rel)))
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot inherit from a partition")));
 
 	/*
 	 * Prevent circularity by seeing if proposed parent inherits from child.
@@ -10313,6 +10466,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);
 
@@ -10327,16 +10543,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;
 }
 
 /*
@@ -10387,7 +10595,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
@@ -10405,11 +10613,13 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 	int			parent_natts;
 	TupleDesc	tupleDesc;
 	HeapTuple	tuple;
+	bool		is_attach_partition;
 
 	attrrel = heap_open(AttributeRelationId, RowExclusiveLock);
 
 	tupleDesc = RelationGetDescr(parent_rel);
 	parent_natts = tupleDesc->natts;
+	is_attach_partition = relid_is_partitioned(RelationGetRelid(parent_rel));
 
 	for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++)
 	{
@@ -10432,14 +10642,16 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 				attribute->atttypmod != childatt->atttypmod)
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("child table \"%s\" has different type for column \"%s\"",
+						 errmsg("%s table \"%s\" has different type for column \"%s\"",
+								is_attach_partition ? "source" : "child",
 								RelationGetRelationName(child_rel),
 								attributeName)));
 
 			if (attribute->attcollation != childatt->attcollation)
 				ereport(ERROR,
 						(errcode(ERRCODE_COLLATION_MISMATCH),
-						 errmsg("child table \"%s\" has different collation for column \"%s\"",
+						 errmsg("%s table \"%s\" has different collation for column \"%s\"",
+								is_attach_partition ? "source" : "child",
 								RelationGetRelationName(child_rel),
 								attributeName)));
 
@@ -10450,8 +10662,9 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 			if (attribute->attnotnull && !childatt->attnotnull)
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
-				errmsg("column \"%s\" in child table must be marked NOT NULL",
-					   attributeName)));
+				errmsg("column \"%s\" in %s table must be marked NOT NULL",
+					   attributeName,
+					   is_attach_partition ? "source" : "child")));
 
 			/*
 			 * OK, bump the child column's inheritance count.  (If we fail
@@ -10466,7 +10679,8 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 		{
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("child table is missing column \"%s\"",
+					 errmsg("%s table is missing column \"%s\"",
+							is_attach_partition ? "source" : "child",
 							attributeName)));
 		}
 	}
@@ -10480,7 +10694,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.
@@ -10499,9 +10713,11 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 	SysScanDesc parent_scan;
 	ScanKeyData parent_key;
 	HeapTuple	parent_tuple;
+	bool		is_attach_partition;
 
 	catalog_relation = heap_open(ConstraintRelationId, RowExclusiveLock);
 	tuple_desc = RelationGetDescr(catalog_relation);
+	is_attach_partition = relid_is_partitioned(RelationGetRelid(parent_rel));
 
 	/* Outer loop scans through the parent's constraint definitions */
 	ScanKeyInit(&parent_key,
@@ -10549,16 +10765,21 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 			if (!constraints_equivalent(parent_tuple, child_tuple, tuple_desc))
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("child table \"%s\" has different definition for check constraint \"%s\"",
+						 errmsg("%s table \"%s\" has different definition for check constraint \"%s\"",
+								is_attach_partition ? "source" : "child",
 								RelationGetRelationName(child_rel),
 								NameStr(parent_con->conname))));
 
-			/* If the constraint is "no inherit" then cannot merge */
-			if (child_con->connoinherit)
+			/*
+			 * If the constraint is "no inherit" then cannot merge, unless
+			 * this child table is a partition being attached.
+			 */
+			if (child_con->connoinherit && !is_attach_partition)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-						 errmsg("constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
+						 errmsg("constraint \"%s\" conflicts with non-inherited constraint on %s table \"%s\"",
 								NameStr(child_con->conname),
+								is_attach_partition ? "source" : "child",
 								RelationGetRelationName(child_rel))));
 
 			/*
@@ -10568,6 +10789,11 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 			child_copy = heap_copytuple(child_tuple);
 			child_con = (Form_pg_constraint) GETSTRUCT(child_copy);
 			child_con->coninhcount++;
+
+			/* If we are becoming a partition, reset the NO INHERIT flag. */
+			if (is_attach_partition)
+				child_con->connoinherit = false;
+
 			simple_heap_update(catalog_relation, &child_copy->t_self, child_copy);
 			CatalogUpdateIndexes(catalog_relation, child_copy);
 			heap_freetuple(child_copy);
@@ -10581,7 +10807,8 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 		if (!found)
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("child table is missing constraint \"%s\"",
+					 errmsg("%s table is missing constraint \"%s\"",
+							is_attach_partition ? "source" : "child",
 							NameStr(parent_con->conname))));
 	}
 
@@ -10592,6 +10819,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 (relid_is_partition(RelationGetRelid(rel)))
+		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.
@@ -10605,13 +10872,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];
@@ -10620,19 +10885,9 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 				constraintTuple;
 	List	   *connames;
 	bool		found = false;
-	ObjectAddress address;
+	bool		is_detach_partition;
 
-	/*
-	 * AccessShareLock on the parent is probably enough, seeing that DROP
-	 * TABLE doesn't lock parent tables at all.  We need some lock since we'll
-	 * be inspecting the parent's schema.
-	 */
-	parent_rel = heap_openrv(parent, AccessShareLock);
-
-	/*
-	 * We don't bother to check ownership of the parent table --- ownership of
-	 * the child is presumed enough rights.
-	 */
+	is_detach_partition = relid_is_partitioned(RelationGetRelid(parent_rel));
 
 	/*
 	 * Find and destroy the pg_inherits entry linking the two, or error out if
@@ -10642,7 +10897,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);
 
@@ -10663,11 +10918,20 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 	heap_close(catalogRelation, RowExclusiveLock);
 
 	if (!found)
-		ereport(ERROR,
-				(errcode(ERRCODE_UNDEFINED_TABLE),
-				 errmsg("relation \"%s\" is not a parent of relation \"%s\"",
-						RelationGetRelationName(parent_rel),
-						RelationGetRelationName(rel))));
+	{
+		if (is_detach_partition)
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_TABLE),
+					 errmsg("relation \"%s\" is not a 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
@@ -10676,7 +10940,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)))
@@ -10738,7 +11002,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);
 
@@ -10769,7 +11033,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)
@@ -10781,30 +11045,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;
 }
 
 /*
@@ -12434,3 +12688,190 @@ ComputePartitionAttrs(Oid relid, List *partParams, AttrNumber *partattrs,
 		partopclass[attn++] = opclassOid;
 	}
 }
+
+
+/*
+ * ALTER TABLE <name> ATTACH PARTITION <partition-name> FOR VALUES
+ *
+ * Return the address of the newly attached partition.
+ */
+static ObjectAddress
+ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
+{
+	Relation	attachRel,
+				inheritsRelation;
+	SysScanDesc scan;
+	ScanKeyData key;
+	HeapTuple	tuple;
+	AttrNumber	attno;
+	int			natts;
+	TupleDesc	tupleDesc;
+	ObjectAddress address;
+
+	attachRel = heap_openrv(cmd->name, AccessShareLock);
+
+	/*
+	 * Must be owner of both parent and source table -- parent was checked by
+	 * ATSimplePermissions call in ATPrepCmd
+	 */
+	ATSimplePermissions(attachRel, ATT_TABLE);
+
+	if (relid_is_partition(RelationGetRelid(attachRel)))
+		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")));
+
+	if (attachRel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach a partitioned table as partition")));
+
+	/* attachRel should not already be a inheritance child of some relation */
+	inheritsRelation = heap_open(InheritsRelationId, AccessShareLock);
+	ScanKeyInit(&key,
+				Anum_pg_inherits_inhrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(attachRel)));
+	scan = systable_beginscan(inheritsRelation, InheritsRelidSeqnoIndexId,
+							  true, NULL, 1, &key);
+	if (HeapTupleIsValid(systable_getnext(scan)))
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach table that is a inheritance child as partition")));
+	systable_endscan(scan);
+	heap_close(inheritsRelation, AccessShareLock);
+
+	/* If attachRel is temp, it must belong to this session */
+	if (attachRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+		!attachRel->rd_islocaltemp)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+		errmsg("cannot attach a temporary relation of another session as partition ")));
+
+	/* If parent has OIDs, attachRel must have OIDs */
+	if (attachRel->rd_rel->reloftype)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot attach a typed table 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("Table being attached should contain only the columns"
+							   " present in parent.")));
+	}
+
+	/*
+	 * 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(RelationGetRelid(rel), cmd->bound);
+
+	/* Store bound into the catalog */
+	StorePartitionBound(RelationGetRelid(attachRel), RelationGetRelid(rel),
+						cmd->bound);
+
+	/* OK to create inheritance.  Rest of the checks performed there */
+	CreateInheritance(attachRel, rel);
+
+	/*
+	 * Set up to have the rows in table to be checked for violation of the
+	 * partition bound spec in phase 3 scan.
+	 */
+	if (!cmd->skip_validate)
+	{
+		AlteredTableInfo *tab;
+		PartitionCheck *check;
+		List *my_check,
+			 *parent_check = RelationGetPartitionCheckQual(rel),
+			 *check_quals;
+
+		tab = ATGetQueueEntry(wqueue, attachRel);
+		check = (PartitionCheck *) palloc0(sizeof(PartitionCheck));
+		my_check = get_check_qual_from_partbound(attachRel, rel, cmd->bound);
+		check_quals = parent_check ? list_concat(parent_check, my_check)
+									: my_check;
+		check->expr = makeBoolExpr(AND_EXPR, check_quals, -1);
+		tab->partitionCheck = check;
+	}
+
+	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
+
+	/* keep our lock until commit */
+	heap_close(attachRel, NoLock);
+
+	return address;
+}
+
+/*
+ * ALTER TABLE DETACH PARTITION
+ *
+ * Return the address of the relation that is no longer a partition of rel.
+ */
+static ObjectAddress
+ATExecDetachPartition(Relation rel, RangeVar *name)
+{
+	Relation	partRel;
+	ObjectAddress address;
+
+	partRel = heap_openrv(name, AccessShareLock);
+
+	/* All inheritance related checks are performed within the function */
+	RemoveInheritance(partRel, rel);
+
+	/* Remove the pg_partition entry */
+	RemovePartitionEntryByRelId(RelationGetRelid(partRel));
+
+	/*
+	 * 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/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 700d7be..1323bdf 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2604,6 +2604,7 @@ _copyColumnDef(const ColumnDef *from)
 	COPY_SCALAR_FIELD(is_local);
 	COPY_SCALAR_FIELD(is_not_null);
 	COPY_SCALAR_FIELD(is_from_type);
+	COPY_SCALAR_FIELD(is_for_partition);
 	COPY_SCALAR_FIELD(storage);
 	COPY_NODE_FIELD(raw_default);
 	COPY_NODE_FIELD(cooked_default);
@@ -2999,6 +3000,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_NODE_FIELD(relation);
 	COPY_NODE_FIELD(tableElts);
 	COPY_NODE_FIELD(inhRelations);
+	COPY_NODE_FIELD(partbound);
 	COPY_NODE_FIELD(partby);
 	COPY_NODE_FIELD(ofTypename);
 	COPY_NODE_FIELD(constraints);
@@ -4181,6 +4183,43 @@ _copyPartitionElem(const PartitionElem *from)
 	return newnode;
 }
 
+static PartitionListSpec *
+_copyPartitionListSpec(const PartitionListSpec *from)
+{
+	PartitionListSpec *newnode = makeNode(PartitionListSpec);
+
+	COPY_NODE_FIELD(values);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static PartitionRangeSpec *
+_copyPartitionRangeSpec(const PartitionRangeSpec *from)
+{
+	PartitionRangeSpec *newnode = makeNode(PartitionRangeSpec);
+
+	COPY_SCALAR_FIELD(lowerinc);
+	COPY_NODE_FIELD(lower);
+	COPY_SCALAR_FIELD(upperinc);
+	COPY_NODE_FIELD(upper);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static PartitionCmd *
+_copyPartitionCmd(const PartitionCmd *from)
+{
+	PartitionCmd *newnode = makeNode(PartitionCmd);
+
+	COPY_NODE_FIELD(name);
+	COPY_NODE_FIELD(bound);
+	COPY_SCALAR_FIELD(skip_validate);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					pg_list.h copy functions
  * ****************************************************************
@@ -5098,6 +5137,15 @@ copyObject(const void *from)
 		case T_PartitionElem:
 			retval = _copyPartitionElem(from);
 			break;
+		case T_PartitionListSpec:
+			retval = _copyPartitionListSpec(from);
+			break;
+		case T_PartitionRangeSpec:
+			retval = _copyPartitionRangeSpec(from);
+			break;
+		case T_PartitionCmd:
+			retval = _copyPartitionCmd(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 9554c2f..93fc8f2 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1156,6 +1156,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_NODE_FIELD(relation);
 	COMPARE_NODE_FIELD(tableElts);
 	COMPARE_NODE_FIELD(inhRelations);
+	COMPARE_NODE_FIELD(partbound);
 	COMPARE_NODE_FIELD(partby);
 	COMPARE_NODE_FIELD(ofTypename);
 	COMPARE_NODE_FIELD(constraints);
@@ -2363,6 +2364,7 @@ _equalColumnDef(const ColumnDef *a, const ColumnDef *b)
 	COMPARE_SCALAR_FIELD(is_local);
 	COMPARE_SCALAR_FIELD(is_not_null);
 	COMPARE_SCALAR_FIELD(is_from_type);
+	COMPARE_SCALAR_FIELD(is_for_partition);
 	COMPARE_SCALAR_FIELD(storage);
 	COMPARE_NODE_FIELD(raw_default);
 	COMPARE_NODE_FIELD(cooked_default);
@@ -2641,6 +2643,37 @@ _equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
 	return true;
 }
 
+static bool
+_equalPartitionListSpec(const PartitionListSpec *a, const PartitionListSpec *b)
+{
+	COMPARE_NODE_FIELD(values);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalPartitionRangeSpec(const PartitionRangeSpec *a, const PartitionRangeSpec *b)
+{
+	COMPARE_SCALAR_FIELD(lowerinc);
+	COMPARE_NODE_FIELD(lower);
+	COMPARE_SCALAR_FIELD(upperinc);
+	COMPARE_NODE_FIELD(upper);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
+{
+	COMPARE_NODE_FIELD(name);
+	COMPARE_NODE_FIELD(bound);
+	COMPARE_SCALAR_FIELD(skip_validate);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3397,6 +3430,15 @@ equal(const void *a, const void *b)
 		case T_PartitionElem:
 			retval = _equalPartitionElem(a, b);
 			break;
+		case T_PartitionListSpec:
+			retval = _equalPartitionListSpec(a, b);
+			break;
+		case T_PartitionRangeSpec:
+			retval = _equalPartitionRangeSpec(a, b);
+			break;
+		case T_PartitionCmd:
+			retval = _equalPartitionCmd(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 4d5e37e..2f4ccb8 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(relation);
 	WRITE_NODE_FIELD(tableElts);
 	WRITE_NODE_FIELD(inhRelations);
+	WRITE_NODE_FIELD(partbound);
 	WRITE_NODE_FIELD(partby);
 	WRITE_NODE_FIELD(ofTypename);
 	WRITE_NODE_FIELD(constraints);
@@ -2573,6 +2574,7 @@ _outColumnDef(StringInfo str, const ColumnDef *node)
 	WRITE_BOOL_FIELD(is_local);
 	WRITE_BOOL_FIELD(is_not_null);
 	WRITE_BOOL_FIELD(is_from_type);
+	WRITE_BOOL_FIELD(is_for_partition);
 	WRITE_CHAR_FIELD(storage);
 	WRITE_NODE_FIELD(raw_default);
 	WRITE_NODE_FIELD(cooked_default);
@@ -3288,6 +3290,25 @@ _outPartitionElem(StringInfo str, const PartitionElem *node)
 	WRITE_LOCATION_FIELD(location);
 }
 
+static void
+_outPartitionListSpec(StringInfo str, const PartitionListSpec *node)
+{
+	WRITE_NODE_TYPE("PARTITIONLISTVALUES");
+
+	WRITE_NODE_FIELD(values);
+}
+
+static void
+_outPartitionRangeSpec(StringInfo str, const PartitionRangeSpec *node)
+{
+	WRITE_NODE_TYPE("PARTITIONRANGE");
+
+	WRITE_BOOL_FIELD(lowerinc);
+	WRITE_NODE_FIELD(lower);
+	WRITE_BOOL_FIELD(upperinc);
+	WRITE_NODE_FIELD(upper);
+}
+
 /*
  * outNode -
  *	  converts a Node into ascii string and append it to 'str'
@@ -3874,6 +3895,12 @@ outNode(StringInfo str, const void *obj)
 			case T_PartitionElem:
 				_outPartitionElem(str, obj);
 				break;
+			case T_PartitionListSpec:
+				_outPartitionListSpec(str, obj);
+				break;
+			case T_PartitionRangeSpec:
+				_outPartitionRangeSpec(str, obj);
+				break;
 
 			default:
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 94954dc..c23023a 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2247,6 +2247,35 @@ _readExtensibleNode(void)
 }
 
 /*
+ * _readPartitionListSpec
+ */
+static PartitionListSpec *
+_readPartitionListSpec(void)
+{
+	READ_LOCALS(PartitionListSpec);
+
+	READ_NODE_FIELD(values);
+
+	READ_DONE();
+}
+
+/*
+ * _readPartitionRangeSpec
+ */
+static PartitionRangeSpec *
+_readPartitionRangeSpec(void)
+{
+	READ_LOCALS(PartitionRangeSpec);
+
+	READ_BOOL_FIELD(lowerinc);
+	READ_NODE_FIELD(lower);
+	READ_BOOL_FIELD(upperinc);
+	READ_NODE_FIELD(upper);
+
+	READ_DONE();
+}
+
+/*
  * parseNodeString
  *
  * Given a character string representing a node tree, parseNodeString creates
@@ -2476,6 +2505,10 @@ parseNodeString(void)
 		return_value = _readAlternativeSubPlan();
 	else if (MATCH("EXTENSIBLENODE", 14))
 		return_value = _readExtensibleNode();
+	else if (MATCH("PARTITIONLISTVALUES", 19))
+		return_value = _readPartitionListSpec();
+	else if (MATCH("PARTITIONRANGE", 14))
+		return_value = _readPartitionRangeSpec();
 	else
 	{
 		elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index e912a2d..b3595db 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	VariableSetStmt		*vsetstmt;
 	PartitionElem		*partelem;
 	PartitionBy			*partby;
+	PartitionRangeSpec  *partrange;
 }
 
 %type <node>	stmt schema_stmt
@@ -544,6 +545,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <partby>		PartitionBy OptPartitionBy
 %type <partelem>	part_elem
 %type <list>		part_params
+%type <list>		OptPartitionElementList PartitionElementList
+%type <node>		PartitionElement
+%type <node>		ForValues
+%type <node>		partvalue
+%type <list>		partvalue_list
+%type <boolean>		lb_inc ub_inc
+%type <list>		RangeBound
+%type <boolean>		opt_validate_spec
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -569,7 +578,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 /* ordinary key words in alphabetical order */
 %token <keyword> ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
 	AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
-	ASSERTION ASSIGNMENT ASYMMETRIC AT ATTRIBUTE AUTHORIZATION
+	ASSERTION ASSIGNMENT ASYMMETRIC AT ATTACH ATTRIBUTE AUTHORIZATION
 
 	BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT
 	BOOLEAN_P BOTH BY
@@ -585,7 +594,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 	DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
 	DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DESC
-	DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP
+	DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
+	DOUBLE_P DROP
 
 	EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
 	EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
@@ -599,7 +609,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	HANDLER HAVING HEADER_P HOLD HOUR_P
 
 	IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P
-	INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
+	INCLUDING INCLUSIVE INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
 	INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
 	INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
 
@@ -2373,6 +2383,38 @@ alter_table_cmd:
 					n->def = (Node *)$1;
 					$$ = (Node *) n;
 				}
+			/* ALTER TABLE <name> ATTACH PARTITION <table_name> FOR VALUES */
+			| ATTACH PARTITION qualified_name ForValues opt_validate_spec
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					PartitionCmd *cmd = makeNode(PartitionCmd);
+
+					n->subtype = AT_AttachPartition;
+					cmd->name = $3;
+					cmd->bound = (Node *) $4;
+					cmd->skip_validate = $5;
+					n->def = (Node *) cmd;
+
+					$$ = (Node *) n;
+				}
+			/* ALTER TABLE <name> DETACH PARTITION <partition_name> */
+			| DETACH PARTITION qualified_name
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					PartitionCmd *cmd = makeNode(PartitionCmd);
+
+					n->subtype = AT_DetachPartition;
+					cmd->name = $3;
+					n->def = (Node *) cmd;
+
+					$$ = (Node *) n;
+				}
+		;
+
+opt_validate_spec:
+			NO VALIDATE			{ $$ = true; }
+			| VALIDATE			{ $$ = false; }
+			| /* EMPTY */		{ $$ = false; }
 		;
 
 alter_column_default:
@@ -2468,6 +2510,60 @@ reloption_elem:
 				}
 		;
 
+ForValues:
+			/* a LIST partition */
+			FOR VALUES IN_P '(' partvalue_list ')'
+				{
+					PartitionListSpec *n = makeNode(PartitionListSpec);
+
+					n->values = $5;
+					n->location = @1;
+
+					$$ = (Node *) n;
+				}
+
+			/* a RANGE partition */
+			| FOR VALUES START RangeBound lb_inc END_P RangeBound ub_inc
+				{
+					PartitionRangeSpec *n = makeNode(PartitionRangeSpec);
+
+					n->lowerinc = $5;
+					n->lower = $4;
+					n->upperinc = $8;
+					n->upper = $7;
+					n->location = @1;
+
+					$$ = (Node *) n;
+				}
+		;
+
+RangeBound:
+			UNBOUNDED					{ $$ = NIL; }
+			| '(' partvalue_list ')'	{ $$ = $2; }
+		;
+
+lb_inc:
+			EXCLUSIVE		{ $$ = false;}
+			| INCLUSIVE		{ $$ = true; }
+			| /* EMPTY */	{ $$ = true; }
+		;
+
+ub_inc:
+			INCLUSIVE		{ $$ = true; }
+			| EXCLUSIVE		{ $$ = false; }
+			| /* EMPTY */	{ $$ = false; }
+		;
+
+partvalue:
+			Sconst			{ $$ = makeStringConst($1, @1); }
+			| NumericOnly	{ $$ = makeAConst($1, @1); }
+			| NULL_P		{ $$ = makeNullAConst(@1); }
+		;
+
+partvalue_list:
+			partvalue						{ $$ = list_make1($1); }
+			| partvalue_list ',' partvalue	{ $$ = lappend($1, $3); }
+		;
 
 /*****************************************************************************
  *
@@ -2885,6 +2981,44 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
+		| CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
+			OptPartitionElementList ForValues OptPartitionBy OptWith
+			OnCommitOption OptTableSpace
+				{
+					CreateStmt *n = makeNode(CreateStmt);
+					$4->relpersistence = $2;
+					n->relation = $4;
+					n->tableElts = $8;
+					n->inhRelations = list_make1($7);
+					n->partbound = (Node *) $9;
+					n->partby = $10;
+					n->ofTypename = NULL;
+					n->constraints = NIL;
+					n->options = $11;
+					n->oncommit = $12;
+					n->tablespacename = $13;
+					n->if_not_exists = false;
+					$$ = (Node *)n;
+				}
+		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
+			qualified_name OptPartitionElementList ForValues OptPartitionBy
+			OptWith OnCommitOption OptTableSpace
+				{
+					CreateStmt *n = makeNode(CreateStmt);
+					$7->relpersistence = $2;
+					n->relation = $7;
+					n->tableElts = $11;
+					n->inhRelations = list_make1($10);
+					n->partbound = (Node *) $12;
+					n->partby = $13;
+					n->ofTypename = NULL;
+					n->constraints = NIL;
+					n->options = $14;
+					n->oncommit = $15;
+					n->tablespacename = $16;
+					n->if_not_exists = true;
+					$$ = (Node *)n;
+				}
 		;
 
 /*
@@ -2930,6 +3064,11 @@ OptTypedTableElementList:
 			| /*EMPTY*/							{ $$ = NIL; }
 		;
 
+OptPartitionElementList:
+			'(' PartitionElementList ')'		{ $$ = $2; }
+			| /*EMPTY*/							{ $$ = NIL; }
+		;
+
 TableElementList:
 			TableElement
 				{
@@ -2952,6 +3091,17 @@ TypedTableElementList:
 				}
 		;
 
+PartitionElementList:
+			PartitionElement
+				{
+					$$ = list_make1($1);
+				}
+			| PartitionElementList ',' PartitionElement
+				{
+					$$ = lappend($1, $3);
+				}
+		;
+
 TableElement:
 			columnDef							{ $$ = $1; }
 			| TableLikeClause					{ $$ = $1; }
@@ -2963,6 +3113,11 @@ TypedTableElement:
 			| TableConstraint					{ $$ = $1; }
 		;
 
+PartitionElement:
+			columnOptions						{ $$ = $1; }
+			| TableConstraint					{ $$ = $1; }
+		;
+
 columnDef:	ColId Typename create_generic_options ColQualList
 				{
 					ColumnDef *n = makeNode(ColumnDef);
@@ -2972,6 +3127,7 @@ columnDef:	ColId Typename create_generic_options ColQualList
 					n->is_local = true;
 					n->is_not_null = false;
 					n->is_from_type = false;
+					n->is_for_partition = false;
 					n->storage = 0;
 					n->raw_default = NULL;
 					n->cooked_default = NULL;
@@ -2993,6 +3149,7 @@ columnOptions:	ColId WITH OPTIONS ColQualList
 					n->is_local = true;
 					n->is_not_null = false;
 					n->is_from_type = false;
+					n->is_for_partition = false;
 					n->storage = 0;
 					n->raw_default = NULL;
 					n->cooked_default = NULL;
@@ -4544,6 +4701,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;
+				}
 		;
 
 /*****************************************************************************
@@ -11133,6 +11332,7 @@ TableFuncElement:	ColId Typename opt_collate_clause
 					n->is_local = true;
 					n->is_not_null = false;
 					n->is_from_type = false;
+					n->is_for_partition = false;
 					n->storage = 0;
 					n->raw_default = NULL;
 					n->cooked_default = NULL;
@@ -13812,6 +14012,7 @@ unreserved_keyword:
 			| ASSERTION
 			| ASSIGNMENT
 			| AT
+			| ATTACH
 			| ATTRIBUTE
 			| BACKWARD
 			| BEFORE
@@ -13858,6 +14059,7 @@ unreserved_keyword:
 			| DELIMITER
 			| DELIMITERS
 			| DEPENDS
+			| DETACH
 			| DICTIONARY
 			| DISABLE_P
 			| DISCARD
@@ -13900,6 +14102,7 @@ unreserved_keyword:
 			| IMPLICIT_P
 			| IMPORT_P
 			| INCLUDING
+			| INCLUSIVE
 			| INCREMENT
 			| INDEX
 			| INDEXES
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 3e8d457..37eb9d3 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -508,7 +508,10 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 				err = _("grouping operations are not allowed in partition key expression");
 
 			break;
+		case EXPR_KIND_PARTITION_FOR_VALUES:
+			errkind = true;
 
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -869,6 +872,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_PARTITION_KEY:
 			err = _("window functions are not allowed in partition key expression");
 			break;
+		case EXPR_KIND_PARTITION_FOR_VALUES:
+			errkind = true;
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index a97f727..cb0579d 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -355,6 +355,26 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 				result = (Node *) expr;
 				break;
 			}
+		case T_PartitionListSpec:
+			{
+				PartitionListSpec *list = (PartitionListSpec *) expr;
+
+				list->values = transformExpressionList(pstate, list->values,
+														pstate->p_expr_kind);
+				result = expr;
+				break;
+			}
+		case T_PartitionRangeSpec:
+			{
+				PartitionRangeSpec *range = (PartitionRangeSpec *) expr;
+
+				range->lower = transformExpressionList(pstate, range->lower,
+														pstate->p_expr_kind);
+				range->upper = transformExpressionList(pstate, range->upper,
+														pstate->p_expr_kind);
+				result = expr;
+				break;
+			}
 
 		default:
 			/* should not reach here */
@@ -1723,6 +1743,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_OFFSET:
 		case EXPR_KIND_RETURNING:
 		case EXPR_KIND_VALUES:
+		case EXPR_KIND_PARTITION_FOR_VALUES:
 			/* okay */
 			break;
 		case EXPR_KIND_CHECK_CONSTRAINT:
@@ -3302,6 +3323,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "WHEN";
 		case EXPR_KIND_PARTITION_KEY:
 			return "partition key expression";
+		case EXPR_KIND_PARTITION_FOR_VALUES:
+			return "FOR VALUES";
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index be21a8e..578ae2f 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -49,8 +49,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"
@@ -90,6 +92,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 */
@@ -132,6 +135,10 @@ static void transformConstraintAttrs(CreateStmtContext *cxt,
 						 List *constraintList);
 static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
 static void setSchemaName(char *context_schema, char **stmt_schema_name);
+static void transformPartitionOf(CreateStmtContext *cxt, Node *bound);
+static void transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd);
+static Node *transformPartitionBound(CreateStmtContext *cxt, Relation parent,
+						Node *bound);
 
 
 /*
@@ -233,6 +240,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
 	cxt.ispartitioned = stmt->partby != NULL;
+	cxt.partbound = NULL;
 
 	/*
 	 * Notice that we allow OIDs here only for plain tables, even though
@@ -251,11 +259,14 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	if (stmt->ofTypename)
 		transformOfType(&cxt, stmt->ofTypename);
 
+	if (stmt->partbound)
+		transformPartitionOf(&cxt, stmt->partbound);
+
 	if (stmt->partby)
 	{
 		int		partnatts = list_length(stmt->partby->partParams);
 
-		if (stmt->inhRelations)
+		if (stmt->inhRelations && !stmt->partbound)
 			ereport(ERROR,
 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 				 errmsg("cannot create partitioned table as inheritance child")));
@@ -361,6 +372,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	 */
 	stmt->tableElts = cxt.columns;
 	stmt->constraints = cxt.ckconstraints;
+	stmt->partbound = cxt.partbound;
 
 	result = lappend(cxt.blist, stmt);
 	result = list_concat(result, cxt.alist);
@@ -900,6 +912,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 		def->is_local = true;
 		def->is_not_null = attribute->attnotnull;
 		def->is_from_type = false;
+		def->is_for_partition = false;
 		def->storage = 0;
 		def->raw_default = NULL;
 		def->cooked_default = NULL;
@@ -1119,6 +1132,7 @@ transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
 		n->is_local = true;
 		n->is_not_null = false;
 		n->is_from_type = true;
+		n->is_for_partition = false;
 		n->storage = 0;
 		n->raw_default = NULL;
 		n->cooked_default = NULL;
@@ -2588,6 +2602,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
 	cxt.ispartitioned = relid_is_partitioned(relid);
+	cxt.partbound = NULL;
 
 	/*
 	 * The only subtypes that currently require parse transformation handling
@@ -2676,6 +2691,22 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 					break;
 				}
 
+			case AT_AttachPartition:
+				{
+					PartitionCmd	*partcmd = (PartitionCmd *) cmd->def;
+
+					transformAttachPartition(&cxt, partcmd);
+
+					/* assign transformed values */
+					partcmd->bound = cxt.partbound;
+				}
+
+				newcmds = lappend(newcmds, cmd);
+				break;
+			case AT_DetachPartition:
+				newcmds = lappend(newcmds, cmd);
+				break;
+
 			default:
 				newcmds = lappend(newcmds, cmd);
 				break;
@@ -3040,3 +3071,304 @@ setSchemaName(char *context_schema, char **stmt_schema_name)
 						"different from the one being created (%s)",
 						*stmt_schema_name, context_schema)));
 }
+
+/*
+ * transformPartitionOf
+ *		Analyze PARTITION OF ... FOR VALUES ...
+ */
+static void
+transformPartitionOf(CreateStmtContext *cxt, Node *bound)
+{
+	TupleDesc	tupdesc;
+	int			i;
+	RangeVar   *part = cxt->relation;
+	RangeVar   *partof = linitial(cxt->inhRelations);
+	Relation	parentRel;
+
+	parentRel = heap_openrv(partof, AccessShareLock);
+
+	/* Check if the target table is partitioned at all */
+	if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("\"%s\" is not partitioned",
+						RelationGetRelationName(parentRel))));
+	Assert(RelationGetPartitionKey(parentRel) != NULL);
+
+	/* Permanent rels cannot be partitions of temporary ones */
+	if (part->relpersistence != RELPERSISTENCE_TEMP &&
+		parentRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot create as partition of temporary relation \"%s\"",
+						partof->relname)));
+
+	/* If parent rel is temp, it must belong to this session */
+	if (parentRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+		!parentRel->rd_islocaltemp)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot create as partition of temporary relation of another session")));
+
+	/*
+	 * Do not allow OIDs in a partition, if not present in the parent. But
+	 * force them in partition, if they are present in the parent.
+	 */
+	if (cxt->hasoids && !parentRel->rd_rel->relhasoids)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot create table with OIDs as partition of table without OIDs")));
+
+	if (parentRel->rd_rel->relhasoids)
+		cxt->hasoids = true;
+
+	tupdesc = RelationGetDescr(parentRel);
+	for (i = 0; i < tupdesc->natts; i++)
+	{
+		Form_pg_attribute attribute = tupdesc->attrs[i];
+		ColumnDef  *def;
+
+		if (attribute->attisdropped)
+			continue;
+
+		def = makeNode(ColumnDef);
+		def->colname = pstrdup(NameStr(attribute->attname));
+		def->typeName = makeTypeNameFromOid(attribute->atttypid,
+											attribute->atttypmod);
+		def->inhcount = 1;
+		def->is_local = false;
+		def->is_not_null = attribute->attnotnull;
+		def->is_from_type = false;
+		def->is_for_partition = true;
+		def->storage = attribute->attstorage;
+		def->raw_default = NULL;
+		def->cooked_default = NULL;
+		def->collClause = NULL;
+		def->collOid = attribute->attcollation;
+		def->constraints = NIL;
+		def->location = -1;
+
+		cxt->columns = lappend(cxt->columns, def);
+	}
+
+	/* tranform the values */
+	cxt->partbound = transformPartitionBound(cxt, parentRel, bound);
+
+	heap_close(parentRel, AccessShareLock);
+}
+
+/*
+ * transformAttachPartition
+ *		Analyze ATTACH PARTITION ... FOR VALUES ...
+ */
+static void
+transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd)
+{
+	Relation	parentRel = cxt->rel;
+
+	/* Check if the target table is partitioned at all */
+	if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("\"%s\" is not partitioned", cxt->relation->relname)));
+	Assert(RelationGetPartitionKey(parentRel) != NULL);
+
+	/* tranform the values */
+	cxt->partbound = transformPartitionBound(cxt, parentRel, cmd->bound);
+}
+
+/*
+ * transformPartitionBound
+ *
+ * Transform partition bound per the partition key
+ */
+static Node *
+transformPartitionBound(CreateStmtContext *cxt, Relation parent, Node *bound)
+{
+	int			i;
+	ListCell   *cell;
+	Oid			parentId = RelationGetRelid(parent);
+	PartitionKey		key = RelationGetPartitionKey(parent);
+	char				strategy = get_partition_key_strategy(key);
+	int					partnatts = get_partition_key_natts(key);
+	PartitionListSpec  *list, *result_list;
+	PartitionRangeSpec *range, *result_range;
+
+	switch (strategy)
+	{
+		case PARTITION_STRAT_LIST:
+			if (!IsA(bound, PartitionListSpec))
+				ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("invalid bound specification for a list partition"),
+					 parser_errposition(cxt->pstate, exprLocation(bound))));
+
+			list = (PartitionListSpec *) transformExpr(cxt->pstate, bound,
+											EXPR_KIND_PARTITION_FOR_VALUES);
+
+			result_list = makeNode(PartitionListSpec);
+
+			foreach(cell, list->values)
+			{
+				Node   *value = (Node *) lfirst(cell);
+				Node   *orig_value = value;
+				Oid		valuetype;
+
+				valuetype = exprType(value);
+				value = coerce_to_target_type(cxt->pstate,
+											value, valuetype,
+											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 key column \"%s\"",
+					 format_type_be(get_partition_col_typid(key, 0)),
+					 get_relid_attribute_name(parentId,
+											  get_partition_col_attnum(key, 0))),
+					 parser_errposition(cxt->pstate, exprLocation(orig_value))));
+
+				/* Simplify the expression */
+				value = (Node *) expression_planner((Expr *) value);
+
+				result_list->values = lappend(result_list->values, value);
+			}
+			return (Node *) result_list;
+
+		case PARTITION_STRAT_RANGE:
+			if (!IsA(bound, PartitionRangeSpec))
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("invalid bound specification for a range partition"),
+					 parser_errposition(cxt->pstate, exprLocation(bound))));
+
+			range = (PartitionRangeSpec *) transformExpr(cxt->pstate, bound,
+											EXPR_KIND_PARTITION_FOR_VALUES);
+
+			if (!range->lower && !range->upper)
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("both START and END cannot be UNBOUNDED"),
+					 parser_errposition(cxt->pstate, range->location)));
+
+			result_range = makeNode(PartitionRangeSpec);
+			result_range->lowerinc = range->lowerinc;
+			result_range->upperinc = range->upperinc;
+
+			if (range->lower && list_length(range->lower) > partnatts)
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("START has more values specified than number"
+							" of columns in the partition key"),
+							parser_errposition(cxt->pstate,
+									exprLocation(list_nth(range->lower,
+									 list_length(range->lower) - 1)))));
+			else if (range->lower && list_length(range->lower) < partnatts)
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("START has fewer values specified than number"
+							" of columns in the partition key"),
+							parser_errposition(cxt->pstate,
+									exprLocation(list_nth(range->lower,
+									 list_length(range->lower) - 1)))));
+
+			if (range->upper && list_length(range->upper) > partnatts)
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("END has more values specified than number"
+							" of columns in the partition key"),
+							parser_errposition(cxt->pstate,
+									exprLocation(list_nth(range->upper,
+									 list_length(range->upper) - 1)))));
+			else if (range->upper && list_length(range->upper) < partnatts)
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("END has fewer values specified than number"
+							" of columns in the partition key"),
+							parser_errposition(cxt->pstate,
+									exprLocation(list_nth(range->upper,
+									 list_length(range->upper) - 1)))));
+
+			if (range->lower)
+			{
+				i = 0;
+				foreach (cell, range->lower)
+				{
+					Node   *value = (Node *) lfirst(cell);
+					Node   *orig_value = value;
+					Oid		valuetype;
+
+					valuetype = exprType(value);
+					value = coerce_to_target_type(cxt->pstate,
+												value, valuetype,
+											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 key column \"%s\"",
+									format_type_be(get_partition_col_typid(key, i)),
+									get_relid_attribute_name(parentId,
+													  get_partition_col_attnum(key, i))),
+							 parser_errposition(cxt->pstate, exprLocation(orig_value))));
+
+					/* Simplify the expression */
+					value = (Node *) expression_planner((Expr *) value);
+
+					result_range->lower = lappend(result_range->lower, value);
+					++i;
+				}
+			}
+			else
+				result_range->lowerinc = false;
+
+			if (range->upper)
+			{
+				i = 0;
+				foreach (cell, range->upper)
+				{
+					Node   *value = (Node *) lfirst(cell);
+					Node   *orig_value = value;
+					Oid		valuetype;
+
+					valuetype = exprType(value);
+					value = coerce_to_target_type(cxt->pstate,
+												value, valuetype,
+											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 key column \"%s\"",
+								format_type_be(get_partition_col_typid(key, i)),
+								get_relid_attribute_name(parentId,
+													  get_partition_col_attnum(key, i))),
+							 parser_errposition(cxt->pstate, exprLocation(orig_value))));
+
+					/* Simplify the expression */
+					value = (Node *) expression_planner((Expr *) value);
+
+					result_range->upper = lappend(result_range->upper, value);
+					++i;
+				}
+			}
+			else
+				result_range->upperinc = false;
+
+			return (Node *) result_range;
+	}
+
+	return NULL;
+}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 3787441..180f8fd 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -279,6 +279,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);
 
 
 /*
@@ -928,6 +930,37 @@ 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);
+
+		for (i = 0; i < pdesc1->nparts; i++)
+		{
+			if (!partition_equal(key, pdesc1->parts[i], pdesc2->parts[i]))
+				return false;
+		}
+	}
+	else if (pdesc2 != NULL)
+		return false;
+
+	return true;
+}
+
+/*
  *		RelationBuildDesc
  *
  *		Build a relation descriptor.  The caller must hold at least
@@ -1055,11 +1088,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_REL)
+	{
 		RelationBuildPartitionKey(relation);
+		RelationBuildPartitionDesc(relation);
+	}
 	else
+	{
 		relation->rd_partkey = NULL;
+		relation->rd_partdesc = NULL;
+		relation->rd_pdcxt = NULL;
+	}
 
 	/*
 	 * if it's an index, initialize index-related information
@@ -2059,6 +2099,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 	if (relation->rd_rsdesc)
 		MemoryContextDelete(relation->rd_rsdesc->rscxt);
 	FreePartitionKey(relation->rd_partkey);
+	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);
@@ -2222,6 +2266,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);
@@ -2252,6 +2297,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
@@ -2307,6 +2355,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 */
@@ -3533,6 +3588,20 @@ RelationCacheInitializePhase3(void)
 			restart = true;
 		}
 
+		/*
+		 * Reload partition key and descriptor for a partitioned table.
+		 */
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+		{
+			RelationBuildPartitionKey(relation);
+			Assert(relation->rd_partkey != NULL);
+
+			RelationBuildPartitionDesc(relation);
+			Assert(relation->rd_partdesc != NULL);
+
+			restart = true;
+		}
+
 		/* Release hold on the relation */
 		RelationDecrementReferenceCount(relation);
 
@@ -5057,6 +5126,8 @@ load_relcache_init_file(bool shared)
 		rel->trigdesc = NULL;
 		rel->rd_rsdesc = 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/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index a6563fe..4a5783a 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -49,6 +49,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opfamily.h"
 #include "catalog/pg_partitioned.h"
+#include "catalog/pg_partition.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_range.h"
 #include "catalog/pg_rewrite.h"
@@ -569,6 +570,17 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		8
 	},
+	{PartitionRelationId,		/* PARTRELID */
+		PartitionRelidIndexId,
+		1,
+		{
+			Anum_pg_partition_partrelid,
+			0,
+			0,
+			0
+		},
+		128
+	},
 	{PartitionedRelationId,		/* PARTEDRELID */
 		PartitionedRelidIndexId,
 		1,
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index cadb741..73786c0 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -322,6 +322,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication
 DECLARE_UNIQUE_INDEX(pg_partitioned_partedrelid_index, 3351, on pg_partitioned using btree(partedrelid oid_ops));
 #define PartitionedRelidIndexId          3351
 
+DECLARE_UNIQUE_INDEX(pg_partition_partrelid_index, 3354, on pg_partition using btree(partrelid oid_ops));
+#define PartitionRelidIndexId          3354
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index acff099..db59c9a 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -14,10 +14,23 @@
 #define PARTITION_H
 
 #include "fmgr.h"
+#include "parser/parse_node.h"
 #include "utils/relcache.h"
 
 typedef struct PartitionKeyData *PartitionKey;
 
+/*
+ * Information about partitions of a partitioned table.
+ */
+typedef struct PartitionInfoData *PartitionInfo;
+typedef struct PartitionDescData
+{
+	int		nparts;			/* Number of partitions */
+	PartitionInfo *parts;	/* Array of PartitionInfoData pointers */
+} PartitionDescData;
+
+typedef struct PartitionDescData *PartitionDesc;
+
 /* relcache support for partition key information */
 extern void RelationBuildPartitionKey(Relation relation);
 extern void FreePartitionKey(PartitionKey key);
@@ -32,4 +45,15 @@ extern int16 get_partition_col_attnum(PartitionKey key, int col);
 extern Oid get_partition_col_typid(PartitionKey key, int col);
 extern int32 get_partition_col_typmod(PartitionKey key, int col);
 
+/* relcache support functions for partition descriptor */
+extern void RelationBuildPartitionDesc(Relation relation);
+extern bool partition_equal(PartitionKey key, PartitionInfo p1,
+											  PartitionInfo p2);
+
+/* For commands/tablecmds.c's and catalog/heap.c's perusal */
+extern void check_new_partition_bound(Oid parentId, Node *bound);
+extern Oid get_partition_parent(Oid relid);
+extern List *get_check_qual_from_partbound(Relation rel, Relation parent,
+										   Node *bound);
+extern List *RelationGetPartitionCheckQual(Relation rel);
 #endif   /* PARTITION_H */
diff --git a/src/include/catalog/pg_partition.h b/src/include/catalog/pg_partition.h
new file mode 100644
index 0000000..d31a6b7
--- /dev/null
+++ b/src/include/catalog/pg_partition.h
@@ -0,0 +1,54 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partition.h
+ *	  definition of the system "partition" relation (pg_partition)
+ *	  along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partition.h $
+ *
+ * NOTES
+ *	  the genbki.sh script reads this file and generates .bki
+ *	  information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITION_H
+#define PG_PARTITION_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ *		pg_partitioned_rel definition.  cpp turns this into
+ *		typedef struct FormData_pg_partitioned_rel
+ * ----------------
+ */
+#define PartitionRelationId 3353
+
+CATALOG(pg_partition,3353) BKI_WITHOUT_OIDS
+{
+	Oid			partrelid;		/* partition relation OID */
+
+#ifdef CATALOG_VARLEN			/* variable-length fields start here */
+	pg_node_tree	partbound;	/* node representation of partition bound */
+#endif
+} FormData_pg_partition;
+
+/* ----------------
+ *      Form_pg_partition corresponds to a pointer to a tuple with
+ *      the format of pg_partition relation.
+ * ----------------
+ */
+typedef FormData_pg_partition *Form_pg_partition;
+
+/* ----------------
+ *      compiler constants for pg_partition
+ * ----------------
+ */
+#define Natts_pg_partition				2
+#define Anum_pg_partition_partrelid		1
+#define Anum_pg_partition_partbound		2
+
+#endif   /* PG_PARTITION_H */
diff --git a/src/include/catalog/pg_partition_fn.h b/src/include/catalog/pg_partition_fn.h
new file mode 100644
index 0000000..dccb774
--- /dev/null
+++ b/src/include/catalog/pg_partition_fn.h
@@ -0,0 +1,22 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partition_fn.h
+ *	  prototypes for functions in catalog/pg_partition.c
+ *
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_partition_fn.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITION_FN_H
+#define PG_PARTITION_FN_H
+
+/* pg_partition catalog functions */
+extern void StorePartitionBound(Oid relid, Oid parentId, Node *bound);
+extern void RemovePartitionEntryByRelId(Oid relid);
+extern bool relid_is_partition(Oid relid);
+
+#endif   /* PG_PARTITION_FN_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index c316af6..365073b 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -405,6 +405,7 @@ typedef enum NodeTag
 	T_AlterPolicyStmt,
 	T_CreateTransformStmt,
 	T_CreateAmStmt,
+	T_PartitionCmd,
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -454,6 +455,8 @@ typedef enum NodeTag
 	T_RoleSpec,
 	T_PartitionElem,
 	T_PartitionBy,
+	T_PartitionListSpec,
+	T_PartitionRangeSpec,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 2b77579..e044aa7 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -592,6 +592,7 @@ typedef struct ColumnDef
 	bool		is_local;		/* column has local (non-inherited) def'n */
 	bool		is_not_null;	/* NOT NULL constraint specified? */
 	bool		is_from_type;	/* column definition came from table type */
+	bool		is_for_partition;	/* column definition is for a partition */
 	char		storage;		/* attstorage setting, or 0 for default */
 	Node	   *raw_default;	/* default value (untransformed parse tree) */
 	Node	   *cooked_default; /* default value (transformed expr tree) */
@@ -732,6 +733,40 @@ typedef struct PartitionBy
 	int			location;	/* token location, or -1 if unknown */
 } PartitionBy;
 
+/*
+ * PartitionListSpec - a list partition bound
+ */
+typedef struct PartitionListSpec
+{
+	NodeTag		type;
+	List	   *values;
+	int			location;
+} PartitionListSpec;
+
+/*
+ * PartitionRangeSpec - a range partition bound
+ */
+typedef struct PartitionRangeSpec
+{
+	NodeTag		type;
+	bool		lowerinc;
+	List	   *lower;
+	bool		upperinc;
+	List	   *upper;
+	int			location;   /* token location, or -1 if unknown */
+} PartitionRangeSpec;
+
+/*
+ * PartitionCmd -  ALTER TABLE partition commands
+ */
+typedef struct PartitionCmd
+{
+	NodeTag		type;
+	RangeVar   *name;
+	Node	   *bound;
+	bool		skip_validate;
+} PartitionCmd;
+
 /****************************************************************************
  *	Nodes for a Query tree
  ****************************************************************************/
@@ -1559,7 +1594,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
@@ -1784,7 +1821,9 @@ typedef struct CreateStmt
 	RangeVar   *relation;		/* relation to create */
 	List	   *tableElts;		/* column definitions (list of ColumnDef) */
 	List	   *inhRelations;	/* relations to inherit from (list of
-								 * inhRelation) */
+								 * inhRelation); (ab)used also as partition
+								 * parent */
+	Node	   *partbound;		/* FOR VALUES clause */
 	PartitionBy *partby;		/* PARTITION BY clause */
 	TypeName   *ofTypename;		/* OF typename */
 	List	   *constraints;	/* constraints (list of Constraint nodes) */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 40da67a..70c264c 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -49,6 +49,7 @@ PG_KEYWORD("assertion", ASSERTION, UNRESERVED_KEYWORD)
 PG_KEYWORD("assignment", ASSIGNMENT, UNRESERVED_KEYWORD)
 PG_KEYWORD("asymmetric", ASYMMETRIC, RESERVED_KEYWORD)
 PG_KEYWORD("at", AT, UNRESERVED_KEYWORD)
+PG_KEYWORD("attach", ATTACH, UNRESERVED_KEYWORD)
 PG_KEYWORD("attribute", ATTRIBUTE, UNRESERVED_KEYWORD)
 PG_KEYWORD("authorization", AUTHORIZATION, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("backward", BACKWARD, UNRESERVED_KEYWORD)
@@ -127,6 +128,7 @@ PG_KEYWORD("delimiter", DELIMITER, UNRESERVED_KEYWORD)
 PG_KEYWORD("delimiters", DELIMITERS, UNRESERVED_KEYWORD)
 PG_KEYWORD("depends", DEPENDS, UNRESERVED_KEYWORD)
 PG_KEYWORD("desc", DESC, RESERVED_KEYWORD)
+PG_KEYWORD("detach", DETACH, UNRESERVED_KEYWORD)
 PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD)
 PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD)
@@ -191,6 +193,7 @@ PG_KEYWORD("implicit", IMPLICIT_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("import", IMPORT_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("in", IN_P, RESERVED_KEYWORD)
 PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD)
+PG_KEYWORD("inclusive", INCLUSIVE, UNRESERVED_KEYWORD)
 PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD)
 PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD)
 PG_KEYWORD("indexes", INDEXES, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index a13c6fb..3d45663 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -65,7 +65,8 @@ typedef enum ParseExprKind
 	EXPR_KIND_EXECUTE_PARAMETER,	/* parameter value in EXECUTE */
 	EXPR_KIND_TRIGGER_WHEN,		/* WHEN condition in CREATE TRIGGER */
 	EXPR_KIND_POLICY,			/* USING or WITH CHECK expr in policy */
-	EXPR_KIND_PARTITION_KEY		/* partition key expression */
+	EXPR_KIND_PARTITION_KEY,	/* partition key expression */
+	EXPR_KIND_PARTITION_FOR_VALUES	/* partition bound */
 } ParseExprKind;
 
 
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index e05d122..7121390 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -95,6 +95,9 @@ typedef struct RelationData
 	bool		rd_fkeyvalid;	/* true if list has been computed */
 
 	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 */
@@ -540,6 +543,12 @@ typedef struct ViewOptions
  */
 #define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
 
+/*
+ * 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/include/utils/syscache.h b/src/include/utils/syscache.h
index e727842..4e493e0 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
 	OPEROID,
 	OPFAMILYAMNAMENSP,
 	OPFAMILYOID,
+	PARTRELID,
 	PARTEDRELID,
 	PROCNAMEARGSNSP,
 	PROCOID,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index cb105af..5598809 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2958,5 +2958,186 @@ CREATE TABLE no_inh_child (
 CREATE TABLE inh_parent(a int);
 ALTER TABLE no_inh_child INHERIT inh_parent;
 ERROR:  cannot change inheritance of partitioned table
-DROP TABLE no_inh_child, inh_parent, partitioned;
-ERROR:  table "partitioned" does not exist
+DROP TABLE no_inh_child, inh_parent;
+-- ATTACH PARTITION
+-- check target table 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 partition bounds compatible
+CREATE TABLE list_parted (
+	a int,
+	b char(2) NOT NULL 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 START (1) END (10);
+ERROR:  invalid bound specification for a list partition
+DROP TABLE fail_part;
+-- check the table being attached exists
+ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
+ERROR:  relation "nonexistant" does not exist
+-- XXX add ownership tests
+-- check the table being attached is not itself partitioned
+CREATE TABLE another_parted (
+	a int
+) PARTITION BY RANGE (a);
+ALTER TABLE list_parted ATTACH PARTITION another_parted FOR VALUES IN (1);
+ERROR:  cannot attach a partitioned table as partition
+DROP TABLE another_parted;
+-- check the table being attached is not inheritance child of some relation
+CREATE TABLE parent (LIKE list_parted);
+CREATE TABLE fail_part () INHERITS (parent);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  cannot attach table that is a inheritance child as partition
+DROP TABLE parent CASCADE;
+NOTICE:  drop cascades to table fail_part
+-- check 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 the 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 the table being attached does not have columns not 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:  Table being attached should contain only the columns present in parent.
+DROP TABLE fail_part;
+-- check the table being attached has all columns of the parent
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  source table is missing column "b"
+DROP TABLE fail_part;
+-- check the columns of the table being attached match in type, collation and NOT NULL status
+CREATE TABLE fail_part (
+	a int,
+	b int
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  source table "fail_part" has different type for column "b"
+ALTER TABLE fail_part ALTER b TYPE char (3);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  source 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:  source table "fail_part" has different collation for column "b"
+DROP TABLE fail_part;
+-- check the table being attached all constraints of the parent
+CREATE TABLE fail_part (
+	a int,
+	b char(2) NOT NULL COLLATE "en_US"
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  source table is missing constraint "check_a"
+-- check the constraint of table being attached 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:  source table "fail_part" has different definition for check constraint "check_a"
+DROP TABLE fail_part;
+-- finally check the attincount and coninhcount after successfully attached
+CREATE TABLE part_1 (
+	a int,
+	b char(2) NOT NULL COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+SELECT attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+ attinhcount 
+-------------
+           1
+           1
+(2 rows)
+
+SELECT coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+ coninhcount 
+-------------
+           1
+(1 row)
+
+-- also check connoinherit on the partition's constraint, it must have been turned off
+SELECT connoinherit FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+ connoinherit 
+--------------
+ f
+(1 row)
+
+-- check the new partition does not overlap with existing partition
+CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR:  new partition's list of values overlaps with partition "part_1" of "list_parted"
+HINT:  Please specify a list of values that does not overlap with any existing partition's list.
+-- check the new partition does not contain values outside specified bound
+INSERT INTO fail_part VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+ERROR:  table contains a row violating the partition bound specification
+-- ok if NO VALIDATE is specified
+ALTER TABLE fail_part RENAME TO part_2;
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (2) NO VALIDATE;
+-- check the table being attached is not already a partition
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (1);
+ERROR:  "part_2" is already a partition
+-- DETACH PARTITION
+-- check the partition being detached exists at all
+ALTER TABLE list_parted DETACH PARTITION part_3;
+ERROR:  relation "part_3" does not exist
+-- check the partition being detached is a partition (of the parent)
+CREATE TABLE not_a_part (a int);
+ALTER TABLE list_parted DETACH PARTITION not_a_part;
+ERROR:  relation "not_a_part" is not a partition of relation "list_parted"
+-- check that attinhcount and coninhcount dropped to 0 after detached
+ALTER TABLE list_parted DETACH PARTITION part_2;
+SELECT attinhcount FROM pg_attribute WHERE attrelid = 'part_2'::regclass AND attnum > 0;
+ attinhcount 
+-------------
+           0
+           0
+(2 rows)
+
+SELECT coninhcount FROM pg_constraint WHERE conrelid = 'part_2'::regclass AND conname = 'check_a';
+ coninhcount 
+-------------
+           0
+(1 row)
+
+-- Miscellaneous ALTER TABLE special behaviors for partitions
+-- cannot add/drop a column to/from a partition or rename it
+ALTER TABLE part_1 ADD COLUMN c text;
+ERROR:  cannot add column to a partition
+ALTER TABLE part_1 DROP COLUMN b;
+ERROR:  cannot drop column from a partition
+ALTER TABLE part_1 RENAME COLUMN b to c;
+ERROR:  cannot rename column of a partition
+-- cannot alter type of a column of a partition
+ALTER TABLE part_1 ALTER COLUMN b TYPE text;
+ERROR:  cannot alter column type of a partition
+-- cannot let a partition participate in regular inheritance
+CREATE TABLE inh_test () INHERITS (part_1);
+ERROR:  cannot inherit from partition "part_1"
+CREATE TABLE inh_test (LIKE part_1);
+ALTER TABLE inh_test INHERIT part_1;
+ERROR:  cannot inherit from a partition
+ALTER TABLE part_1 INHERIT inh_test;
+ERROR:  cannot change inheritance of a partition
+-- cannot alter DROP NOT NULL on a partition column if the parent has NOT NULL set
+ALTER TABLE part_1 ALTER b DROP NOT NULL;
+ERROR:  column "b" is not null in parent
+HINT:  Please drop not null in the parent instead
+-- cleanup
+DROP TABLE list_parted CASCADE;
+NOTICE:  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 4fc8c06..9295093 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -439,3 +439,202 @@ Partitioned table "public.describe_list_key"
 Partition Key: PARTITION BY LIST (a)
 
 DROP TABLE describe_range_key, describe_list_key;
+--
+-- CREATE TABLE PARTITION OF
+--
+-- check partition bound syntax
+CREATE TABLE list_parted (
+	a int
+) PARTITION BY LIST (a);
+-- syntax allows only string literal, numeric and null to be specified for
+-- a partition bound value
+CREATE TABLE lpart1 PARTITION OF list_parted FOR VALUES IN ('1');
+CREATE TABLE lpart2 PARTITION OF list_parted FOR VALUES IN (1);
+ERROR:  new partition's list of values overlaps with partition "lpart1" of "list_parted"
+HINT:  Please specify a list of values that does not overlap with any existing partition's list.
+CREATE TABLE lpart3 PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN (int '1');
+ERROR:  syntax error at or near "int"
+LINE 1: ...fail_lpart PARTITION OF list_parted FOR VALUES IN (int '1');
+                                                              ^
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ('1'::int);
+ERROR:  syntax error at or near "::"
+LINE 1: ...ail_lpart PARTITION OF list_parted FOR VALUES IN ('1'::int);
+                                                                ^
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ();
+ERROR:  syntax error at or near ")"
+LINE 1: ... TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ();
+                                                                     ^
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES START (1) END (2);
+ERROR:  invalid bound specification for a list partition
+CREATE TABLE range_parted (
+	a date
+) PARTITION BY RANGE (a);
+-- trying to specify list for range partitioned table
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES IN ('a');
+ERROR:  invalid bound specification for a range partition
+-- both start and end bounds of a range partition cannot be UNBOUNDED
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START UNBOUNDED END UNBOUNDED;
+ERROR:  both START and END cannot be UNBOUNDED
+LINE 1: CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES...
+                                                          ^
+-- each of start and end bounds must have same number of values as there
+-- are columns in the partition key
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z', 1);
+ERROR:  START has more values specified than number of columns in the partition key
+LINE 1: ... PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z...
+                                                             ^
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('z', 1);
+ERROR:  END has more values specified than number of columns in the partition key
+LINE 1: ...RTITION OF range_parted FOR VALUES START ('a') END ('z', 1);
+                                                                    ^
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z');
+ERROR:  START has more values specified than number of columns in the partition key
+LINE 1: ... PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z...
+                                                             ^
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('b');
+ERROR:  invalid input syntax for type date: "a"
+LINE 1: ...rpart PARTITION OF range_parted FOR VALUES START ('a') END (...
+                                                             ^
+-- check if compatible with the specified parent
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE unparted (
+	a int
+);
+CREATE TABLE 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 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,
+	b int
+) PARTITION BY RANGE (a, b) WITHOUT OIDS;
+CREATE TABLE part PARTITION OF no_oids_parted FOR VALUES IN ('a') WITH OIDS;
+ERROR:  cannot create table with OIDs as partition of table without OIDs
+DROP TABLE no_oids_parted;
+-- check for partition bound overlap and other invalid specifications
+CREATE TABLE list_parted2 (
+	a varchar
+) PARTITION BY LIST (a);
+CREATE TABLE nulls_z_part PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
+CREATE TABLE ab_part PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE fail_nulls_part PARTITION OF list_parted2 FOR VALUES IN (null);
+ERROR:  new partition's list of values overlaps with partition "nulls_z_part" of "list_parted2"
+HINT:  Please specify a list of values that does not overlap with any existing partition's list.
+CREATE TABLE fail_bc_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+ERROR:  new partition's list of values overlaps with partition "ab_part" of "list_parted2"
+HINT:  Please specify a list of values that does not overlap with any existing partition's list.
+CREATE TABLE range_parted2 (
+	a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (0);
+ERROR:  new partition's range contains no element
+HINT:  Please specify a non-empty range.
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (1);
+ERROR:  new partition's range contains no element
+HINT:  Please specify a non-empty range.
+CREATE TABLE part_1_1 PARTITION OF range_parted2 FOR VALUES START (1) END (1) INCLUSIVE;
+CREATE TABLE part_unb_1 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (1);
+CREATE TABLE fail_unb_2 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (2);
+ERROR:  new partition's range overlaps with partition "part_unb_1" of "range_parted2"
+HINT:  Please specify a range that does not overlap with any existing partition's range.
+CREATE TABLE part_2_10_inc PARTITION OF range_parted2 FOR VALUES START (2) END (10) INCLUSIVE;
+CREATE TABLE fail_part_5_15 PARTITION OF range_parted2 FOR VALUES START (5) END (15);
+ERROR:  new partition's range overlaps with partition "part_2_10_inc" of "range_parted2"
+HINT:  Please specify a range that does not overlap with any existing partition's range.
+CREATE TABLE fail_part_10_20 PARTITION OF range_parted2 FOR VALUES START (10) END (20);
+ERROR:  new partition's range overlaps with partition "part_2_10_inc" of "range_parted2"
+HINT:  Please specify a range that does not overlap with any existing partition's range.
+-- check for multi-column range partition key where tuple comparison occurs
+CREATE TABLE range_parted3 (
+	a varchar,
+	b int
+) PARTITION BY RANGE (a, b);
+CREATE TABLE part_a_1_a_10 PARTITION OF range_parted3 FOR VALUES START ('a', 1) END ('a', 10);
+CREATE TABLE part_a_10_a_20 PARTITION OF range_parted3 FOR VALUES START ('a', 10) END ('a', 20);
+CREATE TABLE fail_part_a_15_a_25 PARTITION OF range_parted3 FOR VALUES START ('a', 15) END ('a', 25);
+ERROR:  new partition's range overlaps with partition "part_a_10_a_20" of "range_parted3"
+HINT:  Please specify a range that does not overlap with any existing partition's range.
+CREATE TABLE part_b_1_b_10 PARTITION OF range_parted3 FOR VALUES START ('b', 1) END ('b', 10);
+CREATE TABLE part_b_10_b_20 PARTITION OF range_parted3 FOR VALUES START ('b', 10) END ('b', 20);
+CREATE TABLE fail_part_b_5_b_15 PARTITION OF range_parted3 FOR VALUES START ('b', 5) END ('b', 15);
+ERROR:  new partition's range overlaps with partition "part_b_1_b_10" of "range_parted3"
+HINT:  Please specify a range that does not overlap with any existing partition's range.
+-- check schema propagation from parent
+CREATE TABLE parted (
+	a text,
+	b int NOT NULL DEFAULT 1,
+	CONSTRAINT check_b CHECK (b > 0) NO INHERIT
+) PARTITION BY LIST (a);
+CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a');
+-- the above command creates inheritance
+SELECT count(*) FROM pg_inherits WHERE inhrelid = 'part_a'::regclass;
+ count 
+-------
+     1
+(1 row)
+
+-- NO INHERIT on a parent's constraint ignored
+SELECT connoinherit FROM pg_constraint WHERE conrelid = 'part_a'::regclass AND conname = 'check_b';
+ connoinherit 
+--------------
+ f
+(1 row)
+
+-- specify a column option overriding parent's and table constraint which is merged
+CREATE TABLE part_b PARTITION OF parted (
+	b WITH OPTIONS DEFAULT 10,
+	CONSTRAINT check_b CHECK (b > 0)
+) FOR VALUES IN ('b');
+NOTICE:  merging constraint "check_b" with inherited definition
+SELECT conislocal, connoinherit FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_b';
+ conislocal | connoinherit 
+------------+--------------
+ t          | f
+(1 row)
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_col_part_c 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 partition of partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES START (1) END (10);
+-- partition cannot be dropped directly
+DROP TABLE part_a;
+ERROR:  "part_a" is a partition of "parted"
+HINT:  Use ALTER TABLE DETACH PARTITION to be able to drop it.
+-- need to specify CASCADE to drop partitions along with the parent
+DROP TABLE parted;
+ERROR:  cannot drop partitioned table parted because other objects depend on it
+DETAIL:  table part_a depends on partitioned table parted
+table part_b depends on partitioned table parted
+partitioned table part_c depends on partitioned table parted
+table part_c_1_10 depends on partitioned 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 15 other objects
+DETAIL:  drop cascades to table part_a_1_a_10
+drop cascades to table part_a_10_a_20
+drop cascades to table part_b_1_b_10
+drop cascades to table part_b_10_b_20
+drop cascades to table part_1_1
+drop cascades to table part_unb_1
+drop cascades to table part_2_10_inc
+drop cascades to table nulls_z_part
+drop cascades to table ab_part
+drop cascades to table lpart1
+drop cascades to table lpart3
+drop cascades to table part_a
+drop cascades to table part_b
+drop cascades to partitioned table part_c
+drop cascades to table part_c_1_10
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index b1e473d..57bde9d 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -118,6 +118,7 @@ pg_namespace|t
 pg_opclass|t
 pg_operator|t
 pg_opfamily|t
+pg_partition|t
 pg_partitioned|t
 pg_pltemplate|t
 pg_policy|t
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index f11f856..b9a808f 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1873,4 +1873,155 @@ CREATE TABLE no_inh_child (
 ) PARTITION BY RANGE (a);
 CREATE TABLE inh_parent(a int);
 ALTER TABLE no_inh_child INHERIT inh_parent;
-DROP TABLE no_inh_child, inh_parent, partitioned;
+DROP TABLE no_inh_child, inh_parent;
+
+-- ATTACH PARTITION
+
+-- check target table 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 partition bounds compatible
+CREATE TABLE list_parted (
+	a int,
+	b char(2) NOT NULL 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 START (1) END (10);
+DROP TABLE fail_part;
+
+-- check the table being attached exists
+ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
+
+-- XXX add ownership tests
+
+-- check the table being attached is not itself partitioned
+CREATE TABLE another_parted (
+	a int
+) PARTITION BY RANGE (a);
+ALTER TABLE list_parted ATTACH PARTITION another_parted FOR VALUES IN (1);
+DROP TABLE another_parted;
+
+-- check the table being attached is not inheritance child of some relation
+CREATE TABLE parent (LIKE list_parted);
+CREATE TABLE fail_part () INHERITS (parent);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE parent CASCADE;
+
+-- check 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 the 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 the table being attached does not have columns not 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 the table being attached has all columns of the parent
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the columns of the table being attached match in type, collation and NOT NULL status
+CREATE TABLE fail_part (
+	a int,
+	b int
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ALTER TABLE fail_part ALTER b TYPE char (3);
+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 the table being attached all constraints of the parent
+CREATE TABLE fail_part (
+	a int,
+	b char(2) NOT NULL COLLATE "en_US"
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+-- check the constraint of table being attached 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;
+
+-- finally check the attincount and coninhcount after successfully attached
+CREATE TABLE part_1 (
+	a int,
+	b char(2) NOT NULL COLLATE "en_US",
+	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+SELECT attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+SELECT coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+-- also check connoinherit on the partition's constraint, it must have been turned off
+SELECT connoinherit FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+
+-- check the new partition does not overlap with existing partition
+CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+-- check the new partition does not contain values outside specified bound
+INSERT INTO fail_part VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+
+-- ok if NO VALIDATE is specified
+ALTER TABLE fail_part RENAME TO part_2;
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (2) NO VALIDATE;
+
+-- check the table being attached is not already a partition
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (1);
+
+-- DETACH PARTITION
+
+-- check the partition being detached exists at all
+ALTER TABLE list_parted DETACH PARTITION part_3;
+
+-- check the partition being detached is a partition (of the parent)
+CREATE TABLE not_a_part (a int);
+ALTER TABLE list_parted DETACH PARTITION not_a_part;
+
+-- check that attinhcount and coninhcount dropped to 0 after detached
+ALTER TABLE list_parted DETACH PARTITION part_2;
+SELECT attinhcount FROM pg_attribute WHERE attrelid = 'part_2'::regclass AND attnum > 0;
+SELECT coninhcount FROM pg_constraint WHERE conrelid = 'part_2'::regclass AND conname = 'check_a';
+
+-- Miscellaneous ALTER TABLE special behaviors for partitions
+
+-- cannot add/drop a column to/from a partition or rename it
+ALTER TABLE part_1 ADD COLUMN c text;
+ALTER TABLE part_1 DROP COLUMN b;
+ALTER TABLE part_1 RENAME COLUMN b to c;
+
+-- cannot alter type of a column of a partition
+ALTER TABLE part_1 ALTER COLUMN b TYPE text;
+
+-- cannot let a partition participate in regular inheritance
+CREATE TABLE inh_test () INHERITS (part_1);
+CREATE TABLE inh_test (LIKE part_1);
+ALTER TABLE inh_test INHERIT part_1;
+ALTER TABLE part_1 INHERIT inh_test;
+
+-- cannot alter DROP NOT NULL on a partition column if the parent has NOT NULL set
+ALTER TABLE part_1 ALTER b DROP NOT NULL;
+
+-- cleanup
+DROP TABLE list_parted CASCADE;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 7c7a18a..1e14c4a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -417,3 +417,138 @@ CREATE TABLE describe_list_key (
 ) PARTITION BY LIST (a);
 \d describe_list_key
 DROP TABLE describe_range_key, describe_list_key;
+
+--
+-- CREATE TABLE PARTITION OF
+--
+
+-- check partition bound syntax
+
+CREATE TABLE list_parted (
+	a int
+) PARTITION BY LIST (a);
+-- syntax allows only string literal, numeric and null to be specified for
+-- a partition bound value
+CREATE TABLE lpart1 PARTITION OF list_parted FOR VALUES IN ('1');
+CREATE TABLE lpart2 PARTITION OF list_parted FOR VALUES IN (1);
+CREATE TABLE lpart3 PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN (int '1');
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ('1'::int);
+
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ();
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES START (1) END (2);
+
+CREATE TABLE range_parted (
+	a date
+) PARTITION BY RANGE (a);
+
+-- trying to specify list for range partitioned table
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES IN ('a');
+-- both start and end bounds of a range partition cannot be UNBOUNDED
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START UNBOUNDED END UNBOUNDED;
+-- each of start and end bounds must have same number of values as there
+-- are columns in the partition key
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z', 1);
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('z', 1);
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z');
+
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('b');
+
+-- check if compatible with the specified parent
+
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE unparted (
+	a int
+);
+CREATE TABLE 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 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,
+	b int
+) PARTITION BY RANGE (a, b) WITHOUT OIDS;
+CREATE TABLE part PARTITION OF no_oids_parted FOR VALUES IN ('a') WITH OIDS;
+DROP TABLE no_oids_parted;
+
+-- check for partition bound overlap and other invalid specifications
+
+CREATE TABLE list_parted2 (
+	a varchar
+) PARTITION BY LIST (a);
+CREATE TABLE nulls_z_part PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
+CREATE TABLE ab_part PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+
+CREATE TABLE fail_nulls_part PARTITION OF list_parted2 FOR VALUES IN (null);
+CREATE TABLE fail_bc_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+
+CREATE TABLE range_parted2 (
+	a int
+) PARTITION BY RANGE (a);
+
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (0);
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (1);
+CREATE TABLE part_1_1 PARTITION OF range_parted2 FOR VALUES START (1) END (1) INCLUSIVE;
+CREATE TABLE part_unb_1 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (1);
+CREATE TABLE fail_unb_2 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (2);
+CREATE TABLE part_2_10_inc PARTITION OF range_parted2 FOR VALUES START (2) END (10) INCLUSIVE;
+CREATE TABLE fail_part_5_15 PARTITION OF range_parted2 FOR VALUES START (5) END (15);
+CREATE TABLE fail_part_10_20 PARTITION OF range_parted2 FOR VALUES START (10) END (20);
+
+-- check for multi-column range partition key where tuple comparison occurs
+CREATE TABLE range_parted3 (
+	a varchar,
+	b int
+) PARTITION BY RANGE (a, b);
+
+CREATE TABLE part_a_1_a_10 PARTITION OF range_parted3 FOR VALUES START ('a', 1) END ('a', 10);
+CREATE TABLE part_a_10_a_20 PARTITION OF range_parted3 FOR VALUES START ('a', 10) END ('a', 20);
+CREATE TABLE fail_part_a_15_a_25 PARTITION OF range_parted3 FOR VALUES START ('a', 15) END ('a', 25);
+CREATE TABLE part_b_1_b_10 PARTITION OF range_parted3 FOR VALUES START ('b', 1) END ('b', 10);
+CREATE TABLE part_b_10_b_20 PARTITION OF range_parted3 FOR VALUES START ('b', 10) END ('b', 20);
+CREATE TABLE fail_part_b_5_b_15 PARTITION OF range_parted3 FOR VALUES START ('b', 5) END ('b', 15);
+
+-- check schema propagation from parent
+
+CREATE TABLE parted (
+	a text,
+	b int NOT NULL DEFAULT 1,
+	CONSTRAINT check_b CHECK (b > 0) NO INHERIT
+) PARTITION BY LIST (a);
+
+CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a');
+-- the above command creates inheritance
+SELECT count(*) FROM pg_inherits WHERE inhrelid = 'part_a'::regclass;
+-- NO INHERIT on a parent's constraint ignored
+SELECT connoinherit FROM pg_constraint WHERE conrelid = 'part_a'::regclass AND conname = 'check_b';
+
+-- specify a column option overriding parent's and table constraint which is merged
+CREATE TABLE part_b PARTITION OF parted (
+	b WITH OPTIONS DEFAULT 10,
+	CONSTRAINT check_b CHECK (b > 0)
+) FOR VALUES IN ('b');
+SELECT conislocal, connoinherit FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_b';
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_col_part_c 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 partition of partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES START (1) END (10);
+
+-- partition 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

